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/binding.h>
6 #include <ddk/debug.h>
7 #include <ddk/device.h>
8 #include <ddk/driver.h>
9 
10 #include <acpica/acpi.h>
11 #include <fuchsia/hardware/power/c/fidl.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <threads.h>
15 #include <zircon/syscalls.h>
16 #include <zircon/types.h>
17 
18 #include "dev.h"
19 #include "errors.h"
20 #include "power.h"
21 
22 typedef struct acpi_pwrsrc_device {
23     zx_device_t* zxdev;
24 
25     ACPI_HANDLE acpi_handle;
26 
27     // event to notify on
28     zx_handle_t event;
29 
30     power_info_t info;
31 
32     mtx_t lock;
33 } acpi_pwrsrc_device_t;
34 
call_PSR(acpi_pwrsrc_device_t * dev,bool notify)35 static zx_status_t call_PSR(acpi_pwrsrc_device_t* dev, bool notify) {
36     ACPI_OBJECT obj = {
37         .Type = ACPI_TYPE_INTEGER,
38     };
39     ACPI_BUFFER buffer = {
40         .Length = sizeof(obj),
41         .Pointer = &obj,
42     };
43     ACPI_STATUS acpi_status = AcpiEvaluateObject(dev->acpi_handle, (char*)"_PSR", NULL, &buffer);
44     if (acpi_status == AE_OK) {
45         mtx_lock(&dev->lock);
46         uint32_t state = dev->info.state;
47         if (obj.Integer.Value) {
48             dev->info.state |= POWER_STATE_ONLINE;
49         } else {
50             dev->info.state &= ~POWER_STATE_ONLINE;
51         }
52         zxlogf(DEBUG1, "acpi-pwrsrc: 0x%x -> 0x%x\n", state, dev->info.state);
53         if (notify && (state != dev->info.state)) {
54             zx_object_signal(dev->event, 0, ZX_USER_SIGNAL_0);
55         }
56         mtx_unlock(&dev->lock);
57     }
58     return acpi_to_zx_status(acpi_status);
59 }
60 
acpi_pwrsrc_notify(ACPI_HANDLE handle,UINT32 value,void * ctx)61 static void acpi_pwrsrc_notify(ACPI_HANDLE handle, UINT32 value, void* ctx) {
62     acpi_pwrsrc_device_t* dev = ctx;
63     zxlogf(TRACE, "acpi-pwrsrc: got event 0x%x\n", value);
64     call_PSR(dev, true);
65 }
66 
acpi_pwrsrc_release(void * ctx)67 static void acpi_pwrsrc_release(void* ctx) {
68     acpi_pwrsrc_device_t* dev = ctx;
69     AcpiRemoveNotifyHandler(dev->acpi_handle, ACPI_DEVICE_NOTIFY, acpi_pwrsrc_notify);
70     if (dev->event != ZX_HANDLE_INVALID) {
71         zx_handle_close(dev->event);
72     }
73     free(dev);
74 }
75 
fidl_pwrsrc_get_power_info(void * ctx,fidl_txn_t * txn)76 zx_status_t fidl_pwrsrc_get_power_info(void* ctx, fidl_txn_t* txn) {
77     acpi_pwrsrc_device_t* dev = ctx;
78     struct fuchsia_hardware_power_SourceInfo info;
79 
80     mtx_lock(&dev->lock);
81     info.state = dev->info.state;
82     info.type = dev->info.type;
83     mtx_unlock(&dev->lock);
84 
85     // reading state clears the signal
86     zx_object_signal(dev->event, ZX_USER_SIGNAL_0, 0);
87     return fuchsia_hardware_power_SourceGetPowerInfo_reply(txn, ZX_OK, &info);
88 }
89 
fidl_pwrsrc_get_state_change_event(void * ctx,fidl_txn_t * txn)90 zx_status_t fidl_pwrsrc_get_state_change_event(void* ctx, fidl_txn_t* txn) {
91     acpi_pwrsrc_device_t* dev = ctx;
92     zx_handle_t out_handle;
93     zx_rights_t rights = ZX_RIGHT_WAIT | ZX_RIGHT_TRANSFER;
94     zx_status_t status = zx_handle_duplicate(dev->event, rights, &out_handle);
95 
96     if (status == ZX_OK) {
97         // clear the signal before returning
98         zx_object_signal(dev->event, ZX_USER_SIGNAL_0, 0);
99     }
100     return fuchsia_hardware_power_SourceGetStateChangeEvent_reply(txn, status, out_handle);
101 }
102 
103 static fuchsia_hardware_power_Source_ops_t fidl_ops = {
104     .GetPowerInfo = fidl_pwrsrc_get_power_info,
105     .GetStateChangeEvent = fidl_pwrsrc_get_state_change_event,
106 };
107 
fuchsia_hardware_power_message_instance(void * ctx,fidl_msg_t * msg,fidl_txn_t * txn)108 static zx_status_t fuchsia_hardware_power_message_instance(void* ctx, fidl_msg_t* msg,
109                                                            fidl_txn_t* txn) {
110     return fuchsia_hardware_power_Source_dispatch(ctx, txn, msg, &fidl_ops);
111 }
112 
113 static zx_protocol_device_t acpi_pwrsrc_device_proto = {
114     .version = DEVICE_OPS_VERSION,
115     .message = fuchsia_hardware_power_message_instance,
116     .release = acpi_pwrsrc_release,
117 };
118 
pwrsrc_init(zx_device_t * parent,ACPI_HANDLE acpi_handle)119 zx_status_t pwrsrc_init(zx_device_t* parent, ACPI_HANDLE acpi_handle) {
120     acpi_pwrsrc_device_t* dev = calloc(1, sizeof(acpi_pwrsrc_device_t));
121     if (!dev) {
122         return ZX_ERR_NO_MEMORY;
123     }
124 
125     dev->acpi_handle = acpi_handle;
126     mtx_init(&dev->lock, mtx_plain);
127 
128     zx_status_t status = zx_event_create(0, &dev->event);
129     if (status != ZX_OK) {
130         free(dev);
131         return status;
132     }
133 
134     dev->info.type = POWER_TYPE_AC;
135     call_PSR(dev, false);
136 
137     ACPI_STATUS acpi_status = AcpiInstallNotifyHandler(acpi_handle, ACPI_DEVICE_NOTIFY,
138                                                        acpi_pwrsrc_notify, dev);
139     if (acpi_status != AE_OK) {
140         zxlogf(ERROR, "acpi-pwrsrc: could not install notify handler\n");
141         acpi_pwrsrc_release(dev);
142         return acpi_to_zx_status(acpi_status);
143     }
144 
145     // read initial value
146     acpi_pwrsrc_notify(acpi_handle, 0, dev);
147 
148     device_add_args_t args = {
149         .version = DEVICE_ADD_ARGS_VERSION,
150         .name = "acpi-pwrsrc",
151         .ctx = dev,
152         .ops = &acpi_pwrsrc_device_proto,
153         .proto_id = ZX_PROTOCOL_POWER,
154     };
155 
156     status = device_add(parent, &args, &dev->zxdev);
157     if (status != ZX_OK) {
158         zxlogf(ERROR, "acpi-pwrsrc: could not add device! err=%d\n", status);
159         acpi_pwrsrc_release(dev);
160         return status;
161     }
162 
163     zxlogf(TRACE, "acpi-pwrsrc: initialized\n");
164 
165     return ZX_OK;
166 }
167