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 */ 27 28 /* 29 * Sun NDI hotplug interfaces 30 */ 31 32 #include <sys/note.h> 33 #include <sys/sysmacros.h> 34 #include <sys/types.h> 35 #include <sys/param.h> 36 #include <sys/systm.h> 37 #include <sys/kmem.h> 38 #include <sys/cmn_err.h> 39 #include <sys/debug.h> 40 #include <sys/avintr.h> 41 #include <sys/autoconf.h> 42 #include <sys/sunndi.h> 43 #include <sys/ndi_impldefs.h> 44 #include <sys/ddi.h> 45 #include <sys/disp.h> 46 #include <sys/stat.h> 47 #include <sys/callb.h> 48 #include <sys/sysevent.h> 49 #include <sys/sysevent/eventdefs.h> 50 #include <sys/sysevent/dr.h> 51 #include <sys/taskq.h> 52 53 /* Local functions prototype */ 54 static void ddihp_cn_run_event(void *arg); 55 static int ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp, 56 ddi_hp_cn_state_t target_state); 57 58 /* 59 * Global functions (called by hotplug controller or nexus drivers) 60 */ 61 62 /* 63 * Register the Hotplug Connection (CN) 64 */ 65 int 66 ndi_hp_register(dev_info_t *dip, ddi_hp_cn_info_t *info_p) 67 { 68 ddi_hp_cn_handle_t *hdlp; 69 int count; 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, &count); 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, count); 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, count); 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, count); 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 count; 141 int ret; 142 143 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_unregister: dip %p, cn name %s\n", 144 (void *)dip, cn_name)); 145 146 ASSERT(!servicing_interrupt()); 147 if (servicing_interrupt()) 148 return (NDI_FAILURE); 149 150 /* Validate the arguments */ 151 if ((dip == NULL) || (cn_name == NULL)) 152 return (NDI_EINVAL); 153 154 ndi_devi_enter(dip, &count); 155 156 hdlp = ddihp_cn_name_to_handle(dip, cn_name); 157 if (hdlp == NULL) { 158 ndi_devi_exit(dip, count); 159 return (NDI_EINVAL); 160 } 161 162 switch (ddihp_cn_unregister(hdlp)) { 163 case DDI_SUCCESS: 164 ret = NDI_SUCCESS; 165 break; 166 case DDI_EINVAL: 167 ret = NDI_EINVAL; 168 break; 169 case DDI_EBUSY: 170 ret = NDI_BUSY; 171 break; 172 default: 173 ret = NDI_FAILURE; 174 break; 175 } 176 177 ndi_devi_exit(dip, count); 178 179 return (ret); 180 } 181 182 /* 183 * Notify the Hotplug Connection (CN) to change state. 184 * Flag: 185 * DDI_HP_REQ_SYNC Return after the change is finished. 186 * DDI_HP_REQ_ASYNC Return after the request is dispatched. 187 */ 188 int 189 ndi_hp_state_change_req(dev_info_t *dip, char *cn_name, 190 ddi_hp_cn_state_t state, uint_t flag) 191 { 192 ddi_hp_cn_async_event_entry_t *eventp; 193 194 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: dip %p " 195 "cn_name: %s, state %x, flag %x\n", 196 (void *)dip, cn_name, state, flag)); 197 198 /* Validate the arguments */ 199 if (dip == NULL || cn_name == NULL) 200 return (NDI_EINVAL); 201 202 if (!NEXUS_HAS_HP_OP(dip)) { 203 return (NDI_ENOTSUP); 204 } 205 /* 206 * If the request is to handle the event synchronously, then call 207 * the event handler without queuing the event. 208 */ 209 if (flag & DDI_HP_REQ_SYNC) { 210 ddi_hp_cn_handle_t *hdlp; 211 int count; 212 int ret; 213 214 ASSERT(!servicing_interrupt()); 215 if (servicing_interrupt()) 216 return (NDI_FAILURE); 217 218 ndi_devi_enter(dip, &count); 219 220 hdlp = ddihp_cn_name_to_handle(dip, cn_name); 221 if (hdlp == NULL) { 222 ndi_devi_exit(dip, count); 223 224 return (NDI_EINVAL); 225 } 226 227 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: hdlp %p " 228 "calling ddihp_cn_req_handler() directly to handle " 229 "target_state %x\n", (void *)hdlp, state)); 230 231 ret = ddihp_cn_req_handler(hdlp, state); 232 233 ndi_devi_exit(dip, count); 234 235 return (ret); 236 } 237 238 eventp = kmem_zalloc(sizeof (ddi_hp_cn_async_event_entry_t), 239 KM_NOSLEEP); 240 if (eventp == NULL) 241 return (NDI_NOMEM); 242 243 eventp->cn_name = ddi_strdup(cn_name, KM_NOSLEEP); 244 if (eventp->cn_name == NULL) { 245 kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t)); 246 return (NDI_NOMEM); 247 } 248 eventp->dip = dip; 249 eventp->target_state = state; 250 251 /* 252 * Hold the parent's ref so that it won't disappear when the taskq is 253 * scheduled to run. 254 */ 255 ndi_hold_devi(dip); 256 257 if (taskq_dispatch(system_taskq, ddihp_cn_run_event, eventp, 258 TQ_NOSLEEP) == TASKQID_INVALID) { 259 ndi_rele_devi(dip); 260 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: " 261 "taskq_dispatch failed! dip %p " 262 "target_state %x\n", (void *)dip, state)); 263 return (NDI_NOMEM); 264 } 265 266 return (NDI_CLAIMED); 267 } 268 269 /* 270 * Walk the link of Hotplug Connection handles of a dip: 271 * DEVI(dip)->devi_hp_hdlp->[link of connections] 272 */ 273 void 274 ndi_hp_walk_cn(dev_info_t *dip, int (*f)(ddi_hp_cn_info_t *, 275 void *), void *arg) 276 { 277 int count; 278 ddi_hp_cn_handle_t *head, *curr, *prev; 279 280 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p arg %p\n", 281 (void *)dip, arg)); 282 283 ASSERT(!servicing_interrupt()); 284 if (servicing_interrupt()) 285 return; 286 287 /* Validate the arguments */ 288 if (dip == NULL) 289 return; 290 291 ndi_devi_enter(dip, &count); 292 293 head = DEVI(dip)->devi_hp_hdlp; 294 curr = head; 295 prev = NULL; 296 while (curr != NULL) { 297 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p " 298 "current cn_name: %s\n", 299 (void *)dip, curr->cn_info.cn_name)); 300 switch ((*f)(&(curr->cn_info), arg)) { 301 case DDI_WALK_TERMINATE: 302 ndi_devi_exit(dip, count); 303 304 return; 305 case DDI_WALK_CONTINUE: 306 default: 307 if (DEVI(dip)->devi_hp_hdlp != head) { 308 /* 309 * The current node is head and it is removed 310 * by last call to (*f)() 311 */ 312 head = DEVI(dip)->devi_hp_hdlp; 313 curr = head; 314 prev = NULL; 315 } else if (prev && prev->next != curr) { 316 /* 317 * The current node is a middle node or tail 318 * node and it is removed by last call to 319 * (*f)() 320 */ 321 curr = prev->next; 322 } else { 323 /* no removal accurred on curr node */ 324 prev = curr; 325 curr = curr->next; 326 } 327 } 328 } 329 ndi_devi_exit(dip, count); 330 } 331 332 /* 333 * Local functions (called within this file) 334 */ 335 336 /* 337 * Wrapper function for ddihp_cn_req_handler() called from taskq 338 */ 339 static void 340 ddihp_cn_run_event(void *arg) 341 { 342 ddi_hp_cn_async_event_entry_t *eventp = 343 (ddi_hp_cn_async_event_entry_t *)arg; 344 dev_info_t *dip = eventp->dip; 345 ddi_hp_cn_handle_t *hdlp; 346 int count; 347 348 /* Lock before access */ 349 ndi_devi_enter(dip, &count); 350 351 hdlp = ddihp_cn_name_to_handle(dip, eventp->cn_name); 352 if (hdlp) { 353 (void) ddihp_cn_req_handler(hdlp, eventp->target_state); 354 } else { 355 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_run_event: no handle for " 356 "cn_name: %s dip %p. Request for target_state %x is" 357 " dropped. \n", 358 eventp->cn_name, (void *)dip, eventp->target_state)); 359 } 360 361 ndi_devi_exit(dip, count); 362 363 /* Release the devi's ref that is held from interrupt context. */ 364 ndi_rele_devi((dev_info_t *)DEVI(dip)); 365 kmem_free(eventp->cn_name, strlen(eventp->cn_name) + 1); 366 kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t)); 367 } 368 369 /* 370 * Handle state change request of a Hotplug Connection (CN) 371 */ 372 static int 373 ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp, 374 ddi_hp_cn_state_t target_state) 375 { 376 dev_info_t *dip = hdlp->cn_dip; 377 int ret = DDI_SUCCESS; 378 379 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler:" 380 " hdlp %p, target_state %x\n", 381 (void *)hdlp, target_state)); 382 383 ASSERT(DEVI_BUSY_OWNED(dip)); 384 385 /* 386 * We do not want to fetch the state first, as calling ddihp_cn_getstate 387 * will update the cn_state member of the connection handle. The 388 * connector's hotplug operations rely on this value to know how 389 * target_state compares to the last known state of the device and make 390 * decisions about whether to clean up, post sysevents about the state 391 * change, and so on. 392 * 393 * Instead, just carry out the request to change the state. The 394 * connector's hotplug operations will update the state in the 395 * connection handle after they complete their necessary state change 396 * actions. 397 */ 398 if (hdlp->cn_info.cn_state != target_state) { 399 ddi_hp_cn_state_t result_state = 0; 400 401 DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_CHANGE_STATE, 402 (void *)&target_state, (void *)&result_state, ret); 403 404 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler: dip %p, " 405 "hdlp %p changed state to %x, ret=%x\n", 406 (void *)dip, (void *)hdlp, result_state, ret)); 407 } 408 409 if (ret == DDI_SUCCESS) 410 return (NDI_CLAIMED); 411 else 412 return (NDI_UNCLAIMED); 413 } 414