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 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 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 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 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