1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright (c) 2018, Joyent, Inc.
14 */
15
16 /*
17 * ACPI driver that enumerates and creates USB nodes.
18 */
19
20 #include <sys/types.h>
21 #include <sys/atomic.h>
22 #include <sys/sunddi.h>
23 #include <sys/sunndi.h>
24 #include <sys/acpi/acpi.h>
25 #include <sys/acpica.h>
26 #include <sys/acpidev.h>
27 #include <sys/acpidev_impl.h>
28
29 /*
30 * List of class drivers which will be called in order when handling
31 * children of ACPI USB objects.
32 */
33 acpidev_class_list_t *acpidev_class_list_usbport = NULL;
34
35 static acpidev_filter_result_t acpidev_usbport_filter_cb(acpidev_walk_info_t *,
36 ACPI_HANDLE, acpidev_filter_rule_t *, char *, int);
37
38 static acpidev_filter_rule_t acpidev_usbport_filters[] = {
39 {
40 acpidev_usbport_filter_cb,
41 0,
42 ACPIDEV_FILTER_SCAN,
43 &acpidev_class_list_usbport,
44 3,
45 INT_MAX,
46 NULL,
47 NULL
48 }
49 };
50
51 /*
52 * We've been passed something by the general device scanner. This means that we
53 * were able to determine that the parent was a valid PCI device with a USB
54 * class code.
55 */
56 static ACPI_STATUS
acpidev_usbport_probe(acpidev_walk_info_t * infop)57 acpidev_usbport_probe(acpidev_walk_info_t *infop)
58 {
59 ACPI_STATUS ret;
60 int flags;
61
62 if (infop->awi_info->Type != ACPI_TYPE_DEVICE) {
63 return (AE_OK);
64 }
65
66 flags = ACPIDEV_PROCESS_FLAG_SCAN;
67 switch (infop->awi_op_type) {
68 case ACPIDEV_OP_BOOT_PROBE:
69 case ACPIDEV_OP_BOOT_REPROBE:
70 flags |= ACPIDEV_PROCESS_FLAG_CREATE;
71 break;
72 case ACPIDEV_OP_HOTPLUG_PROBE:
73 flags |= ACPIDEV_PROCESS_FLAG_CREATE |
74 ACPIDEV_PROCESS_FLAG_SYNCSTATUS |
75 ACPIDEV_PROCESS_FLAG_HOLDBRANCH;
76 break;
77 default:
78 return (AE_BAD_PARAMETER);
79 }
80
81 if (infop->awi_parent == NULL) {
82 return (AE_BAD_PARAMETER);
83 }
84
85 /*
86 * Inherit our parents value.
87 */
88 if (infop->awi_parent->awi_scratchpad[AWI_SCRATCH_USBPORT] != 0) {
89 infop->awi_scratchpad[AWI_SCRATCH_USBPORT] =
90 infop->awi_parent->awi_scratchpad[AWI_SCRATCH_USBPORT];
91 } else {
92 infop->awi_scratchpad[AWI_SCRATCH_USBPORT] = infop->awi_level;
93 }
94
95 ret = acpidev_process_object(infop, flags);
96 if (ACPI_FAILURE(ret) && ret != AE_NOT_EXIST &&
97 ret != AE_ALREADY_EXISTS) {
98 cmn_err(CE_WARN, "!failed to process USB object %s: %d",
99 infop->awi_name, ret);
100 } else {
101 ret = AE_OK;
102 }
103
104 return (ret);
105 }
106
107 static acpidev_filter_result_t
acpidev_usbport_filter_cb(acpidev_walk_info_t * infop,ACPI_HANDLE hdl,acpidev_filter_rule_t * afrp,char * devname,int len)108 acpidev_usbport_filter_cb(acpidev_walk_info_t *infop, ACPI_HANDLE hdl,
109 acpidev_filter_rule_t *afrp, char *devname, int len)
110 {
111 ACPI_BUFFER buf;
112
113 if (infop->awi_info->Type != ACPI_TYPE_DEVICE) {
114 return (ACPIDEV_FILTER_SKIP);
115 }
116
117 /*
118 * Make sure we can get the _ADR method for this as a reasonable case of
119 * determining whether or not this is something that we care about.
120 */
121 buf.Length = ACPI_ALLOCATE_BUFFER;
122 if (ACPI_FAILURE(AcpiEvaluateObject(hdl, "_ADR", NULL, &buf))) {
123 return (ACPIDEV_FILTER_SKIP);
124 }
125 AcpiOsFree(buf.Pointer);
126
127 if (infop->awi_level == infop->awi_scratchpad[AWI_SCRATCH_USBPORT]) {
128 (void) snprintf(devname, len, "usbroothub");
129 } else {
130 (void) snprintf(devname, len, "port");
131 }
132
133 return (ACPIDEV_FILTER_DEFAULT);
134 }
135
136 static acpidev_filter_result_t
acpidev_usbport_filter(acpidev_walk_info_t * infop,char * devname,int maxlen)137 acpidev_usbport_filter(acpidev_walk_info_t *infop, char *devname, int maxlen)
138 {
139 acpidev_filter_result_t res;
140
141 ASSERT(infop != NULL);
142 if (infop->awi_op_type == ACPIDEV_OP_BOOT_PROBE ||
143 infop->awi_op_type == ACPIDEV_OP_BOOT_REPROBE ||
144 infop->awi_op_type == ACPIDEV_OP_HOTPLUG_PROBE) {
145 res = acpidev_filter_device(infop, infop->awi_hdl,
146 ACPIDEV_ARRAY_PARAM(acpidev_usbport_filters),
147 devname, maxlen);
148 } else {
149 ACPIDEV_DEBUG(CE_WARN, "!acpidev: unknown operation type %u "
150 "in acpidev_device_filter().", infop->awi_op_type);
151 res = ACPIDEV_FILTER_FAILED;
152 }
153
154 return (res);
155 }
156
157 static ACPI_STATUS
acpidev_usbport_init(acpidev_walk_info_t * infop)158 acpidev_usbport_init(acpidev_walk_info_t *infop)
159 {
160 char *name;
161 ACPI_BUFFER buf;
162 char acpi_strbuf[128];
163
164 char *compatible[] = {
165 ACPIDEV_TYPE_USBPORT,
166 ACPIDEV_TYPE_VIRTNEX
167 };
168
169 if (ACPI_FAILURE(acpidev_set_compatible(infop,
170 ACPIDEV_ARRAY_PARAM(compatible)))) {
171 return (AE_ERROR);
172 }
173
174 /*
175 * Set the port's unit address to the last component of the ACPI path
176 * for it. This needs to be unique for a given set of parents and
177 * children. Because the hubs all usually have names that aren't unique
178 * we end up using the name of the parent for the top level device.
179 *
180 * For children, just use their acpi port address.
181 */
182 if (infop->awi_parent->awi_scratchpad[AWI_SCRATCH_USBPORT] == 0) {
183 name = strrchr(infop->awi_parent->awi_name, '.');
184 if (name != NULL)
185 name = name + 1;
186
187 /*
188 * Also, add the parents name as a property so when user land is
189 * trying to marry up USB devices with the root controller, it
190 * can.
191 */
192 if (ndi_prop_update_string(DDI_DEV_T_NONE, infop->awi_dip,
193 "acpi-controller-name", infop->awi_parent->awi_name) !=
194 DDI_PROP_SUCCESS) {
195 return (AE_ERROR);
196 }
197 } else {
198 ACPI_OBJECT *obj;
199 buf.Length = ACPI_ALLOCATE_BUFFER;
200 if (ACPI_FAILURE(AcpiEvaluateObject(infop->awi_hdl, "_ADR",
201 NULL, &buf))) {
202 return (AE_ERROR);
203 }
204
205 obj = (ACPI_OBJECT *)buf.Pointer;
206 if (obj->Type != ACPI_TYPE_INTEGER) {
207 AcpiOsFree(buf.Pointer);
208 return (AE_ERROR);
209 }
210 if (ndi_prop_update_int64(DDI_DEV_T_NONE, infop->awi_dip,
211 "acpi-address", (int64_t)obj->Integer.Value) !=
212 DDI_PROP_SUCCESS) {
213 AcpiOsFree(buf.Pointer);
214 return (AE_ERROR);
215 }
216 (void) snprintf(acpi_strbuf, sizeof (acpi_strbuf), "%lu",
217 obj->Integer.Value);
218 name = acpi_strbuf;
219 AcpiOsFree(buf.Pointer);
220 }
221
222
223 if (ACPI_FAILURE(acpidev_set_unitaddr(infop, NULL, 0,
224 name))) {
225 return (AE_ERROR);
226 }
227
228 buf.Length = ACPI_ALLOCATE_BUFFER;
229 if (ACPI_SUCCESS(AcpiEvaluateObject(infop->awi_hdl, "_PLD", NULL,
230 &buf))) {
231 ACPI_OBJECT *obj = (ACPI_OBJECT *)buf.Pointer;
232
233 if (obj->Type == ACPI_TYPE_PACKAGE && obj->Package.Count >= 1 &&
234 obj->Package.Elements[0].Type == ACPI_TYPE_BUFFER &&
235 obj->Package.Elements[0].Buffer.Length >=
236 ACPI_PLD_REV1_BUFFER_SIZE) {
237 (void) ndi_prop_update_byte_array(DDI_DEV_T_NONE,
238 infop->awi_dip, "acpi-physical-location",
239 obj->Package.Elements[0].Buffer.Pointer,
240 obj->Package.Elements[0].Buffer.Length);
241 }
242 AcpiOsFree(buf.Pointer);
243 }
244
245 buf.Length = ACPI_ALLOCATE_BUFFER;
246 if (ACPI_SUCCESS(AcpiEvaluateObject(infop->awi_hdl, "_UPC", NULL,
247 &buf))) {
248 ACPI_OBJECT *obj = (ACPI_OBJECT *)buf.Pointer;
249
250 if (obj->Type == ACPI_TYPE_PACKAGE && obj->Package.Count >= 4 &&
251 obj->Package.Elements[0].Type == ACPI_TYPE_INTEGER &&
252 obj->Package.Elements[1].Type == ACPI_TYPE_INTEGER) {
253 if (obj->Package.Elements[0].Integer.Value != 0) {
254 (void) ndi_prop_create_boolean(DDI_DEV_T_NONE,
255 infop->awi_dip, "usb-port-connectable");
256 }
257
258 (void) ndi_prop_update_int(DDI_DEV_T_NONE,
259 infop->awi_dip, "usb-port-type",
260 (int)obj->Package.Elements[1].Integer.Value);
261 }
262 AcpiOsFree(buf.Pointer);
263 }
264
265 return (AE_OK);
266 }
267
268 acpidev_class_t acpidev_class_usbport = {
269 0, /* adc_refcnt */
270 ACPIDEV_CLASS_REV1, /* adc_version */
271 ACPIDEV_CLASS_ID_USB, /* adc_class_id */
272 "ACPI USBPORT", /* adc_class_name */
273 ACPIDEV_TYPE_USBPORT, /* adc_dev_type */
274 NULL, /* adc_private */
275 NULL, /* adc_pre_probe */
276 NULL, /* adc_post_probe */
277 acpidev_usbport_probe, /* adc_probe */
278 acpidev_usbport_filter, /* adc_filter */
279 acpidev_usbport_init, /* adc_init */
280 NULL, /* adc_fini */
281 };
282