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