1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * 25 * Copyright 2019 Joyent, Inc. 26 * Copyright 2023 Oxide Computer Company 27 */ 28 29 /* 30 * Sun NDI hotplug interfaces 31 */ 32 33 #include <sys/note.h> 34 #include <sys/sysmacros.h> 35 #include <sys/types.h> 36 #include <sys/param.h> 37 #include <sys/systm.h> 38 #include <sys/kmem.h> 39 #include <sys/cmn_err.h> 40 #include <sys/debug.h> 41 #include <sys/avintr.h> 42 #include <sys/autoconf.h> 43 #include <sys/sunndi.h> 44 #include <sys/ndi_impldefs.h> 45 #include <sys/ddi.h> 46 #include <sys/disp.h> 47 #include <sys/stat.h> 48 #include <sys/callb.h> 49 #include <sys/sysevent.h> 50 #include <sys/sysevent/eventdefs.h> 51 #include <sys/sysevent/dr.h> 52 #include <sys/taskq.h> 53 54 /* Local functions prototype */ 55 static void ddihp_cn_run_event(void *arg); 56 static int ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp, 57 ddi_hp_cn_state_t target_state); 58 59 /* 60 * Global functions (called by hotplug controller or nexus drivers) 61 */ 62 63 /* 64 * Register the Hotplug Connection (CN) 65 */ 66 int 67 ndi_hp_register(dev_info_t *dip, ddi_hp_cn_info_t *info_p) 68 { 69 ddi_hp_cn_handle_t *hdlp; 70 71 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_register: dip %p, info_p %p\n", 72 (void *)dip, (void *)info_p)); 73 74 ASSERT(!servicing_interrupt()); 75 if (servicing_interrupt()) 76 return (NDI_FAILURE); 77 78 /* Validate the arguments */ 79 if ((dip == NULL) || (info_p == NULL)) 80 return (NDI_EINVAL); 81 82 if (!NEXUS_HAS_HP_OP(dip)) { 83 return (NDI_ENOTSUP); 84 } 85 /* Lock before access */ 86 ndi_devi_enter(dip); 87 88 hdlp = ddihp_cn_name_to_handle(dip, info_p->cn_name); 89 if (hdlp) { 90 /* This cn_name is already registered. */ 91 ndi_devi_exit(dip); 92 93 return (NDI_SUCCESS); 94 } 95 /* 96 * Create and initialize hotplug Connection handle 97 */ 98 hdlp = (ddi_hp_cn_handle_t *)kmem_zalloc( 99 (sizeof (ddi_hp_cn_handle_t)), KM_SLEEP); 100 101 /* Copy the Connection information */ 102 hdlp->cn_dip = dip; 103 bcopy(info_p, &(hdlp->cn_info), sizeof (*info_p)); 104 105 /* Copy cn_name */ 106 hdlp->cn_info.cn_name = ddi_strdup(info_p->cn_name, KM_SLEEP); 107 108 if (ddihp_cn_getstate(hdlp) != DDI_SUCCESS) { 109 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_register: dip %p, hdlp %p" 110 "ddi_cn_getstate failed\n", (void *)dip, (void *)hdlp)); 111 112 goto fail; 113 } 114 115 /* 116 * Append the handle to the list 117 */ 118 DDIHP_LIST_APPEND(ddi_hp_cn_handle_t, (DEVI(dip)->devi_hp_hdlp), 119 hdlp); 120 121 ndi_devi_exit(dip); 122 123 return (NDI_SUCCESS); 124 125 fail: 126 kmem_free(hdlp->cn_info.cn_name, strlen(hdlp->cn_info.cn_name) + 1); 127 kmem_free(hdlp, sizeof (ddi_hp_cn_handle_t)); 128 ndi_devi_exit(dip); 129 130 return (NDI_FAILURE); 131 } 132 133 /* 134 * Unregister a Hotplug Connection (CN) 135 */ 136 int 137 ndi_hp_unregister(dev_info_t *dip, char *cn_name) 138 { 139 ddi_hp_cn_handle_t *hdlp; 140 int ret; 141 142 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_unregister: dip %p, cn name %s\n", 143 (void *)dip, cn_name)); 144 145 ASSERT(!servicing_interrupt()); 146 if (servicing_interrupt()) 147 return (NDI_FAILURE); 148 149 /* Validate the arguments */ 150 if ((dip == NULL) || (cn_name == NULL)) 151 return (NDI_EINVAL); 152 153 ndi_devi_enter(dip); 154 155 hdlp = ddihp_cn_name_to_handle(dip, cn_name); 156 if (hdlp == NULL) { 157 ndi_devi_exit(dip); 158 return (NDI_EINVAL); 159 } 160 161 switch (ddihp_cn_unregister(hdlp)) { 162 case DDI_SUCCESS: 163 ret = NDI_SUCCESS; 164 break; 165 case DDI_EINVAL: 166 ret = NDI_EINVAL; 167 break; 168 case DDI_EBUSY: 169 ret = NDI_BUSY; 170 break; 171 default: 172 ret = NDI_FAILURE; 173 break; 174 } 175 176 ndi_devi_exit(dip); 177 178 return (ret); 179 } 180 181 /* 182 * Notify the Hotplug Connection (CN) to change state. 183 * Flag: 184 * DDI_HP_REQ_SYNC Return after the change is finished. 185 * DDI_HP_REQ_ASYNC Return after the request is dispatched. 186 */ 187 int 188 ndi_hp_state_change_req(dev_info_t *dip, char *cn_name, 189 ddi_hp_cn_state_t state, uint_t flag) 190 { 191 ddi_hp_cn_async_event_entry_t *eventp; 192 193 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: dip %p " 194 "cn_name: %s, state %x, flag %x\n", 195 (void *)dip, cn_name, state, flag)); 196 197 /* Validate the arguments */ 198 if (dip == NULL || cn_name == NULL) 199 return (NDI_EINVAL); 200 201 if (!NEXUS_HAS_HP_OP(dip)) { 202 return (NDI_ENOTSUP); 203 } 204 /* 205 * If the request is to handle the event synchronously, then call 206 * the event handler without queuing the event. 207 */ 208 if (flag & DDI_HP_REQ_SYNC) { 209 ddi_hp_cn_handle_t *hdlp; 210 int ret; 211 212 ASSERT(!servicing_interrupt()); 213 if (servicing_interrupt()) 214 return (NDI_FAILURE); 215 216 ndi_devi_enter(dip); 217 218 hdlp = ddihp_cn_name_to_handle(dip, cn_name); 219 if (hdlp == NULL) { 220 ndi_devi_exit(dip); 221 222 return (NDI_EINVAL); 223 } 224 225 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: hdlp %p " 226 "calling ddihp_cn_req_handler() directly to handle " 227 "target_state %x\n", (void *)hdlp, state)); 228 229 ret = ddihp_cn_req_handler(hdlp, state); 230 231 ndi_devi_exit(dip); 232 233 return (ret); 234 } 235 236 eventp = kmem_zalloc(sizeof (ddi_hp_cn_async_event_entry_t), 237 KM_NOSLEEP); 238 if (eventp == NULL) 239 return (NDI_NOMEM); 240 241 eventp->cn_name = ddi_strdup(cn_name, KM_NOSLEEP); 242 if (eventp->cn_name == NULL) { 243 kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t)); 244 return (NDI_NOMEM); 245 } 246 eventp->dip = dip; 247 eventp->target_state = state; 248 249 /* 250 * Hold the parent's ref so that it won't disappear when the taskq is 251 * scheduled to run. 252 */ 253 ndi_hold_devi(dip); 254 255 if (taskq_dispatch(system_taskq, ddihp_cn_run_event, eventp, 256 TQ_NOSLEEP) == TASKQID_INVALID) { 257 ndi_rele_devi(dip); 258 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: " 259 "taskq_dispatch failed! dip %p " 260 "target_state %x\n", (void *)dip, state)); 261 return (NDI_NOMEM); 262 } 263 264 return (NDI_CLAIMED); 265 } 266 267 /* 268 * Walk the link of Hotplug Connection handles of a dip: 269 * DEVI(dip)->devi_hp_hdlp->[link of connections] 270 */ 271 void 272 ndi_hp_walk_cn(dev_info_t *dip, int (*f)(ddi_hp_cn_info_t *, 273 void *), void *arg) 274 { 275 ddi_hp_cn_handle_t *head, *curr, *prev; 276 277 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p arg %p\n", 278 (void *)dip, arg)); 279 280 ASSERT(!servicing_interrupt()); 281 if (servicing_interrupt()) 282 return; 283 284 /* Validate the arguments */ 285 if (dip == NULL) 286 return; 287 288 ndi_devi_enter(dip); 289 290 head = DEVI(dip)->devi_hp_hdlp; 291 curr = head; 292 prev = NULL; 293 while (curr != NULL) { 294 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p " 295 "current cn_name: %s\n", 296 (void *)dip, curr->cn_info.cn_name)); 297 switch ((*f)(&(curr->cn_info), arg)) { 298 case DDI_WALK_TERMINATE: 299 ndi_devi_exit(dip); 300 301 return; 302 case DDI_WALK_CONTINUE: 303 default: 304 if (DEVI(dip)->devi_hp_hdlp != head) { 305 /* 306 * The current node is head and it is removed 307 * by last call to (*f)() 308 */ 309 head = DEVI(dip)->devi_hp_hdlp; 310 curr = head; 311 prev = NULL; 312 } else if (prev && prev->next != curr) { 313 /* 314 * The current node is a middle node or tail 315 * node and it is removed by last call to 316 * (*f)() 317 */ 318 curr = prev->next; 319 } else { 320 /* no removal accurred on curr node */ 321 prev = curr; 322 curr = curr->next; 323 } 324 } 325 } 326 ndi_devi_exit(dip); 327 } 328 329 /* 330 * Local functions (called within this file) 331 */ 332 333 /* 334 * Wrapper function for ddihp_cn_req_handler() called from taskq 335 */ 336 static void 337 ddihp_cn_run_event(void *arg) 338 { 339 ddi_hp_cn_async_event_entry_t *eventp = 340 (ddi_hp_cn_async_event_entry_t *)arg; 341 dev_info_t *dip = eventp->dip; 342 dev_info_t *pdip; 343 ddi_hp_cn_handle_t *hdlp; 344 345 /* 346 * See notes in ddihp_modctl(). This is another essentially identical 347 * path we get to internally rather than from userland, but the same 348 * problem applies here. 349 */ 350 pdip = ddi_get_parent(dip); 351 if (pdip != NULL) 352 ndi_devi_enter(pdip); 353 354 /* Lock before access */ 355 ndi_devi_enter(dip); 356 357 hdlp = ddihp_cn_name_to_handle(dip, eventp->cn_name); 358 if (hdlp) { 359 (void) ddihp_cn_req_handler(hdlp, eventp->target_state); 360 } else { 361 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_run_event: no handle for " 362 "cn_name: %s dip %p. Request for target_state %x is" 363 " dropped. \n", 364 eventp->cn_name, (void *)dip, eventp->target_state)); 365 } 366 367 ndi_devi_exit(dip); 368 if (pdip != NULL) 369 ndi_devi_exit(pdip); 370 371 /* Release the devi's ref that is held from interrupt context. */ 372 ndi_rele_devi((dev_info_t *)DEVI(dip)); 373 kmem_free(eventp->cn_name, strlen(eventp->cn_name) + 1); 374 kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t)); 375 } 376 377 /* 378 * Handle state change request of a Hotplug Connection (CN) 379 */ 380 static int 381 ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp, 382 ddi_hp_cn_state_t target_state) 383 { 384 dev_info_t *dip = hdlp->cn_dip; 385 int ret = DDI_SUCCESS; 386 387 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler:" 388 " hdlp %p, target_state %x\n", 389 (void *)hdlp, target_state)); 390 391 ASSERT(DEVI_BUSY_OWNED(dip)); 392 393 /* 394 * We do not want to fetch the state first, as calling ddihp_cn_getstate 395 * will update the cn_state member of the connection handle. The 396 * connector's hotplug operations rely on this value to know how 397 * target_state compares to the last known state of the device and make 398 * decisions about whether to clean up, post sysevents about the state 399 * change, and so on. 400 * 401 * Instead, just carry out the request to change the state. The 402 * connector's hotplug operations will update the state in the 403 * connection handle after they complete their necessary state change 404 * actions. 405 */ 406 if (hdlp->cn_info.cn_state != target_state) { 407 ddi_hp_cn_state_t result_state = 0; 408 409 DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_CHANGE_STATE, 410 (void *)&target_state, (void *)&result_state, ret); 411 412 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler: dip %p, " 413 "hdlp %p changed state to %x, ret=%x\n", 414 (void *)dip, (void *)hdlp, result_state, ret)); 415 } 416 417 if (ret == DDI_SUCCESS) 418 return (NDI_CLAIMED); 419 else 420 return (NDI_UNCLAIMED); 421 } 422