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 26 /* 27 * Sun NDI hotplug interfaces 28 */ 29 30 #include <sys/note.h> 31 #include <sys/sysmacros.h> 32 #include <sys/types.h> 33 #include <sys/param.h> 34 #include <sys/systm.h> 35 #include <sys/kmem.h> 36 #include <sys/cmn_err.h> 37 #include <sys/debug.h> 38 #include <sys/avintr.h> 39 #include <sys/autoconf.h> 40 #include <sys/sunndi.h> 41 #include <sys/ndi_impldefs.h> 42 #include <sys/ddi.h> 43 #include <sys/disp.h> 44 #include <sys/stat.h> 45 #include <sys/callb.h> 46 #include <sys/sysevent.h> 47 #include <sys/sysevent/eventdefs.h> 48 #include <sys/sysevent/dr.h> 49 #include <sys/taskq.h> 50 51 /* Local functions prototype */ 52 static void ddihp_cn_run_event(void *arg); 53 static int ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp, 54 ddi_hp_cn_state_t target_state); 55 56 /* 57 * Global functions (called by hotplug controller or nexus drivers) 58 */ 59 60 /* 61 * Register the Hotplug Connection (CN) 62 */ 63 int 64 ndi_hp_register(dev_info_t *dip, ddi_hp_cn_info_t *info_p) 65 { 66 ddi_hp_cn_handle_t *hdlp; 67 int count; 68 69 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_register: dip %p, info_p %p\n", 70 (void *)dip, (void *)info_p)); 71 72 ASSERT(!servicing_interrupt()); 73 if (servicing_interrupt()) 74 return (NDI_FAILURE); 75 76 /* Validate the arguments */ 77 if ((dip == NULL) || (info_p == NULL)) 78 return (NDI_EINVAL); 79 80 if (!NEXUS_HAS_HP_OP(dip)) { 81 return (NDI_ENOTSUP); 82 } 83 /* Lock before access */ 84 ndi_devi_enter(dip, &count); 85 86 hdlp = ddihp_cn_name_to_handle(dip, info_p->cn_name); 87 if (hdlp) { 88 /* This cn_name is already registered. */ 89 ndi_devi_exit(dip, count); 90 91 return (NDI_SUCCESS); 92 } 93 /* 94 * Create and initialize hotplug Connection handle 95 */ 96 hdlp = (ddi_hp_cn_handle_t *)kmem_zalloc( 97 (sizeof (ddi_hp_cn_handle_t)), KM_SLEEP); 98 99 /* Copy the Connection information */ 100 hdlp->cn_dip = dip; 101 bcopy(info_p, &(hdlp->cn_info), sizeof (*info_p)); 102 103 /* Copy cn_name */ 104 hdlp->cn_info.cn_name = ddi_strdup(info_p->cn_name, KM_SLEEP); 105 106 if (ddihp_cn_getstate(hdlp) != DDI_SUCCESS) { 107 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_register: dip %p, hdlp %p" 108 "ddi_cn_getstate failed\n", (void *)dip, (void *)hdlp)); 109 110 goto fail; 111 } 112 113 /* 114 * Append the handle to the list 115 */ 116 DDIHP_LIST_APPEND(ddi_hp_cn_handle_t, (DEVI(dip)->devi_hp_hdlp), 117 hdlp); 118 119 ndi_devi_exit(dip, count); 120 121 return (NDI_SUCCESS); 122 123 fail: 124 kmem_free(hdlp->cn_info.cn_name, strlen(hdlp->cn_info.cn_name) + 1); 125 kmem_free(hdlp, sizeof (ddi_hp_cn_handle_t)); 126 ndi_devi_exit(dip, count); 127 128 return (NDI_FAILURE); 129 } 130 131 /* 132 * Unregister a Hotplug Connection (CN) 133 */ 134 int 135 ndi_hp_unregister(dev_info_t *dip, char *cn_name) 136 { 137 ddi_hp_cn_handle_t *hdlp; 138 int count; 139 int ret; 140 141 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_unregister: dip %p, cn name %s\n", 142 (void *)dip, cn_name)); 143 144 ASSERT(!servicing_interrupt()); 145 if (servicing_interrupt()) 146 return (NDI_FAILURE); 147 148 /* Validate the arguments */ 149 if ((dip == NULL) || (cn_name == NULL)) 150 return (NDI_EINVAL); 151 152 ndi_devi_enter(dip, &count); 153 154 hdlp = ddihp_cn_name_to_handle(dip, cn_name); 155 if (hdlp == NULL) { 156 ndi_devi_exit(dip, count); 157 return (NDI_EINVAL); 158 } 159 160 switch (ddihp_cn_unregister(hdlp)) { 161 case DDI_SUCCESS: 162 ret = NDI_SUCCESS; 163 break; 164 case DDI_EINVAL: 165 ret = NDI_EINVAL; 166 break; 167 case DDI_EBUSY: 168 ret = NDI_BUSY; 169 break; 170 default: 171 ret = NDI_FAILURE; 172 break; 173 } 174 175 ndi_devi_exit(dip, count); 176 177 return (ret); 178 } 179 180 /* 181 * Notify the Hotplug Connection (CN) to change state. 182 * Flag: 183 * DDI_HP_REQ_SYNC Return after the change is finished. 184 * DDI_HP_REQ_ASYNC Return after the request is dispatched. 185 */ 186 int 187 ndi_hp_state_change_req(dev_info_t *dip, char *cn_name, 188 ddi_hp_cn_state_t state, uint_t flag) 189 { 190 ddi_hp_cn_async_event_entry_t *eventp; 191 192 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: dip %p " 193 "cn_name: %s, state %x, flag %x\n", 194 (void *)dip, cn_name, state, flag)); 195 196 /* Validate the arguments */ 197 if (dip == NULL || cn_name == NULL) 198 return (NDI_EINVAL); 199 200 if (!NEXUS_HAS_HP_OP(dip)) { 201 return (NDI_ENOTSUP); 202 } 203 /* 204 * If the request is to handle the event synchronously, then call 205 * the event handler without queuing the event. 206 */ 207 if (flag & DDI_HP_REQ_SYNC) { 208 ddi_hp_cn_handle_t *hdlp; 209 int count; 210 int ret; 211 212 ASSERT(!servicing_interrupt()); 213 if (servicing_interrupt()) 214 return (NDI_FAILURE); 215 216 ndi_devi_enter(dip, &count); 217 218 hdlp = ddihp_cn_name_to_handle(dip, cn_name); 219 if (hdlp == NULL) { 220 ndi_devi_exit(dip, count); 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, count); 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)) { 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 int count; 276 ddi_hp_cn_handle_t *head, *curr, *prev; 277 278 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p arg %p\n", 279 (void *)dip, arg)); 280 281 ASSERT(!servicing_interrupt()); 282 if (servicing_interrupt()) 283 return; 284 285 /* Validate the arguments */ 286 if (dip == NULL) 287 return; 288 289 ndi_devi_enter(dip, &count); 290 291 head = DEVI(dip)->devi_hp_hdlp; 292 curr = head; 293 prev = NULL; 294 while (curr != NULL) { 295 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p " 296 "current cn_name: %s\n", 297 (void *)dip, curr->cn_info.cn_name)); 298 switch ((*f)(&(curr->cn_info), arg)) { 299 case DDI_WALK_TERMINATE: 300 ndi_devi_exit(dip, count); 301 302 return; 303 case DDI_WALK_CONTINUE: 304 default: 305 if (DEVI(dip)->devi_hp_hdlp != head) { 306 /* 307 * The current node is head and it is removed 308 * by last call to (*f)() 309 */ 310 head = DEVI(dip)->devi_hp_hdlp; 311 curr = head; 312 prev = NULL; 313 } else if (prev && prev->next != curr) { 314 /* 315 * The current node is a middle node or tail 316 * node and it is removed by last call to 317 * (*f)() 318 */ 319 curr = prev->next; 320 } else { 321 /* no removal accurred on curr node */ 322 prev = curr; 323 curr = curr->next; 324 } 325 } 326 } 327 ndi_devi_exit(dip, count); 328 } 329 330 /* 331 * Local functions (called within this file) 332 */ 333 334 /* 335 * Wrapper function for ddihp_cn_req_handler() called from taskq 336 */ 337 static void 338 ddihp_cn_run_event(void *arg) 339 { 340 ddi_hp_cn_async_event_entry_t *eventp = 341 (ddi_hp_cn_async_event_entry_t *)arg; 342 dev_info_t *dip = eventp->dip; 343 ddi_hp_cn_handle_t *hdlp; 344 int count; 345 346 /* Lock before access */ 347 ndi_devi_enter(dip, &count); 348 349 hdlp = ddihp_cn_name_to_handle(dip, eventp->cn_name); 350 if (hdlp) { 351 (void) ddihp_cn_req_handler(hdlp, eventp->target_state); 352 } else { 353 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_run_event: no handle for " 354 "cn_name: %s dip %p. Request for target_state %x is" 355 " dropped. \n", 356 eventp->cn_name, (void *)dip, eventp->target_state)); 357 } 358 359 ndi_devi_exit(dip, count); 360 361 /* Release the devi's ref that is held from interrupt context. */ 362 ndi_rele_devi((dev_info_t *)DEVI(dip)); 363 kmem_free(eventp->cn_name, strlen(eventp->cn_name) + 1); 364 kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t)); 365 } 366 367 /* 368 * Handle state change request of a Hotplug Connection (CN) 369 */ 370 static int 371 ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp, 372 ddi_hp_cn_state_t target_state) 373 { 374 dev_info_t *dip = hdlp->cn_dip; 375 int ret = DDI_SUCCESS; 376 377 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler:" 378 " hdlp %p, target_state %x\n", 379 (void *)hdlp, target_state)); 380 381 ASSERT(DEVI_BUSY_OWNED(dip)); 382 383 if (ddihp_cn_getstate(hdlp) != DDI_SUCCESS) { 384 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler: dip %p, " 385 "hdlp %p ddi_cn_getstate failed\n", (void *)dip, 386 (void *)hdlp)); 387 388 return (NDI_UNCLAIMED); 389 } 390 if (hdlp->cn_info.cn_state != target_state) { 391 ddi_hp_cn_state_t result_state = 0; 392 393 DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_CHANGE_STATE, 394 (void *)&target_state, (void *)&result_state, ret); 395 396 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler: dip %p, " 397 "hdlp %p changed state to %x, ret=%x\n", 398 (void *)dip, (void *)hdlp, result_state, ret)); 399 } 400 401 if (ret == DDI_SUCCESS) 402 return (NDI_CLAIMED); 403 else 404 return (NDI_UNCLAIMED); 405 } 406