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 "dev.h"
6
7#include <hw/inout.h>
8#include <ddk/debug.h>
9#include <ddk/driver.h>
10#include <zircon/syscalls.h>
11#include <zircon/types.h>
12#include <threads.h>
13
14#include "errors.h"
15
16#define xprintf(fmt...) zxlogf(SPEW, fmt)
17
18/* EC commands */
19#define EC_CMD_READ 0x80
20#define EC_CMD_WRITE 0x81
21#define EC_CMD_QUERY 0x84
22
23/* EC status register bits */
24#define EC_SC_SCI_EVT (1 << 5)
25#define EC_SC_IBF (1 << 1)
26#define EC_SC_OBF (1 << 0)
27
28/* Thread signals */
29#define IRQ_RECEIVED ZX_EVENT_SIGNALED
30#define EC_THREAD_SHUTDOWN ZX_USER_SIGNAL_0
31#define EC_THREAD_SHUTDOWN_DONE ZX_USER_SIGNAL_1
32
33typedef struct acpi_ec_device {
34    zx_device_t* zxdev;
35
36    ACPI_HANDLE acpi_handle;
37
38    // PIO addresses for EC device
39    uint16_t cmd_port;
40    uint16_t data_port;
41
42    // GPE for EC events
43    ACPI_HANDLE gpe_block;
44    UINT32 gpe;
45
46    // thread for processing events from the EC
47    thrd_t evt_thread;
48
49    zx_handle_t interrupt_event;
50
51    bool gpe_setup : 1;
52    bool thread_setup : 1;
53    bool ec_space_setup : 1;
54} acpi_ec_device_t;
55
56static ACPI_STATUS get_ec_handle(ACPI_HANDLE, UINT32, void*, void**);
57static ACPI_STATUS get_ec_gpe_info(ACPI_HANDLE, ACPI_HANDLE*, UINT32*);
58static ACPI_STATUS get_ec_ports(ACPI_HANDLE, uint16_t*, uint16_t*);
59
60static ACPI_STATUS ec_space_setup_handler(ACPI_HANDLE Region, UINT32 Function,
61                                          void* HandlerContext, void** ReturnContext);
62static ACPI_STATUS ec_space_request_handler(UINT32 Function, ACPI_PHYSICAL_ADDRESS Address,
63                                            UINT32 BitWidth, UINT64* Value,
64                                            void* HandlerContext, void* RegionContext);
65
66static zx_status_t wait_for_interrupt(acpi_ec_device_t* dev);
67static zx_status_t execute_read_op(acpi_ec_device_t* dev, uint8_t addr, uint8_t* val);
68static zx_status_t execute_write_op(acpi_ec_device_t* dev, uint8_t addr, uint8_t val);
69static zx_status_t execute_query_op(acpi_ec_device_t* dev, uint8_t* val);
70
71// Execute the EC_CMD_READ operation.  Requires the ACPI global lock be held.
72static zx_status_t execute_read_op(acpi_ec_device_t* dev, uint8_t addr, uint8_t* val) {
73    // Issue EC command
74    outp(dev->cmd_port, EC_CMD_READ);
75
76    // Wait for EC to read the command so we can write the address
77    while (inp(dev->cmd_port) & EC_SC_IBF) {
78        zx_status_t status = wait_for_interrupt(dev);
79        if (status != ZX_OK) {
80            return status;
81        }
82    }
83
84    // Specify the address
85    outp(dev->data_port, addr);
86
87    // Wait for EC to read the address and write a response
88    while ((inp(dev->cmd_port) & (EC_SC_OBF | EC_SC_IBF)) != EC_SC_OBF) {
89        zx_status_t status = wait_for_interrupt(dev);
90        if (status != ZX_OK) {
91            return status;
92        }
93    }
94
95    // Read the response
96    *val = inp(dev->data_port);
97    return ZX_OK;
98}
99
100// Execute the EC_CMD_WRITE operation.  Requires the ACPI global lock be held.
101static zx_status_t execute_write_op(acpi_ec_device_t* dev, uint8_t addr, uint8_t val) {
102    // Issue EC command
103    outp(dev->cmd_port, EC_CMD_WRITE);
104
105    // Wait for EC to read the command so we can write the address
106    while (inp(dev->cmd_port) & EC_SC_IBF) {
107        zx_status_t status = wait_for_interrupt(dev);
108        if (status != ZX_OK) {
109            return status;
110        }
111    }
112
113    // Specify the address
114    outp(dev->data_port, addr);
115
116    // Wait for EC to read the address
117    while (inp(dev->cmd_port) & EC_SC_IBF) {
118        zx_status_t status = wait_for_interrupt(dev);
119        if (status != ZX_OK) {
120            return status;
121        }
122    }
123
124    // Write the data
125    outp(dev->data_port, val);
126
127    // Wait for EC to read the data
128    while (inp(dev->cmd_port) & EC_SC_IBF) {
129        zx_status_t status = wait_for_interrupt(dev);
130        if (status != ZX_OK) {
131            return status;
132        }
133    }
134
135    return ZX_OK;
136}
137
138// Execute the EC_CMD_QUERY operation.  Requires the ACPI global lock be held.
139static zx_status_t execute_query_op(acpi_ec_device_t* dev, uint8_t* event) {
140    // Query EC command
141    outp(dev->cmd_port, EC_CMD_QUERY);
142
143    // Wait for EC to respond
144    while ((inp(dev->cmd_port) & (EC_SC_OBF | EC_SC_IBF)) != EC_SC_OBF) {
145        zx_status_t status = wait_for_interrupt(dev);
146        if (status != ZX_OK) {
147            return status;
148        }
149    }
150
151    *event = inp(dev->data_port);
152    return ZX_OK;
153}
154
155static ACPI_STATUS ec_space_setup_handler(ACPI_HANDLE Region, UINT32 Function,
156                                          void* HandlerContext, void** ReturnContext) {
157    acpi_ec_device_t* dev = HandlerContext;
158    *ReturnContext = dev;
159
160    if (Function == ACPI_REGION_ACTIVATE) {
161        xprintf("acpi-ec: Setting up EC region\n");
162        return AE_OK;
163    } else if (Function == ACPI_REGION_DEACTIVATE) {
164        xprintf("acpi-ec: Tearing down EC region\n");
165        return AE_OK;
166    } else {
167        return AE_SUPPORT;
168    }
169}
170
171static ACPI_STATUS ec_space_request_handler(UINT32 Function, ACPI_PHYSICAL_ADDRESS Address,
172                                            UINT32 BitWidth, UINT64* Value,
173                                            void* HandlerContext, void* RegionContext) {
174    acpi_ec_device_t* dev = HandlerContext;
175
176    if (BitWidth != 8 && BitWidth != 16 && BitWidth != 32 && BitWidth != 64) {
177        return AE_BAD_PARAMETER;
178    }
179    if (Address > UINT8_MAX || Address - 1 + BitWidth / 8 > UINT8_MAX) {
180        return AE_BAD_PARAMETER;
181    }
182
183    UINT32 global_lock;
184    while (AcpiAcquireGlobalLock(0xFFFF, &global_lock) != AE_OK)
185        ;
186
187    // NB: The processing of the read/write ops below will generate interrupts,
188    // which will unfortunately cause spurious wakeups on the event thread.  One
189    // design that would avoid this is to have that thread responsible for
190    // processing these EC address space requests, but an attempt at an
191    // implementation failed due to apparent deadlocks against the Global Lock.
192
193    const size_t bytes = BitWidth / 8;
194    ACPI_STATUS status = AE_OK;
195    uint8_t* value_bytes = (uint8_t*)Value;
196    if (Function == ACPI_WRITE) {
197        for (size_t i = 0; i < bytes; ++i) {
198            zx_status_t zx_status = execute_write_op(dev, Address + i, value_bytes[i]);
199            if (zx_status != ZX_OK) {
200                status = AE_ERROR;
201                goto finish;
202            }
203        }
204    } else {
205        *Value = 0;
206        for (size_t i = 0; i < bytes; ++i) {
207            zx_status_t zx_status = execute_read_op(dev, Address + i, value_bytes + i);
208            if (zx_status != ZX_OK) {
209                status = AE_ERROR;
210                goto finish;
211            }
212        }
213    }
214
215finish:
216    AcpiReleaseGlobalLock(global_lock);
217    return status;
218}
219
220static zx_status_t wait_for_interrupt(acpi_ec_device_t* dev) {
221    uint32_t pending;
222    zx_status_t status = zx_object_wait_one(dev->interrupt_event,
223                                            IRQ_RECEIVED | EC_THREAD_SHUTDOWN,
224                                            ZX_TIME_INFINITE,
225                                            &pending);
226    if (status != ZX_OK) {
227        printf("acpi-ec: thread wait failed: %d\n", status);
228        zx_object_signal(dev->interrupt_event, 0, EC_THREAD_SHUTDOWN_DONE);
229        return status;
230    }
231
232    if (pending & EC_THREAD_SHUTDOWN) {
233        zx_object_signal(dev->interrupt_event, 0, EC_THREAD_SHUTDOWN_DONE);
234        return ZX_ERR_STOP;
235    }
236
237    /* Clear interrupt */
238    zx_object_signal(dev->interrupt_event, IRQ_RECEIVED, 0);
239    return ZX_OK;
240}
241
242static int acpi_ec_thread(void* arg) {
243    acpi_ec_device_t* dev = arg;
244    UINT32 global_lock;
245
246    while (1) {
247        zx_status_t zx_status = wait_for_interrupt(dev);
248        if (zx_status != ZX_OK) {
249            goto exiting_without_lock;
250        }
251
252        while (AcpiAcquireGlobalLock(0xFFFF, &global_lock) != AE_OK)
253            ;
254
255        uint8_t status;
256        bool processed_evt = false;
257        while ((status = inp(dev->cmd_port)) & EC_SC_SCI_EVT) {
258            uint8_t event_code;
259            zx_status_t zx_status = execute_query_op(dev, &event_code);
260            if (zx_status != ZX_OK) {
261                goto exiting_with_lock;
262            }
263
264            if (event_code != 0) {
265                char method[5] = {0};
266                snprintf(method, sizeof(method), "_Q%02x", event_code);
267                xprintf("acpi-ec: Invoking method %s\n", method);
268                AcpiEvaluateObject(dev->acpi_handle, method, NULL, NULL);
269                xprintf("acpi-ec: Invoked method %s\n", method);
270            } else {
271                xprintf("acpi-ec: Spurious event?\n");
272            }
273
274            processed_evt = true;
275
276            /* Clear interrupt before we check EVT again, to prevent a spurious
277             * interrupt later.  There could be two sources of that spurious
278             * wakeup: Either we handled two events back-to-back, or we didn't
279             * wait for the OBF interrupt above. */
280            zx_object_signal(dev->interrupt_event, IRQ_RECEIVED, 0);
281        }
282
283        if (!processed_evt) {
284            xprintf("acpi-ec: Spurious wakeup, no evt: %#x\n", status);
285        }
286
287        AcpiReleaseGlobalLock(global_lock);
288    }
289
290exiting_with_lock:
291    AcpiReleaseGlobalLock(global_lock);
292exiting_without_lock:
293    xprintf("acpi-ec: thread terminated\n");
294    return 0;
295}
296
297static uint32_t raw_ec_event_gpe_handler(ACPI_HANDLE gpe_dev, uint32_t gpe_num, void* ctx) {
298    acpi_ec_device_t* dev = ctx;
299    zx_object_signal(dev->interrupt_event, 0, IRQ_RECEIVED);
300    return ACPI_REENABLE_GPE;
301}
302
303static ACPI_STATUS get_ec_handle(
304    ACPI_HANDLE object,
305    UINT32 nesting_level,
306    void* context,
307    void** ret) {
308
309    *(ACPI_HANDLE*)context = object;
310    return AE_OK;
311}
312
313static ACPI_STATUS get_ec_gpe_info(
314    ACPI_HANDLE ec_handle, ACPI_HANDLE* gpe_block, UINT32* gpe) {
315    ACPI_BUFFER buffer = {
316        .Length = ACPI_ALLOCATE_BUFFER,
317        .Pointer = NULL,
318    };
319    ACPI_STATUS status = AcpiEvaluateObject(
320        ec_handle, (char*)"_GPE", NULL, &buffer);
321    if (status != AE_OK) {
322        return status;
323    }
324
325    /* According to section 12.11 of ACPI v6.1, a _GPE object on this device
326     * evaluates to either an integer specifying bit in the GPEx_STS blocks
327     * to use, or a package specifying which GPE block and which bit inside
328     * that block to use. */
329    ACPI_OBJECT* gpe_obj = buffer.Pointer;
330    if (gpe_obj->Type == ACPI_TYPE_INTEGER) {
331        *gpe_block = NULL;
332        *gpe = gpe_obj->Integer.Value;
333    } else if (gpe_obj->Type == ACPI_TYPE_PACKAGE) {
334        if (gpe_obj->Package.Count != 2) {
335            goto bailout;
336        }
337        ACPI_OBJECT* block_obj = &gpe_obj->Package.Elements[0];
338        ACPI_OBJECT* gpe_num_obj = &gpe_obj->Package.Elements[1];
339        if (block_obj->Type != ACPI_TYPE_LOCAL_REFERENCE) {
340            goto bailout;
341        }
342        if (gpe_num_obj->Type != ACPI_TYPE_INTEGER) {
343            goto bailout;
344        }
345        *gpe_block = block_obj->Reference.Handle;
346        *gpe = gpe_num_obj->Integer.Value;
347    } else {
348        goto bailout;
349    }
350    ACPI_FREE(buffer.Pointer);
351    return AE_OK;
352
353bailout:
354    xprintf("Failed to intepret EC GPE number");
355    ACPI_FREE(buffer.Pointer);
356    return AE_BAD_DATA;
357}
358
359struct ec_ports_callback_ctx {
360    uint16_t* data_port;
361    uint16_t* cmd_port;
362    unsigned int resource_num;
363};
364
365static ACPI_STATUS get_ec_ports_callback(
366    ACPI_RESOURCE* Resource, void* Context) {
367    struct ec_ports_callback_ctx* ctx = Context;
368
369    if (Resource->Type == ACPI_RESOURCE_TYPE_END_TAG) {
370        return AE_OK;
371    }
372
373    /* The spec says there will be at most 3 resources */
374    if (ctx->resource_num >= 3) {
375        return AE_BAD_DATA;
376    }
377    /* The third resource only exists on HW-Reduced platforms, which we don't
378     * support at the moment. */
379    if (ctx->resource_num == 2) {
380        xprintf("RESOURCE TYPE %d\n", Resource->Type);
381        return AE_NOT_IMPLEMENTED;
382    }
383
384    /* The two resources we're expecting are both address regions.  First the
385     * data one, then the command one.  We assume they're single IO ports. */
386    if (Resource->Type != ACPI_RESOURCE_TYPE_IO) {
387        return AE_SUPPORT;
388    }
389    if (Resource->Data.Io.Maximum != Resource->Data.Io.Minimum) {
390        return AE_SUPPORT;
391    }
392
393    uint16_t port = Resource->Data.Io.Minimum;
394    if (ctx->resource_num == 0) {
395        *ctx->data_port = port;
396    } else {
397        *ctx->cmd_port = port;
398    }
399
400    ctx->resource_num++;
401    return AE_OK;
402}
403
404static ACPI_STATUS get_ec_ports(
405    ACPI_HANDLE ec_handle, uint16_t* data_port, uint16_t* cmd_port) {
406    struct ec_ports_callback_ctx ctx = {
407        .data_port = data_port,
408        .cmd_port = cmd_port,
409        .resource_num = 0,
410    };
411
412    return AcpiWalkResources(ec_handle, (char*)"_CRS", get_ec_ports_callback, &ctx);
413}
414
415static void acpi_ec_release(void* ctx) {
416    acpi_ec_device_t* dev = ctx;
417
418    if (dev->ec_space_setup) {
419        AcpiRemoveAddressSpaceHandler(ACPI_ROOT_OBJECT, ACPI_ADR_SPACE_EC, ec_space_request_handler);
420    }
421
422    if (dev->gpe_setup) {
423        AcpiDisableGpe(dev->gpe_block, dev->gpe);
424        AcpiRemoveGpeHandler(dev->gpe_block, dev->gpe, raw_ec_event_gpe_handler);
425    }
426
427    if (dev->interrupt_event != ZX_HANDLE_INVALID) {
428        if (dev->thread_setup) {
429            /* Shutdown the EC thread */
430            zx_object_signal(dev->interrupt_event, 0, EC_THREAD_SHUTDOWN);
431            zx_object_wait_one(dev->interrupt_event, EC_THREAD_SHUTDOWN_DONE, ZX_TIME_INFINITE, NULL);
432            thrd_join(dev->evt_thread, NULL);
433        }
434
435        zx_handle_close(dev->interrupt_event);
436    }
437
438    free(dev);
439}
440
441static zx_status_t acpi_ec_suspend(void* ctx, uint32_t flags) {
442    acpi_ec_device_t* dev = ctx;
443
444    if (flags != DEVICE_SUSPEND_FLAG_MEXEC) {
445        return ZX_ERR_NOT_SUPPORTED;
446    }
447
448    AcpiRemoveAddressSpaceHandler(ACPI_ROOT_OBJECT, ACPI_ADR_SPACE_EC, ec_space_request_handler);
449    dev->ec_space_setup = false;
450
451    AcpiDisableGpe(dev->gpe_block, dev->gpe);
452    AcpiRemoveGpeHandler(dev->gpe_block, dev->gpe, raw_ec_event_gpe_handler);
453    dev->gpe_setup = false;
454
455    zx_object_signal(dev->interrupt_event, 0, EC_THREAD_SHUTDOWN);
456    zx_object_wait_one(dev->interrupt_event, EC_THREAD_SHUTDOWN_DONE, ZX_TIME_INFINITE, NULL);
457    thrd_join(dev->evt_thread, NULL);
458    zx_handle_close(dev->interrupt_event);
459    dev->interrupt_event = ZX_HANDLE_INVALID;
460    return ZX_OK;
461}
462
463static zx_protocol_device_t acpi_ec_device_proto = {
464    .version = DEVICE_OPS_VERSION,
465    .release = acpi_ec_release,
466    .suspend = acpi_ec_suspend,
467};
468
469zx_status_t ec_init(zx_device_t* parent, ACPI_HANDLE acpi_handle) {
470    xprintf("acpi-ec: init\n");
471
472    acpi_ec_device_t* dev = calloc(1, sizeof(acpi_ec_device_t));
473    if (!dev) {
474        return ZX_ERR_NO_MEMORY;
475    }
476    dev->acpi_handle = acpi_handle;
477
478    zx_status_t err = zx_event_create(0, &dev->interrupt_event);
479    if (err != ZX_OK) {
480        xprintf("acpi-ec: Failed to create event: %d\n", err);
481        acpi_ec_release(dev);
482        return err;
483    }
484
485    ACPI_STATUS status = get_ec_gpe_info(acpi_handle, &dev->gpe_block, &dev->gpe);
486    if (status != AE_OK) {
487        xprintf("acpi-ec: Failed to decode GPE info: %d\n", status);
488        goto acpi_error;
489    }
490
491    status = get_ec_ports(
492        acpi_handle, &dev->data_port, &dev->cmd_port);
493    if (status != AE_OK) {
494        xprintf("acpi-ec: Failed to decode comm info: %d\n", status);
495        goto acpi_error;
496    }
497
498    /* Setup GPE handling */
499    status = AcpiInstallGpeHandler(
500        dev->gpe_block, dev->gpe, ACPI_GPE_EDGE_TRIGGERED,
501        raw_ec_event_gpe_handler, dev);
502    if (status != AE_OK) {
503        xprintf("acpi-ec: Failed to install GPE %d: %x\n", dev->gpe, status);
504        goto acpi_error;
505    }
506    status = AcpiEnableGpe(dev->gpe_block, dev->gpe);
507    if (status != AE_OK) {
508        xprintf("acpi-ec: Failed to enable GPE %d: %x\n", dev->gpe, status);
509        AcpiRemoveGpeHandler(dev->gpe_block, dev->gpe, raw_ec_event_gpe_handler);
510        goto acpi_error;
511    }
512    dev->gpe_setup = true;
513
514    /* TODO(teisenbe): This thread should ideally be at a high priority, since
515       it takes the ACPI global lock which is shared with SMM. */
516    int ret = thrd_create_with_name(&dev->evt_thread, acpi_ec_thread, dev, "acpi-ec-evt");
517    if (ret != thrd_success) {
518        xprintf("acpi-ec: Failed to create thread\n");
519        acpi_ec_release(dev);
520        return ZX_ERR_INTERNAL;
521    }
522    dev->thread_setup = true;
523
524    status = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT, ACPI_ADR_SPACE_EC,
525                                            ec_space_request_handler,
526                                            ec_space_setup_handler,
527                                            dev);
528    if (status != AE_OK) {
529        xprintf("acpi-ec: Failed to install ec space handler\n");
530        acpi_ec_release(dev);
531        return acpi_to_zx_status(status);
532    }
533    dev->ec_space_setup = true;
534
535    device_add_args_t args = {
536        .version = DEVICE_ADD_ARGS_VERSION,
537        .name = "acpi-ec",
538        .ctx = dev,
539        .ops = &acpi_ec_device_proto,
540        .proto_id = ZX_PROTOCOL_MISC,
541    };
542
543    status = device_add(parent, &args, &dev->zxdev);
544    if (status != ZX_OK) {
545        xprintf("acpi-ec: could not add device! err=%d\n", status);
546        acpi_ec_release(dev);
547        return status;
548    }
549
550    printf("acpi-ec: initialized\n");
551    return ZX_OK;
552
553acpi_error:
554    acpi_ec_release(dev);
555    return acpi_to_zx_status(status);
556}
557