1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <ddk/device.h>
6 #include <ddk/driver.h>
7 #include <ddk/binding.h>
8 #include <ddk/debug.h>
9 
10 #include <zircon/types.h>
11 #include <zircon/device/thermal.h>
12 #include <inttypes.h>
13 #include <stdlib.h>
14 #include <stdio.h>
15 
16 #include <acpica/acpi.h>
17 
18 #include "dev.h"
19 #include "errors.h"
20 #include "methods.h"
21 #include "util.h"
22 
23 #define INT3403_TYPE_SENSOR    0x03
24 #define INT3403_THERMAL_EVENT  0x90
25 
26 typedef struct acpi_thermal_device {
27     zx_device_t* zxdev;
28     ACPI_HANDLE acpi_handle;
29 
30     mtx_t lock;
31 
32     // event to notify on
33     zx_handle_t event;
34 
35     // programmable trip points
36     uint32_t trip_point_count;
37     uint32_t trip_points[9];
38 } acpi_thermal_device_t;
39 
acpi_thermal_get_info(acpi_thermal_device_t * dev,thermal_info_t * info)40 static zx_status_t acpi_thermal_get_info(acpi_thermal_device_t* dev, thermal_info_t* info) {
41     mtx_lock(&dev->lock);
42     zx_status_t st = ZX_OK;
43 
44     uint64_t temp;
45     st = acpi_psv_call(dev->acpi_handle, &temp);
46     if (st != ZX_OK) {
47         goto out;
48     }
49     info->passive_temp = (uint32_t)temp; // we probably won't exceed 429496456.35 C
50     st = acpi_crt_call(dev->acpi_handle, &temp);
51     if (st != ZX_OK) {
52         goto out;
53     }
54     info->critical_temp = (uint32_t)temp;
55 
56     info->max_trip_count = dev->trip_point_count;
57     memcpy(info->active_trip, dev->trip_points, sizeof(info->active_trip));
58 
59     st = acpi_tmp_call(dev->acpi_handle, &temp);
60     if (st != ZX_OK) {
61         goto out;
62     }
63     info->state = 0;
64     if (info->active_trip[0] && (temp > info->active_trip[0])) {
65         info->state |= THERMAL_STATE_TRIP_VIOLATION;
66     }
67 out:
68     mtx_unlock(&dev->lock);
69     return st;
70 }
71 
acpi_thermal_read(void * ctx,void * buf,size_t count,zx_off_t off,size_t * actual)72 static zx_status_t acpi_thermal_read(void* ctx, void* buf, size_t count, zx_off_t off,
73                                      size_t* actual) {
74     acpi_thermal_device_t* dev = ctx;
75     uint64_t v;
76 
77     if (count < sizeof(uint32_t)) {
78         return ZX_ERR_BUFFER_TOO_SMALL;
79     }
80 
81     zx_status_t status = acpi_tmp_call(dev->acpi_handle, &v);
82     if (status != ZX_OK) {
83         zxlogf(ERROR, "acpi-thermal: acpi error %d in _TMP\n", status);
84         return status;
85     }
86     uint32_t temp = (uint32_t)v;
87     memcpy(buf, &temp, sizeof(temp));
88     *actual = sizeof(temp);
89 
90     return ZX_OK;
91 }
92 
acpi_thermal_ioctl(void * ctx,uint32_t op,const void * in_buf,size_t in_len,void * out_buf,size_t out_len,size_t * out_actual)93 static zx_status_t acpi_thermal_ioctl(void* ctx, uint32_t op,
94                                       const void* in_buf, size_t in_len,
95                                       void* out_buf, size_t out_len, size_t* out_actual) {
96     acpi_thermal_device_t* dev = ctx;
97     switch (op) {
98     case IOCTL_THERMAL_GET_INFO: {
99         if (out_len != sizeof(thermal_info_t)) {
100             return ZX_ERR_INVALID_ARGS;
101         }
102 
103         // reading state clears the signal
104         zx_object_signal(dev->event, ZX_USER_SIGNAL_0, 0);
105 
106         thermal_info_t info;
107         zx_status_t status = acpi_thermal_get_info(dev, &info);
108         if (status != ZX_OK) {
109             return status;
110         }
111         memcpy(out_buf, &info, sizeof(info));
112         *out_actual = sizeof(info);
113         return ZX_OK;
114     }
115     case IOCTL_THERMAL_SET_TRIP: {
116         if (in_len != sizeof(trip_point_t)) {
117             return ZX_ERR_INVALID_ARGS;
118         }
119         if (dev->trip_point_count < 1) {
120             return ZX_ERR_NOT_SUPPORTED;
121         }
122         trip_point_t* tp = (trip_point_t*)in_buf;
123         // only one trip point for now
124         if (tp->id != 0) {
125             return ZX_ERR_INVALID_ARGS;
126         }
127         ACPI_STATUS acpi_status = acpi_evaluate_method_intarg(dev->acpi_handle,
128                                                               "PAT0", tp->temp);
129         if (acpi_status != AE_OK) {
130             zxlogf(ERROR, "acpi-thermal: acpi error %d in PAT0\n", acpi_status);
131             return acpi_to_zx_status(acpi_status);
132         }
133         mtx_lock(&dev->lock);
134         dev->trip_points[0] = tp->temp;
135         mtx_unlock(&dev->lock);
136         return ZX_OK;
137     }
138     case IOCTL_THERMAL_GET_STATE_CHANGE_EVENT: {
139         if (out_len != sizeof(zx_handle_t)) {
140             return ZX_ERR_INVALID_ARGS;
141         }
142         zx_handle_t* out = (zx_handle_t*)out_buf;
143         zx_status_t status = zx_handle_duplicate(dev->event, ZX_RIGHT_SAME_RIGHTS, out);
144         if (status != ZX_OK) {
145             return status;
146         }
147 
148         // clear the signal before returning
149         zx_object_signal(dev->event, ZX_USER_SIGNAL_0, 0);
150         *out_actual = sizeof(zx_handle_t);
151         return ZX_OK;
152     }
153     default:
154        return ZX_ERR_NOT_SUPPORTED;
155     }
156 }
157 
acpi_thermal_notify(ACPI_HANDLE handle,UINT32 value,void * ctx)158 static void acpi_thermal_notify(ACPI_HANDLE handle, UINT32 value, void* ctx) {
159     acpi_thermal_device_t* dev = ctx;
160     zxlogf(TRACE, "acpi-thermal: got event 0x%x\n", value);
161     switch (value) {
162     case INT3403_THERMAL_EVENT:
163         zx_object_signal(dev->event, 0, ZX_USER_SIGNAL_0);
164         break;
165     }
166 }
167 
acpi_thermal_release(void * ctx)168 static void acpi_thermal_release(void* ctx) {
169     acpi_thermal_device_t* dev = ctx;
170     AcpiRemoveNotifyHandler(dev->acpi_handle, ACPI_DEVICE_NOTIFY, acpi_thermal_notify);
171     zx_handle_close(dev->event);
172     free(dev);
173 }
174 
175 static zx_protocol_device_t acpi_thermal_device_proto = {
176     .version = DEVICE_OPS_VERSION,
177     .read = acpi_thermal_read,
178     .ioctl = acpi_thermal_ioctl,
179     .release = acpi_thermal_release,
180 };
181 
thermal_init(zx_device_t * parent,ACPI_DEVICE_INFO * info,ACPI_HANDLE acpi_handle)182 zx_status_t thermal_init(zx_device_t* parent, ACPI_DEVICE_INFO* info, ACPI_HANDLE acpi_handle) {
183     // only support sensors
184     uint64_t type;
185     ACPI_STATUS acpi_status = acpi_evaluate_integer(acpi_handle, "PTYP", &type);
186     if (acpi_status != AE_OK) {
187         zxlogf(ERROR, "acpi-thermal: acpi error %d in PTYP\n", acpi_status);
188         return acpi_to_zx_status(acpi_status);
189     }
190     if (type != INT3403_TYPE_SENSOR) {
191         return ZX_ERR_NOT_SUPPORTED;
192     }
193 
194     acpi_thermal_device_t* dev = calloc(1, sizeof(acpi_thermal_device_t));
195     if (!dev) {
196         return ZX_ERR_NO_MEMORY;
197     }
198 
199     dev->acpi_handle = acpi_handle;
200 
201     zx_status_t status = zx_event_create(0, &dev->event);
202     if (status != ZX_OK) {
203         zxlogf(ERROR, "acpi-thermal: error %d in zx_event_create\n", status);
204         acpi_thermal_release(dev);
205         return status;
206     }
207 
208     // install acpi event handler
209     acpi_status = AcpiInstallNotifyHandler(acpi_handle, ACPI_DEVICE_NOTIFY,
210                                            acpi_thermal_notify, dev);
211     if (acpi_status != AE_OK) {
212         zxlogf(ERROR, "acpi-thermal: could not install notify handler\n");
213         acpi_thermal_release(dev);
214         return acpi_to_zx_status(acpi_status);
215     }
216 
217     uint64_t v;
218     acpi_status = acpi_evaluate_integer(dev->acpi_handle, "PATC", &v);
219     if (acpi_status != AE_OK) {
220         zxlogf(ERROR, "acpi-thermal: could not get auxiliary trip count\n");
221         return acpi_status;
222     }
223     dev->trip_point_count = (uint32_t)v;
224 
225     char name[5];
226     memcpy(name, &info->Name, sizeof(UINT32));
227     name[4] = '\0';
228 
229    device_add_args_t args = {
230         .version = DEVICE_ADD_ARGS_VERSION,
231         .name = name,
232         .ctx = dev,
233         .ops = &acpi_thermal_device_proto,
234         .proto_id = ZX_PROTOCOL_THERMAL,
235     };
236 
237     status = device_add(parent, &args, &dev->zxdev);
238     if (status != ZX_OK) {
239         zxlogf(ERROR, "acpi-thermal: could not add device! err=%d\n", status);
240         acpi_thermal_release(dev);
241         return status;
242     }
243 
244     zxlogf(TRACE, "acpi-thermal: initialized '%s' %u trip points\n",
245            name, dev->trip_point_count);
246 
247     return ZX_OK;
248 }
249