Mercurial > illumos > illumos-gate
view usr/src/uts/common/io/usb/hwa/hwahc/hwahc.c @ 13631:1e93e74c836a
backout 998: breaks common closed drivers
author | Richard Lowe <richlowe@richlowe.net> |
---|---|
date | Fri, 02 Mar 2012 19:01:23 -0500 |
parents | 76b4d0b12a17 |
children | 776c3d9cae69 |
line wrap: on
line source
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * The Data Transfer Interface driver for Host Wire Adapter device * * HWA device has two interfaces, one is the data transfer interface, * another is the radio control interface. This driver (hwahc) is only * for data transfer interface support, but it depends on the radio * control interface driver (hwarc) to work. That means the hwarc * driver must be loaded while the hwahc is working. This is now * ensured by holding hwarc open until hwahc detaches or powers down. * * The data transfer interface has three endpoints besides the default * control endpoint which is shared between the two interfaces. The * three endpoints are: * * - notification endpoint (intr in type, for asynchronous event * notifications and transfer status notifications) * * - data transfer OUT endpoint (bulk out type, for sending transfer * requests and transfer data from the host to the HWA device) * * - data transfer IN endpoint (bulk in type, for returning transfer * status and transfer data from the HWA device to the host) * * The HWA device is a USB 2.0 device, so it supports the standard USB * requests defined in chapter 9 of USB 2.0 specification as other USB * client devices. But its most important functionality is to work as * a wireless USB host. This means the hwahc driver needs to supply * host controller functionalities, which include children hotplug * support and data transfer support to children device endpoints. * * So hwahc driver is implemented as a nexus driver and it follows the * event mechanism in existing USBA framework to support children * hotplug events. * * The hwahc driver works as the root-hub on wireless USB bus. And it * relays data transfers to/from wireless bus to the USB bus where ehci/ * ohci/uhci works as the root-hub. This makes a bus cascading topology. * * The data transfer to/from wireless device endpoints is implemented by * remote pipe (rpipe) mechanism. The rpipe descriptor on the HWA defines * the attributes of a wireless USB transfer, such as the transfer type, * the target device address, the target endpoint address and the max * packet size. And the transfer requests through data transfer OUT * endpoint will take a certain rpipe as the transfer target, thus * fulfills the data transfer across buses. Refer to chapter 8 of WUSB * 1.0 specification for details of this. */ #define USBDRV_MAJOR_VER 2 #define USBDRV_MINOR_VER 0 #include <sys/usb/hwa/hwahc/hwahc.h> #include <sys/usb/hwa/hwahc/hwahc_util.h> #include <sys/usb/usba/wa.h> #include <sys/usb/usba/wusba.h> #include <sys/usb/usba/whcdi.h> #include <sys/usb/usba.h> #include <sys/usb/usba/usba_impl.h> #include <sys/usb/usba/usba_devdb.h> /* for usba_devdb_refresh */ #include <sys/usb/hubd/hubdvar.h> #include <sys/usb/hubd/hubd_impl.h> /* for hubd_ioctl_data_t */ #include <sys/strsubr.h> /* for allocb_wait */ #include <sys/strsun.h> /* for MBLKL macro */ #include <sys/fs/dv_node.h> /* for devfs_clean */ #include <sys/uwb/uwbai.h> /* for uwb ioctls */ #include <sys/random.h> void *hwahc_statep; /* number of instances */ #define HWAHC_INSTS 1 /* default value for set number DNTS slots request */ #define HWAHC_DEFAULT_DNTS_INTERVAL 2 /* ms */ #define HWAHC_DEFAULT_DNTS_SLOT_NUM 4 /* debug support */ uint_t hwahc_errmask = (uint_t)PRINT_MASK_ALL; uint_t hwahc_errlevel = USB_LOG_L4; uint_t hwahc_instance_debug = (uint_t)-1; /* bus config debug flag */ uint_t hwahc_bus_config_debug = 0; uint8_t hwahc_enable_trust_timeout = 1; /* * Use the default GTK for the whole life of HWA driver. * Not so compatible with WUSB spec. */ static uint8_t dft_gtk[16]; static uint8_t dft_gtkid[3]; extern usb_log_handle_t whcdi_log_handle; /* * Function Prototypes */ /* driver operations (dev_ops) entry points */ static int hwahc_open(dev_t *, int, int, cred_t *); static int hwahc_close(dev_t, int, int, cred_t *); static int hwahc_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); static int hwahc_info(dev_info_t *, ddi_info_cmd_t, void *, void **); static int hwahc_attach(dev_info_t *, ddi_attach_cmd_t); static int hwahc_detach(dev_info_t *, ddi_detach_cmd_t); static int hwahc_power(dev_info_t *, int, int); /* bus_ops entry points */ static int hwahc_bus_ctl(dev_info_t *, dev_info_t *, ddi_ctl_enum_t, void *, void *); static int hwahc_busop_get_eventcookie(dev_info_t *, dev_info_t *, char *, ddi_eventcookie_t *); static int hwahc_busop_add_eventcall( dev_info_t *, dev_info_t *, ddi_eventcookie_t, void (*)(dev_info_t *, ddi_eventcookie_t, void *, void *), void *, ddi_callback_id_t *); static int hwahc_busop_remove_eventcall(dev_info_t *, ddi_callback_id_t); static int hwahc_bus_config(dev_info_t *, uint_t, ddi_bus_config_op_t, void *, dev_info_t **); static int hwahc_bus_unconfig(dev_info_t *, uint_t, ddi_bus_config_op_t, void *); /* hotplug and power management supporting functions */ static int hwahc_disconnect_event_cb(dev_info_t *dip); static int hwahc_reconnect_event_cb(dev_info_t *dip); static int hwahc_pre_suspend_event_cb(dev_info_t *dip); static int hwahc_post_resume_event_cb(dev_info_t *dip); static int hwahc_cpr_suspend(dev_info_t *); static int hwahc_cpr_resume(dev_info_t *); static void hwahc_restore_device_state(dev_info_t *, hwahc_state_t *); static void hwahc_run_callbacks(hwahc_state_t *, usba_event_t); static void hwahc_post_event(hwahc_state_t *, usb_port_t, usba_event_t); static int hwahc_cleanup(dev_info_t *, hwahc_state_t *); static void hwahc_create_pm_components(dev_info_t *, hwahc_state_t *); static void hwahc_destroy_pm_components(hwahc_state_t *); static void hwahc_pm_busy_component(hwahc_state_t *); static void hwahc_pm_idle_component(hwahc_state_t *); static int hwahc_pwrlvl0(hwahc_state_t *); static int hwahc_pwrlvl1(hwahc_state_t *); static int hwahc_pwrlvl2(hwahc_state_t *); static int hwahc_pwrlvl3(hwahc_state_t *); static int hwahc_hc_channel_suspend(hwahc_state_t *); /* hardware initialization and deinitialization functions */ static int hwahc_parse_security_data(wusb_secrt_data_t *, usb_cfg_data_t *); static void hwahc_print_secrt_data(hwahc_state_t *); static int hwahc_hub_attach(hwahc_state_t *); static int hwahc_hub_detach(hwahc_state_t *); static int hwahc_hc_initial_start(hwahc_state_t *); static int hwahc_hc_final_stop(hwahc_state_t *); static int hwahc_wa_start(hwahc_state_t *); static void hwahc_wa_stop(hwahc_state_t *); static int hwahc_hc_channel_start(hwahc_state_t *); static int hwahc_hc_channel_stop(hwahc_state_t *); static void hwahc_hc_data_init(hwahc_state_t *); static void hwahc_hc_data_fini(hwahc_state_t *); /* ioctl support */ static int hwahc_cfgadm_ioctl(hwahc_state_t *, int, intptr_t, int, cred_t *, int *); static int hwahc_wusb_ioctl(hwahc_state_t *, int, intptr_t, int, cred_t *, int *); /* callbacks registered to USBA */ static void hwahc_disconnect_dev(dev_info_t *, usb_port_t); static void hwahc_reconnect_dev(dev_info_t *, usb_port_t); static int hwahc_create_child(dev_info_t *, usb_port_t); static int hwahc_destroy_child(dev_info_t *, usb_port_t); static int hwahc_cleanup_child(dev_info_t *); static int hwahc_delete_child(dev_info_t *, usb_port_t, uint_t, boolean_t); /* data transfer and notification handling */ static void hwahc_intr_cb(usb_pipe_handle_t, struct usb_intr_req *); static void hwahc_intr_exc_cb(usb_pipe_handle_t, struct usb_intr_req *); static void hwahc_handle_notif(hwahc_state_t *, mblk_t *); static void hwahc_handle_xfer_result(hwahc_state_t *, uint8_t); static void hwahc_stop_result_thread(hwahc_state_t *); static void hwahc_result_thread(void *); static void hwahc_handle_dn_notif(hwahc_state_t *, hwa_notif_dn_recvd_t *); static void hwahc_notif_thread(void *); static void hwahc_handle_dn(hwahc_state_t *, hwa_notif_dn_recvd_t *); static void hwahc_drain_notif_queue(hwahc_state_t *); static void hwahc_rpipe_xfer_cb(dev_info_t *, usba_pipe_handle_data_t *, wusb_wa_trans_wrapper_t *, usb_cr_t); static void hwahc_trust_timeout_handler(void *arg); static void hwahc_stop_trust_timer(wusb_dev_info_t *dev); static int hwahc_pipe_submit_periodic_req(wusb_wa_data_t *wa_data, usba_pipe_handle_data_t *ph); /* hwa specific requests */ static int hwahc_set_chid(hwahc_state_t *, uint8_t *); /* helper functions */ static usb_port_t hwahc_get_port_num(hwahc_state_t *, struct devctl_iocdata *); static dev_info_t *hwahc_get_child_dip(hwahc_state_t *, usb_port_t); static struct cb_ops hwahc_cb_ops = { hwahc_open, /* Open */ hwahc_close, /* Close */ nodev, /* Strategy */ nodev, /* Print */ nodev, /* Dump */ nodev, /* Read */ nodev, /* Write */ hwahc_ioctl, /* Ioctl */ nodev, /* Devmap */ nodev, /* Mmap */ nodev, /* Segmap */ nochpoll, /* Poll */ ddi_prop_op, /* cb_prop_op */ NULL, /* Streamtab */ D_MP /* Driver compatibility flag */ }; static struct bus_ops hwahc_busops = { BUSO_REV, nullbusmap, /* bus_map */ NULL, /* bus_get_intrspec */ NULL, /* bus_add_intrspec */ NULL, /* bus_remove_intrspec */ NULL, /* bus_map_fault */ ddi_dma_map, /* bus_dma_map */ ddi_dma_allochdl, ddi_dma_freehdl, ddi_dma_bindhdl, ddi_dma_unbindhdl, ddi_dma_flush, ddi_dma_win, ddi_dma_mctl, /* bus_dma_ctl */ hwahc_bus_ctl, /* bus_ctl */ ddi_bus_prop_op, /* bus_prop_op */ hwahc_busop_get_eventcookie, /* bus_get_eventcookie */ hwahc_busop_add_eventcall, /* bus_add_eventcall */ hwahc_busop_remove_eventcall, /* bus_remove_eventcall */ NULL, /* bus_post_event */ NULL, /* bus_intr_ctl */ hwahc_bus_config, /* bus_config */ hwahc_bus_unconfig, /* bus_unconfig */ NULL, /* bus_fm_init */ NULL, /* bus_fm_fini */ NULL, /* bus_fm_access_enter */ NULL, /* bus_fm_access_exit */ NULL, /* bus_power */ }; static struct dev_ops hwahc_ops = { DEVO_REV, /* Devo_rev */ 0, /* Refcnt */ hwahc_info, /* Info */ nulldev, /* Identify */ nulldev, /* Probe */ hwahc_attach, /* Attach */ hwahc_detach, /* Detach */ nodev, /* Reset */ &hwahc_cb_ops, /* Driver operations */ &hwahc_busops, /* Bus operations */ hwahc_power, /* Power */ ddi_quiesce_not_needed, /* devo_quiesce */ }; static struct modldrv hwahc_modldrv = { &mod_driverops, "WUSB hwa-hc driver", &hwahc_ops }; static struct modlinkage modlinkage = { MODREV_1, &hwahc_modldrv, NULL }; /* events from parent */ static usb_event_t hwahc_events = { hwahc_disconnect_event_cb, hwahc_reconnect_event_cb, hwahc_pre_suspend_event_cb, hwahc_post_resume_event_cb }; /* * events support for children * A map tween USBA_EVENTs and DDI_EVENTs. */ static ndi_event_definition_t hwahc_ndi_event_defs[] = { {USBA_EVENT_TAG_HOT_REMOVAL, DDI_DEVI_REMOVE_EVENT, EPL_KERNEL, NDI_EVENT_POST_TO_ALL}, {USBA_EVENT_TAG_HOT_INSERTION, DDI_DEVI_INSERT_EVENT, EPL_KERNEL, NDI_EVENT_POST_TO_ALL}, {USBA_EVENT_TAG_POST_RESUME, USBA_POST_RESUME_EVENT, EPL_KERNEL, NDI_EVENT_POST_TO_ALL}, {USBA_EVENT_TAG_PRE_SUSPEND, USBA_PRE_SUSPEND_EVENT, EPL_KERNEL, NDI_EVENT_POST_TO_ALL} }; #define HWAHC_N_NDI_EVENTS \ (sizeof (hwahc_ndi_event_defs) / sizeof (ndi_event_definition_t)) static ndi_event_set_t hwahc_ndi_events = { NDI_EVENTS_REV1, HWAHC_N_NDI_EVENTS, hwahc_ndi_event_defs}; /* transfer callbacks */ static wusb_wa_cb_t hwahc_cbs = { hwahc_pipe_submit_periodic_req, hwahc_intr_cb, hwahc_intr_exc_cb, hwahc_rpipe_xfer_cb }; /* * Module-wide initialization routine. */ int _init(void) { int rval; if ((rval = ddi_soft_state_init(&hwahc_statep, sizeof (hwahc_state_t), HWAHC_INSTS)) != 0) { return (rval); } if ((rval = mod_install(&modlinkage)) != 0) { ddi_soft_state_fini(&hwahc_statep); } return (rval); } /* * Module-wide tear-down routine. */ int _fini(void) { int rval; if ((rval = mod_remove(&modlinkage)) == 0) { /* Release per module resources */ ddi_soft_state_fini(&hwahc_statep); } return (rval); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* * hwahc_info: * Get minor number, instance number, etc. */ /*ARGSUSED*/ static int hwahc_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { hwahc_state_t *hwahcp; int error = DDI_FAILURE; int instance = HWAHC_MINOR_TO_INSTANCE(getminor((dev_t)arg)); switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: if ((hwahcp = ddi_get_soft_state(hwahc_statep, instance)) != NULL) { *result = hwahcp->hwahc_dip; if (*result != NULL) { error = DDI_SUCCESS; } } else { *result = NULL; } break; case DDI_INFO_DEVT2INSTANCE: *result = (void *)(uintptr_t)instance; error = DDI_SUCCESS; break; default: break; } return (error); } /* * hwahc_attach: * Attach or resume. * * For attach, initialize state and device, including: * state variables, locks, device node, * resource initialization, event registration, * device registration with system * power management, hotplugging * For resume, restore device and state */ static int hwahc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int instance = ddi_get_instance(dip); hwahc_state_t *hwahcp = NULL; usb_client_dev_data_t *dev_data; struct usb_cfg_data *cfg_data; usba_hcdi_register_args_t hcdi_args; int rval; char *pathname; USB_DPRINTF_L3(PRINT_MASK_ATTA, NULL, "hwahc_attach: cmd=%d", cmd); switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: (void) hwahc_cpr_resume(dip); return (DDI_SUCCESS); default: USB_DPRINTF_L2(PRINT_MASK_ATTA, NULL, "hwahc_attach: failed"); return (DDI_FAILURE); } /* * Allocate soft state information. */ rval = ddi_soft_state_zalloc(hwahc_statep, instance); if (rval != DDI_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, NULL, "hwahc_attach: cannot allocate soft state for instance %d", instance); return (USB_FAILURE); } hwahcp = ddi_get_soft_state(hwahc_statep, instance); if (hwahcp == NULL) { USB_DPRINTF_L2(PRINT_MASK_ATTA, NULL, "hwahc_attach: get soft state failed for instance %d", instance); return (USB_FAILURE); } hwahcp->hwahc_log_handle = usb_alloc_log_hdl(dip, "hwahc", &hwahc_errlevel, &hwahc_errmask, &hwahc_instance_debug, 0); /* initialize hc state */ hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_INIT_STATE; hwahcp->hwahc_dip = dip; hwahcp->hwahc_instance = instance; /* register with USBA as client driver */ if (usb_client_attach(dip, USBDRV_VERSION, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_attach: client attach failed"); goto fail; } if (usb_get_dev_data(dip, &dev_data, USB_PARSE_LVL_IF, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_attach: cannot get dev_data"); goto fail; } /* initialize mutex and cv */ mutex_init(&hwahcp->hwahc_mutex, NULL, MUTEX_DRIVER, dev_data->dev_iblock_cookie); cv_init(&hwahcp->hwahc_result_thread_cv, NULL, CV_DRIVER, NULL); hwahcp->hwahc_flags |= HWAHC_LOCK_INITED; hwahcp->hwahc_dev_data = dev_data; /* initialize data transfer function related structure */ if (wusb_wa_data_init(dip, &hwahcp->hwahc_wa_data, &hwahc_cbs, dev_data, PRINT_MASK_ATTA, hwahcp->hwahc_log_handle) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_attach: init wa data failed"); goto fail; } hwahcp->hwahc_flags |= HWAHC_WA_INITED; cfg_data = dev_data->dev_curr_cfg; /* parse the security descrs from the configuration descr cloud */ if (hwahc_parse_security_data(&hwahcp->hwahc_secrt_data, cfg_data) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_attach: parse security descrs failed"); goto fail; } hwahcp->hwahc_default_pipe = dev_data->dev_default_ph; hwahcp->hwahc_wa_data.wa_private_data = (void *)hwahcp; hwahcp->hwahc_wa_data.wa_default_pipe = hwahcp->hwahc_default_pipe; usb_free_descr_tree(dip, dev_data); hwahcp->hwahc_dev_state = USB_DEV_ONLINE; /* now create components to power manage this device */ hwahc_create_pm_components(dip, hwahcp); /* * Event definition and registration * * allocate a new NDI event handle as a nexus driver */ (void) ndi_event_alloc_hdl(dip, 0, &hwahcp->hwahc_ndi_event_hdl, NDI_SLEEP); /* * bind our NDI events with the event handle, * i.e. Define the events set we're to support as a nexus driver. * * These events will be used by bus_ops functions to register callbacks. */ if (ndi_event_bind_set(hwahcp->hwahc_ndi_event_hdl, &hwahc_ndi_events, NDI_SLEEP)) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_attach: binding event set failed"); goto fail; } /* * Register USB events to USBA(the parent) to get callbacks as a * child of (root) hub */ if (usb_register_event_cbs(dip, &hwahc_events, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_attach: register_events failed"); goto fail; } hwahcp->hwahc_flags |= HWAHC_EVENTS_REGISTERED; /* create minor nodes */ if (ddi_create_minor_node(dip, "hwahc", S_IFCHR, instance << HWAHC_MINOR_INSTANCE_SHIFT, DDI_NT_NEXUS, 0) != DDI_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_attach: cannot create minor node"); goto fail; } hwahcp->hwahc_flags |= HWAHC_MINOR_NODE_CREATED; hwahcp->hwahc_hcdi_ops = hwahc_alloc_hcdi_ops(hwahcp); /* register this hc instance with usba HCD interface */ hcdi_args.usba_hcdi_register_version = HCDI_REGISTER_VERSION; hcdi_args.usba_hcdi_register_dip = dip; hcdi_args.usba_hcdi_register_ops = hwahcp->hwahc_hcdi_ops; /* use parent dma attr here */ hcdi_args.usba_hcdi_register_dma_attr = usba_get_hc_dma_attr(dip); hcdi_args.usba_hcdi_register_iblock_cookie = NULL; if (usba_hcdi_register(&hcdi_args, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_attach: usba_hcdi_register failed"); goto fail; } hwahcp->hwahc_flags |= HWAHC_HCDI_REGISTERED; /* create hub minor node and register to usba HUBD interface */ if (hwahc_hub_attach(hwahcp) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_attach: hub attach failed"); goto fail; } hwahcp->hwahc_flags |= HWAHC_HUBREG; /* intialize WUSB host function related structure */ hwahc_hc_data_init(hwahcp); hwahcp->hwahc_flags |= HWAHC_HC_INITED; /* can be combined with wusb_wa_data_init() */ if (hwahc_wa_start(hwahcp) != USB_SUCCESS) { goto fail; } hwahcp->hwahc_flags |= HWAHC_WA_STARTED; /* report this dev */ ddi_report_dev(dip); hwahc_pm_idle_component(hwahcp); mutex_enter(&(hwahcp->hwahc_mutex)); hwahc_print_secrt_data(hwahcp); mutex_exit(&(hwahcp->hwahc_mutex)); if (uwb_dev_online(dip) != USB_SUCCESS) { goto fail; } return (DDI_SUCCESS); fail: pathname = kmem_alloc(MAXPATHLEN, KM_SLEEP); /* log this message to usba_debug_buf */ USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "cannot attach %s", ddi_pathname(dip, pathname)); kmem_free(pathname, MAXPATHLEN); if (hwahcp) { hwahc_pm_idle_component(hwahcp); rval = hwahc_cleanup(dip, hwahcp); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "failure to complete cleanup after attach failure"); } } return (DDI_FAILURE); } /* * hwahc_detach: * detach or suspend driver instance * * Note: in detach, only contention threads is from pm and disconnnect. */ static int hwahc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { int instance = ddi_get_instance(dip); hwahc_state_t *hwahcp = ddi_get_soft_state(hwahc_statep, instance); int rval = DDI_FAILURE; USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_detach: cmd = %d", cmd); switch (cmd) { case DDI_DETACH: USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "offline uwb device for dip: 0x%p", (void *)dip); /* offline the hwarc interface */ (void) uwb_dev_offline(dip); if (hwahcp) { rval = hwahc_cleanup(dip, hwahcp); } break; case DDI_SUSPEND: rval = hwahc_cpr_suspend(dip); break; default: break; } return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE); } /* * hwahc_cleanup: * clean up on attach failure or detach */ static int hwahc_cleanup(dev_info_t *dip, hwahc_state_t *hwahcp) { USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_cleanup: start"); if ((hwahcp->hwahc_flags & HWAHC_LOCK_INITED) == 0) { goto done; } /* * deallocate events, if events are still registered * (ie. children still attached) then we have to fail the detach */ if (hwahcp->hwahc_ndi_event_hdl && (ndi_event_free_hdl(hwahcp->hwahc_ndi_event_hdl) != NDI_SUCCESS)) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_cleanup: ndi_event_free_hdl failed"); return (USB_FAILURE); } if (hwahcp->hwahc_flags & HWAHC_EVENTS_REGISTERED) { /* unregister events */ usb_unregister_event_cbs(dip, &hwahc_events); } if (hwahcp->hwahc_flags & HWAHC_HCDI_REGISTERED) { /* unregister the instance with usba HCD interface */ usba_hcdi_unregister(hwahcp->hwahc_dip); } mutex_enter(&hwahcp->hwahc_mutex); if (hwahcp->hwahc_hw_state != HWAHC_HW_STOPPED) { /* stop the hw if it is enabled */ (void) hwahc_hc_final_stop(hwahcp); } if (hwahcp->hwahc_flags & HWAHC_WA_STARTED) { /* can be combined with wusb_wa_data_fini() */ mutex_exit(&hwahcp->hwahc_mutex); hwahc_wa_stop(hwahcp); mutex_enter(&hwahcp->hwahc_mutex); } if (hwahcp->hwahc_flags & HWAHC_HC_INITED) { /* deinitialize the WUSB host function related structure */ hwahc_hc_data_fini(hwahcp); } mutex_exit(&hwahcp->hwahc_mutex); if (hwahcp->hwahc_pm) { /* destroy power management components */ hwahc_destroy_pm_components(hwahcp); } if (hwahcp->hwahc_flags & HWAHC_HUBREG) { /* unregister the instance from usba HUBD interface */ if (hwahc_hub_detach(hwahcp) != USB_SUCCESS) { return (USB_FAILURE); } } if (hwahcp->hwahc_hcdi_ops) { usba_free_hcdi_ops(hwahcp->hwahc_hcdi_ops); } mutex_enter(&hwahcp->hwahc_mutex); if (hwahcp->hwahc_secrt_data.secrt_encry_descr) { /* free security descrs */ kmem_free(hwahcp->hwahc_secrt_data.secrt_encry_descr, sizeof (usb_encryption_descr_t) * hwahcp->hwahc_secrt_data.secrt_n_encry); } if (hwahcp->hwahc_flags & HWAHC_WA_INITED) { /* deinitialize data transfer function related structure */ wusb_wa_data_fini(&hwahcp->hwahc_wa_data); } mutex_exit(&hwahcp->hwahc_mutex); if (hwahcp->hwahc_flags & HWAHC_MINOR_NODE_CREATED) { /* remove all the minor nodes */ ddi_remove_minor_node(dip, NULL); } /* destroy mutex and cv */ mutex_destroy(&hwahcp->hwahc_mutex); cv_destroy(&hwahcp->hwahc_result_thread_cv); done: /* unregister the client driver from usba */ usb_client_detach(dip, hwahcp->hwahc_dev_data); USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_cleanup: end"); usb_free_log_hdl(hwahcp->hwahc_log_handle); /* remove all properties created */ ddi_prop_remove_all(dip); /* free the soft state information */ ddi_soft_state_free(hwahc_statep, ddi_get_instance(dip)); return (USB_SUCCESS); } /*ARGSUSED*/ static int hwahc_open(dev_t *devp, int flag, int otyp, cred_t *cred_p) { hwahc_state_t *hwahcp; if ((hwahcp = ddi_get_soft_state(hwahc_statep, HWAHC_MINOR_TO_INSTANCE(getminor(*devp)))) == NULL) { return (ENXIO); } USB_DPRINTF_L4(PRINT_MASK_OPEN, hwahcp->hwahc_log_handle, "hwahc_open: start"); mutex_enter(&hwahcp->hwahc_mutex); /* exclusive open */ if ((flag & FEXCL) && (hwahcp->hwahc_open_count > 0)) { mutex_exit(&hwahcp->hwahc_mutex); return (EBUSY); } if ((hwahcp->hwahc_dev_state == USB_DEV_DISCONNECTED) || (hwahcp->hwahc_dev_state == USB_DEV_SUSPENDED)) { mutex_exit(&hwahcp->hwahc_mutex); return (EIO); } hwahcp->hwahc_open_count++; mutex_exit(&hwahcp->hwahc_mutex); /* raise to full power and keep it until close */ hwahc_pm_busy_component(hwahcp); (void) pm_raise_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_FULL_PWR); USB_DPRINTF_L4(PRINT_MASK_OPEN, hwahcp->hwahc_log_handle, "hwahc_open: end"); return (0); } /*ARGSUSED*/ static int hwahc_close(dev_t dev, int flag, int otyp, cred_t *cred_p) { hwahc_state_t *hwahcp; if ((hwahcp = ddi_get_soft_state(hwahc_statep, HWAHC_MINOR_TO_INSTANCE(getminor(dev)))) == NULL) { return (ENXIO); } USB_DPRINTF_L4(PRINT_MASK_CLOSE, hwahcp->hwahc_log_handle, "hwahc_close: start"); mutex_enter(&hwahcp->hwahc_mutex); if (hwahcp->hwahc_open_count == 0) { USB_DPRINTF_L2(PRINT_MASK_CLOSE, hwahcp->hwahc_log_handle, "hwahc_close: already closed"); mutex_exit(&hwahcp->hwahc_mutex); return (EINVAL); } hwahcp->hwahc_open_count--; mutex_exit(&hwahcp->hwahc_mutex); hwahc_pm_idle_component(hwahcp); USB_DPRINTF_L4(PRINT_MASK_CLOSE, hwahcp->hwahc_log_handle, "hwahc_close: end"); return (0); } /* retrieve port number from devctl data */ static usb_port_t hwahc_get_port_num(hwahc_state_t *hwahcp, struct devctl_iocdata *dcp) { int32_t port; ASSERT(mutex_owned(&hwahcp->hwahc_mutex)); /* Get which port to operate on. */ if (nvlist_lookup_int32(ndi_dc_get_ap_data(dcp), "port", &port) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_get_port_num: port lookup failed"); port = 0; } USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_get_port_num: hwahcp=0x%p, port=%d", (void *)hwahcp, port); return ((usb_port_t)port); } /* return the child dip on a certain port */ static dev_info_t * hwahc_get_child_dip(hwahc_state_t *hwahcp, usb_port_t port) { wusb_hc_data_t *hc_data; dev_info_t *child_dip; hc_data = &hwahcp->hwahc_hc_data; /* check port range to prevent an illegal number */ if (port > hc_data->hc_num_ports) { return (NULL); } mutex_enter(&hc_data->hc_mutex); child_dip = hc_data->hc_children_dips[port]; mutex_exit(&hc_data->hc_mutex); return (child_dip); } /* * hwahc_cfgadm_state: * * child_dip list child_state cfgadm_state * -------------- ---------- ------------ * != NULL connected configured or * unconfigured * != NULL not connected disconnect but * busy/still referenced * NULL connected logically disconnected * NULL not connected empty */ static uint_t hwahc_cfgadm_state(hwahc_state_t *hwahcp, usb_port_t port) { uint_t state; dev_info_t *child_dip = hwahc_get_child_dip(hwahcp, port); wusb_hc_data_t *hc_data = &hwahcp->hwahc_hc_data; wusb_dev_info_t *dev_info; if (child_dip == NULL) { return (HWAHC_CFGADM_INVALID); } mutex_enter(&hc_data->hc_mutex); dev_info = hc_data->hc_dev_infos[port]; if (dev_info) { if (dev_info->wdev_state == WUSB_STATE_CONFIGURED) { if (child_dip && (DEVI_IS_DEVICE_OFFLINE(child_dip) || !i_ddi_devi_attached(child_dip))) { state = HWAHC_CFGADM_UNCONFIGURED; } else if (!child_dip) { state = HWAHC_CFGADM_UNCONFIGURED; } else { state = HWAHC_CFGADM_CONFIGURED; } } else if (dev_info->wdev_state == WUSB_STATE_UNCONNTED) { if (child_dip) { state = HWAHC_CFGADM_STILL_REFERENCED; } else { state = HWAHC_CFGADM_DISCONNECTED; } } else { if (child_dip) { state = HWAHC_CFGADM_STILL_REFERENCED; } else { state = HWAHC_CFGADM_UNCONFIGURED; } } } else { state = HWAHC_CFGADM_EMPTY; } mutex_exit(&hc_data->hc_mutex); USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_cfgadm_state: hwahcp=0x%p, port=%d state=0x%x", (void *) hwahcp, port, state); return (state); } /* cfgadm ioctl support, now only implements list function */ /* ARGSUSED */ static int hwahc_cfgadm_ioctl(hwahc_state_t *hwahcp, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { dev_info_t *rh_dip; dev_info_t *child_dip; struct devctl_iocdata *dcp = NULL; usb_port_t port = 0; devctl_ap_state_t ap_state; int circ, rh_circ, prh_circ; int rv = 0; char *msg; /* read devctl ioctl data */ if ((cmd != DEVCTL_AP_CONTROL) && (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS)) { return (EFAULT); } mutex_enter(&hwahcp->hwahc_mutex); rh_dip = hwahcp->hwahc_hubd->h_usba_device->usb_root_hub_dip; switch (cmd) { case DEVCTL_AP_DISCONNECT: case DEVCTL_AP_UNCONFIGURE: case DEVCTL_AP_CONFIGURE: if (hwahcp->hwahc_dev_state == USB_DEV_DISCONNECTED) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_cfgadm_ioctl: dev already gone"); mutex_exit(&hwahcp->hwahc_mutex); if (dcp) { ndi_dc_freehdl(dcp); } return (EIO); } /* FALLTHROUGH */ case DEVCTL_AP_GETSTATE: if ((port = hwahc_get_port_num(hwahcp, dcp)) == 0) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_cfgadm_ioctl: bad port"); mutex_exit(&hwahcp->hwahc_mutex); if (dcp) { ndi_dc_freehdl(dcp); } return (EINVAL); } break; case DEVCTL_AP_CONTROL: break; default: mutex_exit(&hwahcp->hwahc_mutex); if (dcp) { ndi_dc_freehdl(dcp); } return (ENOTTY); } /* should not happen, just in case */ if (hwahcp->hwahc_dev_state == USB_DEV_SUSPENDED) { mutex_exit(&hwahcp->hwahc_mutex); if (dcp) { ndi_dc_freehdl(dcp); } return (EIO); } mutex_exit(&hwahcp->hwahc_mutex); ndi_devi_enter(ddi_get_parent(rh_dip), &prh_circ); ndi_devi_enter(rh_dip, &rh_circ); ndi_devi_enter(hwahcp->hwahc_dip, &circ); mutex_enter(&hwahcp->hwahc_mutex); switch (cmd) { case DEVCTL_AP_DISCONNECT: /* TODO: not supported now */ rv = EIO; break; case DEVCTL_AP_UNCONFIGURE: /* TODO: not supported now */ rv = EIO; break; case DEVCTL_AP_CONFIGURE: /* TODO: not supported now */ rv = EIO; break; case DEVCTL_AP_GETSTATE: switch (hwahc_cfgadm_state(hwahcp, port)) { case HWAHC_CFGADM_DISCONNECTED: /* port previously 'disconnected' by cfgadm */ ap_state.ap_rstate = AP_RSTATE_DISCONNECTED; ap_state.ap_ostate = AP_OSTATE_UNCONFIGURED; ap_state.ap_condition = AP_COND_OK; break; case HWAHC_CFGADM_UNCONFIGURED: ap_state.ap_rstate = AP_RSTATE_CONNECTED; ap_state.ap_ostate = AP_OSTATE_UNCONFIGURED; ap_state.ap_condition = AP_COND_OK; break; case HWAHC_CFGADM_CONFIGURED: ap_state.ap_rstate = AP_RSTATE_CONNECTED; ap_state.ap_ostate = AP_OSTATE_CONFIGURED; ap_state.ap_condition = AP_COND_OK; break; case HWAHC_CFGADM_STILL_REFERENCED: ap_state.ap_rstate = AP_RSTATE_EMPTY; ap_state.ap_ostate = AP_OSTATE_CONFIGURED; ap_state.ap_condition = AP_COND_UNUSABLE; break; case HWAHC_CFGADM_EMPTY: default: ap_state.ap_rstate = AP_RSTATE_EMPTY; ap_state.ap_ostate = AP_OSTATE_UNCONFIGURED; ap_state.ap_condition = AP_COND_OK; break; } ap_state.ap_last_change = (time_t)-1; ap_state.ap_error_code = 0; ap_state.ap_in_transition = 0; USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "DEVCTL_AP_GETSTATE: " "ostate=0x%x, rstate=0x%x, condition=0x%x", ap_state.ap_ostate, ap_state.ap_rstate, ap_state.ap_condition); /* copy the return-AP-state information to the user space */ if (ndi_dc_return_ap_state(&ap_state, dcp) != NDI_SUCCESS) { rv = EFAULT; } break; case DEVCTL_AP_CONTROL: { /* * Generic devctl for hardware-specific functionality. * For list of sub-commands see hubd_impl.h */ hubd_ioctl_data_t ioc; /* for 64 byte copies */ /* copy user ioctl data in first */ #ifdef _MULTI_DATAMODEL if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) { hubd_ioctl_data_32_t ioc32; if (ddi_copyin((void *)arg, (void *)&ioc32, sizeof (ioc32), mode) != 0) { rv = EFAULT; break; } ioc.cmd = (uint_t)ioc32.cmd; ioc.port = (uint_t)ioc32.port; ioc.get_size = (uint_t)ioc32.get_size; ioc.buf = (caddr_t)(uintptr_t)ioc32.buf; ioc.bufsiz = (uint_t)ioc32.bufsiz; ioc.misc_arg = (uint_t)ioc32.misc_arg; } else #endif /* _MULTI_DATAMODEL */ if (ddi_copyin((void *)arg, (void *)&ioc, sizeof (ioc), mode) != 0) { rv = EFAULT; break; } USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "DEVCTL_AP_CONTROL: ioc: cmd=0x%x port=%d get_size=%d" "\n\tbuf=0x%p, bufsiz=%d, misc_arg=%d", ioc.cmd, ioc.port, ioc.get_size, (void *) ioc.buf, ioc.bufsiz, ioc.misc_arg); /* * To avoid BE/LE and 32/64 issues, a get_size always * returns a 32-bit number. */ if (ioc.get_size != 0 && ioc.bufsiz != (sizeof (uint32_t))) { rv = EINVAL; break; } switch (ioc.cmd) { case USB_DESCR_TYPE_DEV: msg = "DEVCTL_AP_CONTROL: GET_DEVICE_DESC"; if (ioc.get_size) { /* uint32 so this works 32/64 */ uint32_t size = sizeof (usb_dev_descr_t); if (ddi_copyout((void *)&size, ioc.buf, ioc.bufsiz, mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: get_size copyout failed", msg); rv = EIO; break; } } else { /* send out the actual descr */ usb_dev_descr_t *dev_descrp; /* check child_dip */ if ((child_dip = hwahc_get_child_dip(hwahcp, ioc.port)) == NULL) { rv = EINVAL; break; } dev_descrp = usb_get_dev_descr(child_dip); if (ioc.bufsiz != sizeof (*dev_descrp)) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: bufsize passed (%d) != sizeof " "usba_device_descr_t (%d)", msg, ioc.bufsiz, dev_descrp->bLength); rv = EINVAL; break; } if (ddi_copyout((void *)dev_descrp, ioc.buf, ioc.bufsiz, mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: copyout failed.", msg); rv = EIO; break; } } break; case USB_DESCR_TYPE_CFG: { usba_device_t *child_ud = NULL; uint32_t idx = ioc.misc_arg; uint32_t cfg_len = 0; if ((child_dip = hwahc_get_child_dip(hwahcp, ioc.port)) == NULL) { rv = EINVAL; break; } child_ud = usba_get_usba_device(child_dip); cfg_len = (uint32_t)child_ud->usb_cfg_array_len[idx]; msg = "DEVCTL_AP_CONTROL: GET_CONFIG_DESC"; if (ioc.get_size) { if (ddi_copyout((void *)&cfg_len, ioc.buf, ioc.bufsiz, mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: get_size copyout failed", msg); rv = EIO; break; } } else { /* send out the actual descr */ uchar_t *cfg_descr = child_ud->usb_cfg_array[idx]; if (ioc.bufsiz != cfg_len) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: bufsize passed (%d) != size " "of cfg_descr (%d)", msg, ioc.bufsiz, cfg_len); rv = EINVAL; break; } if (ddi_copyout((void *)cfg_descr, ioc.buf, ioc.bufsiz, mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: copyout failed.", msg); rv = EIO; break; } } break; } case USB_DESCR_TYPE_STRING: { char *str; uint32_t size; usba_device_t *usba_device; msg = "DEVCTL_AP_CONTROL: GET_STRING_DESCR"; USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: string request: %d", msg, ioc.misc_arg); /* recheck */ if ((child_dip = hwahc_get_child_dip(hwahcp, ioc.port)) == NULL) { rv = EINVAL; break; } usba_device = usba_get_usba_device(child_dip); switch (ioc.misc_arg) { case HUBD_MFG_STR: str = usba_device->usb_mfg_str; break; case HUBD_PRODUCT_STR: str = usba_device->usb_product_str; break; case HUBD_SERIALNO_STR: str = usba_device->usb_serialno_str; break; case HUBD_CFG_DESCR_STR: mutex_enter(&usba_device->usb_mutex); str = usba_device->usb_cfg_str_descr[ usba_device->usb_active_cfg_ndx]; mutex_exit(&usba_device->usb_mutex); break; default: USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: Invalid string request", msg); rv = EINVAL; break; } /* end of switch */ if (rv != 0) { break; } size = (str != NULL) ? strlen(str) + 1 : 0; if (ioc.get_size) { if (ddi_copyout((void *)&size, ioc.buf, ioc.bufsiz, mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: copyout of size failed.", msg); rv = EIO; break; } } else { if (size == 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: String is NULL", msg); rv = EINVAL; break; } if (ioc.bufsiz != size) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: string buf size wrong", msg); rv = EINVAL; break; } if (ddi_copyout((void *)str, ioc.buf, ioc.bufsiz, mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: copyout failed.", msg); rv = EIO; break; } } break; } case HUBD_GET_CFGADM_NAME: { uint32_t name_len; const char *name; /* recheck */ if ((child_dip = hwahc_get_child_dip(hwahcp, ioc.port)) == NULL) { rv = EINVAL; break; } name = ddi_node_name(child_dip); if (name == NULL) { name = "unsupported"; } name_len = strlen(name) + 1; msg = "DEVCTL_AP_CONTROL: HUBD_GET_CFGADM_NAME"; USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: name=%s name_len=%d", msg, name, name_len); if (ioc.get_size) { if (ddi_copyout((void *)&name_len, ioc.buf, ioc.bufsiz, mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: copyout of size failed", msg); rv = EIO; break; } } else { if (ioc.bufsiz != name_len) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: string buf length wrong", msg); rv = EINVAL; break; } if (ddi_copyout((void *)name, ioc.buf, ioc.bufsiz, mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: copyout failed.", msg); rv = EIO; break; } } break; } /* * Return the config index for the currently-configured * configuration. */ case HUBD_GET_CURRENT_CONFIG: { uint_t config_index; uint32_t size = sizeof (config_index); usba_device_t *usba_device; msg = "DEVCTL_AP_CONTROL: GET_CURRENT_CONFIG"; USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s", msg); /* * Return the config index for the configuration * currently in use. * Recheck if child_dip exists */ if ((child_dip = hwahc_get_child_dip(hwahcp, ioc.port)) == NULL) { rv = EINVAL; break; } usba_device = usba_get_usba_device(child_dip); mutex_enter(&usba_device->usb_mutex); config_index = usba_device->usb_active_cfg_ndx; mutex_exit(&usba_device->usb_mutex); if (ioc.get_size) { if (ddi_copyout((void *)&size, ioc.buf, ioc.bufsiz, mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: copyout of size failed.", msg); rv = EIO; break; } } else { if (ioc.bufsiz != size) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: buffer size wrong", msg); rv = EINVAL; break; } if (ddi_copyout((void *)&config_index, ioc.buf, ioc.bufsiz, mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: copyout failed", msg); rv = EIO; } } break; } case HUBD_GET_DEVICE_PATH: { char *path; uint32_t size; msg = "DEVCTL_AP_CONTROL: GET_DEVICE_PATH"; USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s", msg); /* Recheck if child_dip exists */ if ((child_dip = hwahc_get_child_dip(hwahcp, ioc.port)) == NULL) { rv = EINVAL; break; } /* ddi_pathname doesn't supply /devices, so we do. */ path = kmem_alloc(MAXPATHLEN, KM_SLEEP); (void) strcpy(path, "/devices"); (void) ddi_pathname(child_dip, path + strlen(path)); size = strlen(path) + 1; USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: device path=%s size=%d", msg, path, size); if (ioc.get_size) { if (ddi_copyout((void *)&size, ioc.buf, ioc.bufsiz, mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: copyout of size failed.", msg); rv = EIO; } } else { if (ioc.bufsiz != size) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: buffer wrong size.", msg); rv = EINVAL; } else if (ddi_copyout((void *)path, ioc.buf, ioc.bufsiz, mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: copyout failed.", msg); rv = EIO; } } kmem_free(path, MAXPATHLEN); break; } case HUBD_REFRESH_DEVDB: msg = "DEVCTL_AP_CONTROL: HUBD_REFRESH_DEVDB"; USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s", msg); if ((rv = usba_devdb_refresh()) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "%s: Failed: %d", msg, rv); rv = EIO; } break; default: rv = ENOTSUP; } /* end switch */ break; } default: rv = ENOTTY; } if (dcp) { ndi_dc_freehdl(dcp); } mutex_exit(&hwahcp->hwahc_mutex); ndi_devi_exit(hwahcp->hwahc_dip, circ); ndi_devi_exit(rh_dip, rh_circ); ndi_devi_exit(ddi_get_parent(rh_dip), prh_circ); return (rv); } /* update CHID for the hc driver, return 0 on success */ static int hwahc_set_chid(hwahc_state_t *hwahcp, uint8_t *chid) { wusb_hc_data_t *hc_data = &hwahcp->hwahc_hc_data; ASSERT(!mutex_owned(&hc_data->hc_mutex)); /* same as the old CHID, return success */ if (memcmp(chid, hc_data->hc_chid, 16) == 0) { return (0); } /* * stop hw from working before updating CHID * this may not be necessary but so far we don't know * other ways to do it safely */ if (hwahcp->hwahc_hw_state == HWAHC_HW_STARTED) { /* use final_stop to fully stop the hwa */ if (hwahc_hc_final_stop(hwahcp) != USB_SUCCESS) { return (EIO); } mutex_enter(&hc_data->hc_mutex); (void) memcpy(hc_data->hc_chid, chid, 16); mutex_exit(&hc_data->hc_mutex); /* restart the host */ if (hwahc_hc_initial_start(hwahcp) != USB_SUCCESS) { return (EIO); } return (0); } /* hc is stopped or partially stopped, simply update */ mutex_enter(&hc_data->hc_mutex); (void) memcpy(hc_data->hc_chid, chid, 16); mutex_exit(&hc_data->hc_mutex); return (0); } /* * wusbadm ioctl support */ /* ARGSUSED */ static int hwahc_wusb_ioctl(hwahc_state_t *hwahcp, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { int rv = 0; wusb_hc_data_t *hc_data = &hwahcp->hwahc_hc_data; if (drv_priv(credp) != 0) { USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_wusb_ioctl: user must have SYS_DEVICE privilege," "cmd=%x", cmd); return (EPERM); } mutex_enter(&hwahcp->hwahc_mutex); switch (cmd) { case WUSB_HC_GET_DSTATE: /* Get device state: wusbadm list */ { wusb_hc_get_dstate_t state; usb_port_t port = 0; if (ddi_copyin((void *)arg, (void *)&state, sizeof (state), mode) != 0) { rv = EFAULT; break; } mutex_enter(&hc_data->hc_mutex); if (wusb_hc_is_dev_connected(hc_data, &state.cdid[0], &port)) { state.state = hc_data->hc_dev_infos[port]->wdev_state; } else { /* cdid not found */ state.state = WUSB_STATE_UNCONNTED; } USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_wusb_ioctl: hc_data=%p, port = %d, state=%d", (void *) hc_data, port, state.state); mutex_exit(&hc_data->hc_mutex); if (state.state == WUSB_STATE_CONFIGURED) { /* Get the bind device node name of this child */ (void) memset(state.nodename, 0, MAX_USB_NODENAME); (void) snprintf(state.nodename, MAX_USB_NODENAME, "%s", ddi_node_name(hwahc_get_child_dip(hwahcp, port))); USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_GET_DSTATE: nodename %s", state.nodename); } if (ddi_copyout((void *)&state, (void *)arg, sizeof (state), mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_GET_DSTATE: copyout failed"); rv = EIO; } break; } case WUSB_HC_GET_MAC_ADDR: /* Get host MAC addr */ { uint8_t mac_addr[6]; bzero(mac_addr, 6); /* * get UWB 48-bit mac address * Section 8.6.2.2. */ if (uwb_get_mac_addr(hwahcp->hwahc_dip, mac_addr) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_GET_MAC_ADDR: get mac failed"); rv = EIO; break; } if (ddi_copyout((void *)mac_addr, (void *)arg, 6, mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_GET_MAC_ADDR: copyout failed"); rv = EIO; } break; } case WUSB_HC_ADD_CC: { /* * add a new device CC to host's list: wusbadm associate * Or, the application can pass in a fake CC with only CHID set * to set the host's CHID. */ wusb_hc_cc_list_t *cc_list; cc_list = kmem_zalloc(sizeof (wusb_hc_cc_list_t), KM_SLEEP); if (ddi_copyin((void *)arg, (void *)&cc_list->cc, sizeof (wusb_cc_t), mode) != 0) { rv = EFAULT; kmem_free(cc_list, sizeof (wusb_hc_cc_list_t)); break; } /* update CHID only when cc list is empty */ mutex_enter(&hc_data->hc_mutex); if (hc_data->hc_cc_list == NULL) { mutex_exit(&hc_data->hc_mutex); if ((rv = hwahc_set_chid(hwahcp, cc_list->cc.CHID)) != 0) { kmem_free(cc_list, sizeof (wusb_hc_cc_list_t)); break; } mutex_enter(&hc_data->hc_mutex); } else { /* fail if the CHID in the new CC does not match */ if (memcmp(cc_list->cc.CHID, hc_data->hc_chid, 16) != 0) { rv = EINVAL; kmem_free(cc_list, sizeof (wusb_hc_cc_list_t)); mutex_exit(&hc_data->hc_mutex); break; } } cc_list->next = NULL; wusb_hc_add_cc(&hc_data->hc_cc_list, cc_list); mutex_exit(&hc_data->hc_mutex); break; } case WUSB_HC_REM_CC: { wusb_cc_t cc; usb_port_t port; if (ddi_copyin((void *)arg, (void *)&cc, sizeof (wusb_cc_t), mode) != 0) { rv = EFAULT; break; } /* check if the CHID in the CC matches */ if (memcmp(cc.CHID, hc_data->hc_chid, 16) != 0) { rv = EINVAL; break; } /* if the device is connected, disconnect it first */ mutex_enter(&hc_data->hc_mutex); if (wusb_hc_is_dev_connected(hc_data, cc.CDID, &port)) { mutex_exit(&hc_data->hc_mutex); mutex_exit(&hwahcp->hwahc_mutex); /* * clean up host side state, device not * really disconnected. But user can safely remove * the device now. */ (void) hwahc_destroy_child(hc_data->hc_dip, port); mutex_enter(&hwahcp->hwahc_mutex); mutex_enter(&hc_data->hc_mutex); } wusb_hc_rem_cc(&hc_data->hc_cc_list, &cc); mutex_exit(&hc_data->hc_mutex); break; } case WUSB_HC_SET_CHANNEL: /* for debug purpose */ { uint8_t channel; channel = (uint8_t)arg; if (hwahcp->hwahc_hc_data.hc_channel == channel) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_SET_CHANNEL ioctl: same as existing"); break; } if (hwahcp->hwahc_hw_state != HWAHC_HW_STOPPED) { /* beacon is already started, stop it first */ if (uwb_stop_beacon(hwahcp->hwahc_dip) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_SET_CHANNEL ioctl: " "stop beacon failed"); rv = EIO; break; } /* update channel number */ hwahcp->hwahc_hc_data.hc_channel = channel; /* restart beacon on the new channel */ if (uwb_start_beacon(hwahcp->hwahc_dip, channel) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_SET_CHANNEL ioctl: " "restart beacon failed"); rv = EIO; } break; } /* beacon is not started, simply update channel number */ hwahcp->hwahc_hc_data.hc_channel = channel; break; } case WUSB_HC_START: { int flag; flag = (int)arg; if (hwahcp->hwahc_hw_state == HWAHC_HW_STARTED) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_START ioctl: already started"); break; } /* * now we start hc only when the cc list is not NULL * this limitation may be removed if we support * numeric association, but CHID needs to be set * in advance for the hc to work */ mutex_enter(&hc_data->hc_mutex); if (hc_data->hc_cc_list == NULL) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_START ioctl: cc list not inited"); rv = EINVAL; mutex_exit(&hc_data->hc_mutex); break; } mutex_exit(&hc_data->hc_mutex); /* cannot be both */ if ((flag & WUSB_HC_INITIAL_START) && (flag & WUSB_HC_CHANNEL_START)) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_START ioctl: flag cannot coexist"); rv = EINVAL; break; } /* * init Mac layer 16-bit dev addr. it is important for * authentication. It'd be better to let UWB provide * this address. */ mutex_enter(&hc_data->hc_mutex); if (hc_data->hc_addr == 0) { uint16_t dev_addr = HWAHC_DEV_ADDR_BASE + ddi_get_instance(hwahcp->hwahc_dip); mutex_exit(&hc_data->hc_mutex); /* set UWB 16-bit dev address */ if (uwb_set_dev_addr(hwahcp->hwahc_dip, dev_addr) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_START ioctl: set dev addr failed"); rv = EIO; break; } /* verify the dev addr is set correctly */ if (uwb_get_dev_addr(hwahcp->hwahc_dip, &dev_addr) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_START ioctl: get dev addr failed"); rv = EIO; break; } mutex_enter(&hc_data->hc_mutex); hc_data->hc_addr = dev_addr; USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "host dev addr = 0x%x", dev_addr); } mutex_exit(&hc_data->hc_mutex); /* start functions of wusb host */ if ((flag & WUSB_HC_INITIAL_START) && (hwahcp->hwahc_hw_state == HWAHC_HW_STOPPED)) { if (hwahc_hc_initial_start(hwahcp) != USB_SUCCESS) { rv = EIO; } } else if ((flag & WUSB_HC_CHANNEL_START) && (hwahcp->hwahc_hw_state == HWAHC_HW_CH_STOPPED)) { if (hwahc_hc_channel_start(hwahcp) != USB_SUCCESS) { rv = EIO; } } else { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_START ioctl: unknown flag (%d) or " "state (%d)", flag, hwahcp->hwahc_hw_state); rv = EINVAL; } break; } case WUSB_HC_STOP: { int flag; flag = (int)arg; /* cannot be both */ if ((flag & WUSB_HC_FINAL_STOP) && (flag & WUSB_HC_CHANNEL_STOP)) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_STOP ioctl: flag cannot coexist"); rv = EINVAL; break; } if (flag & WUSB_HC_FINAL_STOP) { if (hwahc_hc_final_stop(hwahcp) != USB_SUCCESS) { rv = EIO; } } else if (flag & WUSB_HC_CHANNEL_STOP) { if (hwahc_hc_channel_stop(hwahcp) != USB_SUCCESS) { rv = EIO; } } else { /* must be one of the STOP flag */ USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_STOP ioctl: invalid flag = %d", flag); rv = EINVAL; } /* REM_ALL_CC flag is optional */ if ((rv == 0) && (flag & WUSB_HC_REM_ALL_CC)) { mutex_enter(&hc_data->hc_mutex); if (hc_data->hc_cc_list) { wusb_hc_free_cc_list(hc_data->hc_cc_list); hc_data->hc_cc_list = NULL; } mutex_exit(&hc_data->hc_mutex); } break; } case WUSB_HC_GET_HSTATE: { int state; if (hwahcp->hwahc_dev_state == USB_DEV_DISCONNECTED) { state = WUSB_HC_DISCONNTED; } else { switch (hwahcp->hwahc_hw_state) { case HWAHC_HW_STOPPED: state = WUSB_HC_STOPPED; break; case HWAHC_HW_STARTED: state = WUSB_HC_STARTED; break; case HWAHC_HW_CH_STOPPED: /* * app can mark the hwa as disabled * for this state */ state = WUSB_HC_CH_STOPPED; break; } } if (ddi_copyout((void *)&state, (void *)arg, sizeof (int), mode) != 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "WUSB_HC_GET_HSTATE: copyout failed"); rv = EIO; } break; } default: USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_ioctl: unsupported command"); rv = ENOTSUP; } mutex_exit(&hwahcp->hwahc_mutex); return (rv); } static int hwahc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { hwahc_state_t *hwahcp; int rval; if ((hwahcp = ddi_get_soft_state(hwahc_statep, HWAHC_MINOR_TO_INSTANCE(getminor(dev)))) == NULL) { return (ENXIO); } USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_ioctl: cmd=%x, arg=%lx, mode=%x, cred=%p, rval=%p dev=0x%lx", cmd, arg, mode, (void *) credp, (void *) rvalp, dev); if (IS_DEVCTL(cmd)) { /* for cfgadm cmd support */ rval = hwahc_cfgadm_ioctl(hwahcp, cmd, arg, mode, credp, rvalp); } else { /* for wusbadm cmd support */ rval = hwahc_wusb_ioctl(hwahcp, cmd, arg, mode, credp, rvalp); } return (rval); } /* return the port number corresponding the child dip */ static usb_port_t hwahc_child_dip2port(hwahc_state_t *hwahcp, dev_info_t *dip) { usb_port_t port; wusb_hc_data_t *hc_data = &hwahcp->hwahc_hc_data; mutex_enter(&hc_data->hc_mutex); for (port = 1; port <= hc_data->hc_num_ports; port++) { if (hc_data->hc_children_dips[port] == dip) { break; } } ASSERT(port <= hc_data->hc_num_ports); mutex_exit(&hc_data->hc_mutex); return (port); } /* * child post attach/detach notification */ static void hwahc_post_attach(hwahc_state_t *hwahcp, dev_info_t *rdip, struct attachspec *as) { /* we don't need additional process for post-attach now */ USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_post_attach: rdip = 0x%p result = %d", (void *) rdip, as->result); } static void hwahc_post_detach(hwahc_state_t *hwahcp, dev_info_t *rdip, struct detachspec *as) { /* we don't need additional process for post-detach now */ USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_post_detach: rdip = 0x%p result = %d", (void *) rdip, as->result); } /* * bus ctl support. * To support different operations, such as a PreAttach preparation, * PostAttach operations. HWA only process the interested operations. * Other general ones are processed by usba_bus_ctl(). */ static int hwahc_bus_ctl(dev_info_t *dip, /* dip could be the parent */ dev_info_t *rdip, /* rdip is the dev node to be operated */ ddi_ctl_enum_t op, void *arg, void *result) { usba_device_t *usba_device = usba_get_usba_device(rdip); dev_info_t *hubdip = usba_device->usb_root_hub_dip; hwahc_state_t *hwahcp; struct attachspec *as; struct detachspec *ds; if ((hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip))) == NULL) { return (DDI_FAILURE); } USB_DPRINTF_L2(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_bus_ctl:\n\t" "dip = 0x%p, rdip = 0x%p, op = 0x%x, arg = 0x%p", (void *) dip, (void *) rdip, op, (void *) arg); switch (op) { case DDI_CTLOPS_ATTACH: as = (struct attachspec *)arg; switch (as->when) { case DDI_PRE : /* nothing to do basically */ USB_DPRINTF_L2(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "DDI_PRE DDI_CTLOPS_ATTACH"); break; case DDI_POST : hwahc_post_attach(hwahcp, rdip, (struct attachspec *)arg); break; } break; case DDI_CTLOPS_DETACH: ds = (struct detachspec *)arg; switch (ds->when) { case DDI_PRE : /* nothing to do basically */ USB_DPRINTF_L2(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "DDI_PRE DDI_CTLOPS_DETACH"); break; case DDI_POST : hwahc_post_detach(hwahcp, rdip, (struct detachspec *)arg); break; } break; case DDI_CTLOPS_REPORTDEV: /* the workhorse behind ddi_report_dev */ { char *name, compat_name[64]; if (usb_owns_device(rdip)) { (void) snprintf(compat_name, sizeof (compat_name), "usb%x,%x", usba_device->usb_dev_descr->idVendor, usba_device->usb_dev_descr->idProduct); } else if (usba_owns_ia(rdip)) { (void) snprintf(compat_name, sizeof (compat_name), "usbia%x,%x.config%x.%x", usba_device->usb_dev_descr->idVendor, usba_device->usb_dev_descr->idProduct, usba_device->usb_cfg_value, usb_get_if_number(rdip)); } else { (void) snprintf(compat_name, sizeof (compat_name), "usbif%x,%x.config%x.%x", usba_device->usb_dev_descr->idVendor, usba_device->usb_dev_descr->idProduct, usba_device->usb_cfg_value, usb_get_if_number(rdip)); } cmn_err(CE_CONT, "?USB %x.%x %s (%s) operating wirelessly with " "HWA device: " "%s@%s, %s%d at bus address %d\n", (usba_device->usb_dev_descr->bcdUSB & 0xff00) >> 8, usba_device->usb_dev_descr->bcdUSB & 0xff, (usb_owns_device(rdip) ? "device" : ((usba_owns_ia(rdip) ? "interface-association" : "interface"))), compat_name, ddi_node_name(rdip), ddi_get_name_addr(rdip), ddi_driver_name(rdip), ddi_get_instance(rdip), usba_device->usb_addr); name = kmem_alloc(MAXNAMELEN, KM_SLEEP); (void) usba_get_mfg_prod_sn_str(rdip, name, MAXNAMELEN); if (name[0] != '\0') { cmn_err(CE_CONT, "?\t%s\n", name); } kmem_free(name, MAXNAMELEN); break; } default: /* pass to usba to handle */ return (usba_bus_ctl(hubdip, rdip, op, arg, result)); } return (DDI_SUCCESS); } /* * bus enumeration entry points * Configures the named device(BUS_CONFIG_ONE) or all devices under * the nexus(BUS_CONFIG_ALL). Drives devinfo state to DS_READY,i.e.device * is fully operational. * * This operation is driven from devfs(reading /devices), devctl, libdevinfo; * or from within the kernel to attach a boot device or layered underlying * driver. */ static int hwahc_bus_config(dev_info_t *dip, uint_t flag, ddi_bus_config_op_t op, void *arg, dev_info_t **child) { hwahc_state_t *hwahcp; int rval, circ; if ((hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip))) == NULL) { return (NDI_FAILURE); } USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_bus_config: op=%d", op); if (hwahc_bus_config_debug) { flag |= NDI_DEVI_DEBUG; } ndi_devi_enter(dip, &circ); rval = ndi_busop_bus_config(dip, flag, op, arg, child, 0); ndi_devi_exit(dip, circ); return (rval); } /* * Unconfigures the named device or all devices under the nexus. The * devinfo state is not DS_READY anymore. * This operations is driven by modunload, devctl or DR branch removal or * rem_drv(1M). */ static int hwahc_bus_unconfig(dev_info_t *dip, uint_t flag, ddi_bus_config_op_t op, void *arg) { hwahc_state_t *hwahcp; wusb_hc_data_t *hc_data; dev_info_t *cdip; usb_port_t port; int rval, circ; if ((hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip))) == NULL) { return (NDI_FAILURE); } USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_bus_unconfig: op=%d", op); if (hwahc_bus_config_debug) { flag |= NDI_DEVI_DEBUG; } if ((op == BUS_UNCONFIG_ALL) && (flag & NDI_AUTODETACH) == 0) { flag |= NDI_DEVI_REMOVE; } /* serialize access */ ndi_devi_enter(dip, &circ); /* unconfig children, detach them */ rval = ndi_busop_bus_unconfig(dip, flag, op, arg); /* logically zap children's list */ hc_data = &hwahcp->hwahc_hc_data; mutex_enter(&hc_data->hc_mutex); for (port = 1; port <= hc_data->hc_num_ports; port++) { hc_data->hc_children_state[port] |= WUSB_CHILD_ZAP; } mutex_exit(&hc_data->hc_mutex); /* fill in what's left */ for (cdip = ddi_get_child(dip); cdip; cdip = ddi_get_next_sibling(cdip)) { usba_device_t *usba_device = usba_get_usba_device(cdip); if (usba_device == NULL) { continue; } mutex_enter(&hc_data->hc_mutex); port = usba_device->usb_port; hc_data->hc_children_dips[port] = cdip; hc_data->hc_children_state[port] &= ~WUSB_CHILD_ZAP; mutex_exit(&hc_data->hc_mutex); } /* physically zap the children we didn't find */ mutex_enter(&hc_data->hc_mutex); for (port = 1; port <= hc_data->hc_num_ports; port++) { if (hc_data->hc_children_state[port] & WUSB_CHILD_ZAP) { wusb_dev_info_t *dev_info; wusb_secrt_data_t *csecrt_data; usba_device_t *child_ud; USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_bus_unconfig: physically zap port %d", port); child_ud = hc_data->hc_usba_devices[port]; mutex_exit(&hc_data->hc_mutex); /* zap the dip and usba_device structure as well */ usba_free_usba_device(child_ud); mutex_enter(&hc_data->hc_mutex); hc_data->hc_usba_devices[port] = NULL; /* dip freed in usba_destroy_child_devi */ hc_data->hc_children_dips[port] = NULL; hc_data->hc_children_state[port] &= ~WUSB_CHILD_ZAP; /* free hc_dev_infos[port] */ dev_info = hc_data->hc_dev_infos[port]; if (dev_info == NULL) { continue; } /* stop the device's trust timer before deallocate it */ hwahc_stop_trust_timer(dev_info); if (dev_info->wdev_secrt_data.secrt_encry_descr) { csecrt_data = &dev_info->wdev_secrt_data; kmem_free(csecrt_data->secrt_encry_descr, sizeof (usb_encryption_descr_t) * csecrt_data->secrt_n_encry); } if (dev_info->wdev_uwb_descr) { kmem_free(dev_info->wdev_uwb_descr, sizeof (usb_uwb_cap_descr_t)); } kmem_free(dev_info, sizeof (wusb_dev_info_t)); hc_data->hc_dev_infos[port] = NULL; } } mutex_exit(&hc_data->hc_mutex); ndi_devi_exit(dip, circ); USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_bus_unconfig: rval=%d", rval); return (rval); } /* * busctl event support * * Called by ndi_busop_get_eventcookie(). Return a event cookie * associated with one event name. * The eventname should be the one we defined in hwahc_ndi_event_defs */ static int hwahc_busop_get_eventcookie(dev_info_t *dip, dev_info_t *rdip, char *eventname, ddi_eventcookie_t *cookie) { hwahc_state_t *hwahcp; if ((hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip))) == NULL) { return (NDI_FAILURE); } USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_busop_get_eventcookie: dip=0x%p, rdip=0x%p, " "event=%s", (void *)dip, (void *)rdip, eventname); USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "(dip=%s%d, rdip=%s%d)", ddi_driver_name(dip), ddi_get_instance(dip), ddi_driver_name(rdip), ddi_get_instance(rdip)); /* return event cookie, iblock cookie, and level */ return (ndi_event_retrieve_cookie(hwahcp->hwahc_ndi_event_hdl, rdip, eventname, cookie, NDI_EVENT_NOPASS)); } /* * Add event handler for a given event cookie */ static int hwahc_busop_add_eventcall(dev_info_t *dip, dev_info_t *rdip, ddi_eventcookie_t cookie, void (*callback)(dev_info_t *dip, ddi_eventcookie_t cookie, void *arg, void *bus_impldata), void *arg, ddi_callback_id_t *cb_id) { hwahc_state_t *hwahcp; usb_port_t port; if ((hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip))) == NULL) { return (NDI_FAILURE); } port = hwahc_child_dip2port(hwahcp, rdip); mutex_enter(&hwahcp->hwahc_mutex); USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_busop_add_eventcall: dip=0x%p, rdip=0x%p " "cookie=0x%p, cb=0x%p, arg=0x%p", (void *)dip, (void *)rdip, (void *)cookie, (void *)callback, arg); USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "(dip=%s%d, rdip=%s%d, event=%s)", ddi_driver_name(dip), ddi_get_instance(dip), ddi_driver_name(rdip), ddi_get_instance(rdip), ndi_event_cookie_to_name(hwahcp->hwahc_ndi_event_hdl, cookie)); /* Set flag on children registering events */ switch (ndi_event_cookie_to_tag(hwahcp->hwahc_ndi_event_hdl, cookie)) { case USBA_EVENT_TAG_HOT_REMOVAL: hwahcp->hwahc_child_events[port] |= HWAHC_CHILD_EVENT_DISCONNECT; break; case USBA_EVENT_TAG_PRE_SUSPEND: hwahcp->hwahc_child_events[port] |= HWAHC_CHILD_EVENT_PRESUSPEND; break; default: break; } mutex_exit(&hwahcp->hwahc_mutex); /* add callback to our event set */ return (ndi_event_add_callback(hwahcp->hwahc_ndi_event_hdl, rdip, cookie, callback, arg, NDI_SLEEP, cb_id)); } /* * Remove a callback previously added by bus_add_eventcall() */ static int hwahc_busop_remove_eventcall(dev_info_t *dip, ddi_callback_id_t cb_id) { hwahc_state_t *hwahcp; ndi_event_callbacks_t *id = (ndi_event_callbacks_t *)cb_id; if ((hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip))) == NULL) { return (NDI_FAILURE); } USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_busop_remove_eventcall: dip=0x%p, rdip=0x%p " "cookie=0x%p", (void *)dip, (void *) id->ndi_evtcb_dip, (void *)id->ndi_evtcb_cookie); USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "(dip=%s%d, rdip=%s%d, event=%s)", ddi_driver_name(dip), ddi_get_instance(dip), ddi_driver_name(id->ndi_evtcb_dip), ddi_get_instance(id->ndi_evtcb_dip), ndi_event_cookie_to_name(hwahcp->hwahc_ndi_event_hdl, id->ndi_evtcb_cookie)); /* remove event registration from our event set */ return (ndi_event_remove_callback(hwahcp->hwahc_ndi_event_hdl, cb_id)); } /* * hwahc_post_event * post event to a single child on the port depending on the type, i.e. * to invoke the child's registered callback. */ static void hwahc_post_event(hwahc_state_t *hwahcp, usb_port_t port, usba_event_t type) { int rval; dev_info_t *dip; usba_device_t *usba_device; ddi_eventcookie_t cookie, rm_cookie, suspend_cookie; wusb_hc_data_t *hc_data = &hwahcp->hwahc_hc_data; USB_DPRINTF_L4(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_post_event: port=%d event=%s", port, ndi_event_tag_to_name(hwahcp->hwahc_ndi_event_hdl, type)); cookie = ndi_event_tag_to_cookie(hwahcp->hwahc_ndi_event_hdl, type); rm_cookie = ndi_event_tag_to_cookie(hwahcp->hwahc_ndi_event_hdl, USBA_EVENT_TAG_HOT_REMOVAL); suspend_cookie = ndi_event_tag_to_cookie(hwahcp->hwahc_ndi_event_hdl, USBA_EVENT_TAG_PRE_SUSPEND); /* * Hotplug daemon may be attaching a driver that may be registering * event callbacks. So it already has got the device tree lock and * event handle mutex. So to prevent a deadlock while posting events, * we grab and release the locks in the same order. */ mutex_enter(&hwahcp->hwahc_mutex); dip = hwahcp->hwahc_hc_data.hc_children_dips[port]; usba_device = hwahcp->hwahc_hc_data.hc_usba_devices[port]; mutex_exit((&hwahcp->hwahc_mutex)); switch (type) { case USBA_EVENT_TAG_HOT_REMOVAL: /* stop this device's timer to prevent its further process */ mutex_enter(&hc_data->hc_mutex); hwahc_stop_trust_timer(hc_data->hc_dev_infos[port]); mutex_exit(&hc_data->hc_mutex); /* Clear the registered event flag */ mutex_enter(&hwahcp->hwahc_mutex); hwahcp->hwahc_child_events[port] &= ~HWAHC_CHILD_EVENT_DISCONNECT; mutex_exit(&hwahcp->hwahc_mutex); (void) ndi_event_do_callback(hwahcp->hwahc_ndi_event_hdl, dip, cookie, NULL); usba_persistent_pipe_close(usba_device); /* * Mark the dip for deletion only after the driver has * seen the disconnect event to prevent cleanup thread * from stepping in between. */ #ifndef __lock_lint mutex_enter(&DEVI(dip)->devi_lock); DEVI_SET_DEVICE_REMOVED(dip); mutex_exit(&DEVI(dip)->devi_lock); #endif break; case USBA_EVENT_TAG_PRE_SUSPEND: mutex_enter(&hwahcp->hwahc_mutex); hwahcp->hwahc_child_events[port] &= ~HWAHC_CHILD_EVENT_PRESUSPEND; mutex_exit(&hwahcp->hwahc_mutex); (void) ndi_event_do_callback(hwahcp->hwahc_ndi_event_hdl, dip, cookie, NULL); /* * persistent pipe close for this event is taken care by the * caller after verfying that all children can suspend */ break; case USBA_EVENT_TAG_HOT_INSERTION: /* * Check if this child has missed the disconnect event before * it registered for event callbacks */ mutex_enter(&hwahcp->hwahc_mutex); if (hwahcp->hwahc_child_events[port] & HWAHC_CHILD_EVENT_DISCONNECT) { /* clear the flag and post disconnect event */ hwahcp->hwahc_child_events[port] &= ~HWAHC_CHILD_EVENT_DISCONNECT; mutex_exit(&hwahcp->hwahc_mutex); (void) ndi_event_do_callback( hwahcp->hwahc_ndi_event_hdl, dip, rm_cookie, NULL); usba_persistent_pipe_close(usba_device); mutex_enter(&hwahcp->hwahc_mutex); } mutex_exit(&hwahcp->hwahc_mutex); /* * Mark the dip as reinserted to prevent cleanup thread * from stepping in. */ #ifndef __lock_lint mutex_enter(&(DEVI(dip)->devi_lock)); DEVI_SET_DEVICE_REINSERTED(dip); mutex_exit(&(DEVI(dip)->devi_lock)); #endif rval = usba_persistent_pipe_open(usba_device); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "failed to reopen all pipes on reconnect"); } (void) ndi_event_do_callback(hwahcp->hwahc_ndi_event_hdl, dip, cookie, NULL); /* * We might see a connect event only if hotplug thread for * disconnect event don't run in time. * Set the flag again, so we don't miss posting a * disconnect event. */ mutex_enter(&hwahcp->hwahc_mutex); hwahcp->hwahc_child_events[port] |= HWAHC_CHILD_EVENT_DISCONNECT; mutex_exit(&hwahcp->hwahc_mutex); break; case USBA_EVENT_TAG_POST_RESUME: /* * Check if this child has missed the pre-suspend event before * it registered for event callbacks */ mutex_enter(&hwahcp->hwahc_mutex); if (hwahcp->hwahc_child_events[port] & HWAHC_CHILD_EVENT_PRESUSPEND) { /* clear the flag and post pre_suspend event */ hwahcp->hwahc_child_events[port] &= ~HWAHC_CHILD_EVENT_PRESUSPEND; mutex_exit(&hwahcp->hwahc_mutex); (void) ndi_event_do_callback( hwahcp->hwahc_ndi_event_hdl, dip, suspend_cookie, NULL); mutex_enter(&hwahcp->hwahc_mutex); } mutex_exit(&hwahcp->hwahc_mutex); mutex_enter(&usba_device->usb_mutex); usba_device->usb_no_cpr = 0; mutex_exit(&usba_device->usb_mutex); /* * Since the pipe has already been opened by whub * at DDI_RESUME time, there is no need for a * persistent pipe open */ (void) ndi_event_do_callback(hwahcp->hwahc_ndi_event_hdl, dip, cookie, NULL); /* * Set the flag again, so we don't miss posting a * pre-suspend event. This enforces a tighter * dev_state model. */ mutex_enter(&hwahcp->hwahc_mutex); hwahcp->hwahc_child_events[port] |= HWAHC_CHILD_EVENT_PRESUSPEND; mutex_exit(&hwahcp->hwahc_mutex); break; } } /* * hwahc_run_callbacks: * Send an event to all children */ static void hwahc_run_callbacks(hwahc_state_t *hwahcp, usba_event_t type) { usb_port_t port; wusb_hc_data_t *hc_data = &hwahcp->hwahc_hc_data; USB_DPRINTF_L4(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_run_callbacks:"); mutex_enter(&hc_data->hc_mutex); for (port = 1; port <= hc_data->hc_num_ports; port++) { if (hc_data->hc_children_dips[port]) { mutex_exit(&hc_data->hc_mutex); hwahc_post_event(hwahcp, port, type); mutex_enter(&hc_data->hc_mutex); } } mutex_exit(&hc_data->hc_mutex); } /* * hwahc_disconnect_event_cb: * Called when hwa device hotplug-removed. * Close pipes * Post event to child * Set state to DISCONNECTED */ static int hwahc_disconnect_event_cb(dev_info_t *dip) { int instance = ddi_get_instance(dip); hwahc_state_t *hwahcp; int circ; if ((hwahcp = ddi_get_soft_state(hwahc_statep, instance)) == NULL) { return (USB_FAILURE); } USB_DPRINTF_L4(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_disconnect_event_cb: dip = 0x%p", (void *)dip); ndi_devi_enter(dip, &circ); mutex_enter(&hwahcp->hwahc_mutex); USB_DPRINTF_L4(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_disconnect_event_cb: devstate= %d hw-state=%d", hwahcp->hwahc_dev_state, hwahcp->hwahc_hw_state); switch (hwahcp->hwahc_dev_state) { case USB_DEV_ONLINE: case USB_DEV_PWRED_DOWN: hwahcp->hwahc_dev_state = USB_DEV_DISCONNECTED; if (hwahcp->hwahc_hw_state != HWAHC_HW_STOPPED) { mutex_exit(&hwahcp->hwahc_mutex); wusb_wa_stop_nep(&hwahcp->hwahc_wa_data); mutex_enter(&hwahcp->hwahc_mutex); hwahc_stop_result_thread(hwahcp); hwahc_drain_notif_queue(hwahcp); } /* FALLTHROUGH */ case USB_DEV_SUSPENDED: /* remain in this state */ mutex_exit(&hwahcp->hwahc_mutex); hwahc_run_callbacks(hwahcp, USBA_EVENT_TAG_HOT_REMOVAL); mutex_enter(&hwahcp->hwahc_mutex); hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_INIT_STATE; break; case USB_DEV_DISCONNECTED: USB_DPRINTF_L2(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_disconnect_event_cb: already disconnected"); break; default: USB_DPRINTF_L2(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_disconnect_event_cb: illegal devstate=%d", hwahcp->hwahc_dev_state); break; } mutex_exit(&hwahcp->hwahc_mutex); ndi_devi_exit(dip, circ); return (USB_SUCCESS); } /* * hwahc_reconnect_event_cb: * Called with device hotplug-inserted * Restore state */ static int hwahc_reconnect_event_cb(dev_info_t *dip) { int instance = ddi_get_instance(dip); hwahc_state_t *hwahcp; int circ; if ((hwahcp = ddi_get_soft_state(hwahc_statep, instance)) == NULL) { return (USB_FAILURE); } USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_reconnect_event_cb: dip = 0x%p", (void *)dip); ndi_devi_enter(dip, &circ); hwahc_restore_device_state(dip, hwahcp); ndi_devi_exit(dip, circ); return (USB_SUCCESS); } /* * hwahc_pre_suspend_event_cb: * Called before HWA device suspend */ static int hwahc_pre_suspend_event_cb(dev_info_t *dip) { int instance = ddi_get_instance(dip); hwahc_state_t *hwahcp; int circ; if ((hwahcp = ddi_get_soft_state(hwahc_statep, instance)) == NULL) { return (USB_FAILURE); } USB_DPRINTF_L4(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_pre_suspend_event_cb: dip = 0x%p", (void *)dip); mutex_enter(&hwahcp->hwahc_mutex); USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_pre_suspend_event_cb: start, hw state = %d, softstate = %d", hwahcp->hwahc_hw_state, hwahcp->hwahc_hc_soft_state); mutex_exit(&hwahcp->hwahc_mutex); /* keep PM out till we see a cpr resume */ (void) hwahc_pm_busy_component(hwahcp); (void) pm_raise_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_FULL_PWR); ndi_devi_enter(dip, &circ); hwahc_run_callbacks(hwahcp, USBA_EVENT_TAG_PRE_SUSPEND); ndi_devi_exit(dip, circ); /* * rc driver is always suspended first, that fails the hc suspend. * need to suspend hc before rc is suspended, so move the suspend * operations here */ mutex_enter(&hwahcp->hwahc_mutex); if (hwahcp->hwahc_dev_state != USB_DEV_ONLINE) { USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_pre_suspend_event_cb: dev_state = %d", hwahcp->hwahc_dev_state); mutex_exit(&hwahcp->hwahc_mutex); return (USB_SUCCESS); } if (hwahcp->hwahc_hw_state == HWAHC_HW_STARTED) { /* * notify children the host is going to stop */ (void) hwahc_hc_channel_suspend(hwahcp); } /* stop the hc from functioning */ if (hwahcp->hwahc_hw_state != HWAHC_HW_STOPPED) { mutex_exit(&hwahcp->hwahc_mutex); wusb_wa_stop_nep(&hwahcp->hwahc_wa_data); mutex_enter(&hwahcp->hwahc_mutex); hwahc_stop_result_thread(hwahcp); hwahc_drain_notif_queue(hwahcp); mutex_exit(&hwahcp->hwahc_mutex); (void) wusb_wa_disable(&hwahcp->hwahc_wa_data, hwahcp->hwahc_default_pipe); mutex_enter(&hwahcp->hwahc_mutex); } USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_pre_suspend_event_cb: end, devstate=%d " "hwstate=%d softstate = %d", hwahcp->hwahc_dev_state, hwahcp->hwahc_hw_state, hwahcp->hwahc_hc_soft_state); mutex_exit(&hwahcp->hwahc_mutex); return (USB_SUCCESS); } /* * hwahc_post_resume_event_cb: * Call after HWA device resume */ static int hwahc_post_resume_event_cb(dev_info_t *dip) { int instance = ddi_get_instance(dip); hwahc_state_t *hwahcp; int circ; if ((hwahcp = ddi_get_soft_state(hwahc_statep, instance)) == NULL) { return (USB_FAILURE); } USB_DPRINTF_L4(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_post_resume_event_cb: dip = 0x%p", (void *)dip); mutex_enter(&hwahcp->hwahc_mutex); USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_post_resume_event_cb: start, hw state = %d, softstate = %d", hwahcp->hwahc_hw_state, hwahcp->hwahc_hc_soft_state); mutex_exit(&hwahcp->hwahc_mutex); ndi_devi_enter(dip, &circ); /* need to place hc restore here to make sure rc has resumed */ hwahc_restore_device_state(dip, hwahcp); hwahc_run_callbacks(hwahcp, USBA_EVENT_TAG_POST_RESUME); ndi_devi_exit(dip, circ); /* enable PM */ (void) hwahc_pm_idle_component(hwahcp); return (USB_SUCCESS); } /* * hwahc_restore_device_state: * Called during hotplug-reconnect and resume. * re-enable power management * Verify the device is the same as before the disconnect/suspend. * Restore device state * Thaw any IO which was frozen. * Quiesce device. (Other routines will activate if thawed IO.) * Set device online. * Leave device disconnected if there are problems. */ static void hwahc_restore_device_state(dev_info_t *dip, hwahc_state_t *hwahcp) { int rval; int old_hw_state; USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_restore_device_state: dip = 0x%p", (void *)dip); mutex_enter(&hwahcp->hwahc_mutex); ASSERT((hwahcp->hwahc_dev_state == USB_DEV_DISCONNECTED) || (hwahcp->hwahc_dev_state == USB_DEV_SUSPENDED)); /* raise power */ mutex_exit(&hwahcp->hwahc_mutex); hwahc_pm_busy_component(hwahcp); (void) pm_raise_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_FULL_PWR); /* * Check if we are talking to the same device * Some host controllers may see all devices disconnected * when they just resume. This may be a cause of not * finding the same device. * * Some HWA devices need to download firmware when it is * powered on. Before the firmware is downloaded, the device * will look differently. */ if (usb_check_same_device(dip, hwahcp->hwahc_log_handle, USB_LOG_L0, PRINT_MASK_ALL, USB_CHK_BASIC | USB_CHK_SERIAL | USB_CHK_VIDPID, NULL) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_restore_device_state: not the same device"); /* change the device state from suspended to disconnected */ mutex_enter(&hwahcp->hwahc_mutex); hwahcp->hwahc_dev_state = USB_DEV_DISCONNECTED; hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_ERROR_STATE; mutex_exit(&hwahcp->hwahc_mutex); hwahc_pm_idle_component(hwahcp); return; } USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_restore_device_state: Hwahc has been reconnected but" " data may have been lost"); mutex_enter(&hwahcp->hwahc_mutex); /* reinitialize the hw */ hwahcp->hwahc_dev_state = USB_DEV_ONLINE; hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_INIT_STATE; if (hwahcp->hwahc_hw_state == HWAHC_HW_STOPPED) { mutex_exit(&hwahcp->hwahc_mutex); /* no need to start hc */ hwahc_pm_idle_component(hwahcp); USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_restore_device_state: stopped hwa"); return; } rval = wusb_hc_set_cluster_id(&hwahcp->hwahc_hc_data, hwahcp->hwahc_hc_data.hc_cluster_id); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_restore_device_state: set cluster id fails"); goto err; } if (hwahcp->hwahc_hw_state == HWAHC_HW_STARTED) { old_hw_state = hwahcp->hwahc_hw_state; hwahcp->hwahc_hw_state = HWAHC_HW_CH_STOPPED; rval = hwahc_hc_channel_start(hwahcp); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_restore_device_state: start hc fails"); hwahcp->hwahc_hw_state = old_hw_state; goto err; } hwahcp->hwahc_hw_state = old_hw_state; } rval = wusb_hc_set_num_dnts(&hwahcp->hwahc_hc_data, HWAHC_DEFAULT_DNTS_INTERVAL, HWAHC_DEFAULT_DNTS_SLOT_NUM); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_restore_device_state: set num dnts fails"); goto err; } /* set default GTK */ rval = wusb_hc_set_gtk(&hwahcp->hwahc_hc_data, dft_gtk, dft_gtkid); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_restore_device_state: set gtk fails"); goto err; } mutex_exit(&hwahcp->hwahc_mutex); rval = wusb_wa_enable(&hwahcp->hwahc_wa_data, hwahcp->hwahc_default_pipe); mutex_enter(&hwahcp->hwahc_mutex); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_restore_device_state: enable wa fails"); goto err; } /* * This is a workaround, sometimes the ioctl and reconnect will * happen at the sametime, so the ioctl will start nep which makes * the below sart nep fail. Need more work to do to avoid such * issues */ (void) wusb_wa_stop_nep(&hwahcp->hwahc_wa_data); rval = wusb_wa_start_nep(&hwahcp->hwahc_wa_data, USB_FLAGS_SLEEP); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_restore_device_state: start notifep fails rval =%d", rval); mutex_exit(&hwahcp->hwahc_mutex); (void) wusb_wa_disable(&hwahcp->hwahc_wa_data, hwahcp->hwahc_default_pipe); mutex_enter(&hwahcp->hwahc_mutex); goto err; } /* Handle transfer results on bulk-in ep */ rval = hwahc_start_result_thread(hwahcp); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_restore_device_state: start result thread fails"); mutex_exit(&hwahcp->hwahc_mutex); wusb_wa_stop_nep(&hwahcp->hwahc_wa_data); (void) wusb_wa_disable(&hwahcp->hwahc_wa_data, hwahcp->hwahc_default_pipe); mutex_enter(&hwahcp->hwahc_mutex); goto err; } /* if the device had remote wakeup earlier, enable it again */ if (hwahcp->hwahc_pm && hwahcp->hwahc_pm->hwahc_wakeup_enabled) { mutex_exit(&hwahcp->hwahc_mutex); (void) usb_handle_remote_wakeup(hwahcp->hwahc_dip, USB_REMOTE_WAKEUP_ENABLE); mutex_enter(&hwahcp->hwahc_mutex); } hwahcp->hwahc_hw_state = HWAHC_HW_STARTED; hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_OPERATIONAL_STATE; mutex_exit(&hwahcp->hwahc_mutex); hwahc_pm_idle_component(hwahcp); return; err: hwahcp->hwahc_hw_state = HWAHC_HW_STOPPED; mutex_exit(&hwahcp->hwahc_mutex); hwahc_pm_idle_component(hwahcp); } /* * hwahc_cpr_suspend: * Clean up device. * Wait for any IO to finish, then close pipes. * Quiesce device. * due to the dependency on hwarc, the actual suspend operations are * moved to hwahc_pre_suspend_event_cb function. */ static int hwahc_cpr_suspend(dev_info_t *dip) { int instance = ddi_get_instance(dip); hwahc_state_t *hwahcp = ddi_get_soft_state(hwahc_statep, instance); if (hwahcp == NULL) { return (USB_FAILURE); } USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_cpr_suspend: start"); mutex_enter(&hwahcp->hwahc_mutex); /* Don't suspend if the device is open. */ if (hwahcp->hwahc_open_count > 0) { USB_DPRINTF_L2(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_cpr_suspend: Device is open, cannot suspend"); mutex_exit(&hwahcp->hwahc_mutex); return (USB_FAILURE); } mutex_exit(&hwahcp->hwahc_mutex); /* raise power */ hwahc_pm_busy_component(hwahcp); (void) pm_raise_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_FULL_PWR); mutex_enter(&hwahcp->hwahc_mutex); switch (hwahcp->hwahc_dev_state) { case USB_DEV_ONLINE: /* real suspend operations put in pre_suspend function */ /* FALLTHRU */ case USB_DEV_DISCONNECTED: case USB_DEV_PWRED_DOWN: hwahcp->hwahc_dev_state = USB_DEV_SUSPENDED; hwahcp->hwahc_hw_state = HWAHC_HW_CH_SUSPEND; break; case USB_DEV_SUSPENDED: default: USB_DPRINTF_L2(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_cpr_suspend: illegal dev state=%d", hwahcp->hwahc_dev_state); break; } mutex_exit(&hwahcp->hwahc_mutex); hwahc_pm_idle_component(hwahcp); USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_cpr_suspend: end"); return (USB_SUCCESS); } /* * hwahc_cpr_resume: * * hwahc_restore_device_state marks success by putting device back online */ static int hwahc_cpr_resume(dev_info_t *dip) { int instance = ddi_get_instance(dip); hwahc_state_t *hwahcp = ddi_get_soft_state(hwahc_statep, instance); if (hwahcp == NULL) { return (USB_FAILURE); } USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_cpr_resume: hw state = %d, softstate = %d", hwahcp->hwahc_hw_state, hwahcp->hwahc_hc_soft_state); /* * rc is always resumed after hc. restoring hc before rc would fail. * move the restoring operations to hwahc_post_resume_event_cb. */ return (USB_SUCCESS); } /* * hwahc_create_pm_components: * Create power managements components */ static void hwahc_create_pm_components(dev_info_t *dip, hwahc_state_t *hwahcp) { hwahc_power_t *hwahcpm; uint_t pwr_states; USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_create_pm_components: Begin"); /* Allocate the state structure */ hwahcpm = kmem_zalloc(sizeof (hwahc_power_t), KM_SLEEP); hwahcp->hwahc_pm = hwahcpm; hwahcpm->hwahc_state = hwahcp; hwahcpm->hwahc_pm_capabilities = 0; hwahcpm->hwahc_current_power = USB_DEV_OS_FULL_PWR; if (usb_create_pm_components(dip, &pwr_states) == USB_SUCCESS) { USB_DPRINTF_L3(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_create_pm_components: created PM components"); if (usb_handle_remote_wakeup(dip, USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS) { hwahcpm->hwahc_wakeup_enabled = 1; } hwahcpm->hwahc_pwr_states = (uint8_t)pwr_states; /* make device busy till end of attach */ hwahc_pm_busy_component(hwahcp); (void) pm_raise_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_FULL_PWR); } else { USB_DPRINTF_L3(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_create_pm_components: failed"); } USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_create_pm_components: End"); } /* * hwahc_destroy_pm_components: * Shut down and destroy power management and remote wakeup functionality */ static void hwahc_destroy_pm_components(hwahc_state_t *hwahcp) { USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_destroy_pm_components: Begin"); ASSERT(!mutex_owned(&hwahcp->hwahc_mutex)); mutex_enter(&hwahcp->hwahc_mutex); if (hwahcp->hwahc_pm && (hwahcp->hwahc_dev_state != USB_DEV_DISCONNECTED)) { mutex_exit(&hwahcp->hwahc_mutex); hwahc_pm_busy_component(hwahcp); mutex_enter(&hwahcp->hwahc_mutex); if (hwahcp->hwahc_pm->hwahc_wakeup_enabled) { int rval; mutex_exit(&hwahcp->hwahc_mutex); (void) pm_raise_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_FULL_PWR); if ((rval = usb_handle_remote_wakeup( hwahcp->hwahc_dip, USB_REMOTE_WAKEUP_DISABLE)) != USB_SUCCESS) { USB_DPRINTF_L3(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_destroy_pm_components: " "Error disabling rmt wakeup: rval = %d", rval); } } else { mutex_exit(&hwahcp->hwahc_mutex); } /* * Since remote wakeup is disabled now, * no one can raise power and get to device * once power is lowered here. */ (void) pm_lower_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_PWR_OFF); hwahc_pm_idle_component(hwahcp); mutex_enter(&hwahcp->hwahc_mutex); } if (hwahcp->hwahc_pm) { kmem_free(hwahcp->hwahc_pm, sizeof (hwahc_power_t)); hwahcp->hwahc_pm = NULL; } mutex_exit(&hwahcp->hwahc_mutex); USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_destroy_pm_components: End"); } /* mark component busy */ static void hwahc_pm_busy_component(hwahc_state_t *hwahcp) { ASSERT(!mutex_owned(&hwahcp->hwahc_mutex)); if (hwahcp->hwahc_pm != NULL) { mutex_enter(&hwahcp->hwahc_mutex); hwahcp->hwahc_pm->hwahc_pm_busy++; USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_pm_busy_component: %d", hwahcp->hwahc_pm->hwahc_pm_busy); mutex_exit(&hwahcp->hwahc_mutex); if (pm_busy_component(hwahcp->hwahc_dip, 0) != DDI_SUCCESS) { mutex_enter(&hwahcp->hwahc_mutex); hwahcp->hwahc_pm->hwahc_pm_busy--; USB_DPRINTF_L2(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_pm_busy_component failed: %d", hwahcp->hwahc_pm->hwahc_pm_busy); mutex_exit(&hwahcp->hwahc_mutex); } } } /* mark component idle */ static void hwahc_pm_idle_component(hwahc_state_t *hwahcp) { ASSERT(!mutex_owned(&hwahcp->hwahc_mutex)); if (hwahcp->hwahc_pm != NULL) { if (pm_idle_component(hwahcp->hwahc_dip, 0) == DDI_SUCCESS) { mutex_enter(&hwahcp->hwahc_mutex); ASSERT(hwahcp->hwahc_pm->hwahc_pm_busy > 0); hwahcp->hwahc_pm->hwahc_pm_busy--; USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_pm_idle_component: %d", hwahcp->hwahc_pm->hwahc_pm_busy); mutex_exit(&hwahcp->hwahc_mutex); } } } /* * hwahc_power : * Power entry point, the workhorse behind pm_raise_power, pm_lower_power, * usb_req_raise_power and usb_req_lower_power. */ /* ARGSUSED */ static int hwahc_power(dev_info_t *dip, int comp, int level) { hwahc_state_t *hwahcp; hwahc_power_t *pm; int rval = USB_FAILURE; hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip)); if (hwahcp == NULL) { return (DDI_FAILURE); } USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_power: dip = 0x%p", (void *)dip); mutex_enter(&hwahcp->hwahc_mutex); if (hwahcp->hwahc_pm == NULL) { goto done; } pm = hwahcp->hwahc_pm; /* Check if we are transitioning to a legal power level */ if (USB_DEV_PWRSTATE_OK(pm->hwahc_pwr_states, level)) { USB_DPRINTF_L2(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_power: illegal power level = %d " "pwr_states: %x", level, pm->hwahc_pwr_states); goto done; } switch (level) { case USB_DEV_OS_PWR_OFF : rval = hwahc_pwrlvl0(hwahcp); break; case USB_DEV_OS_PWR_1: rval = hwahc_pwrlvl1(hwahcp); break; case USB_DEV_OS_PWR_2: rval = hwahc_pwrlvl2(hwahcp); break; case USB_DEV_OS_FULL_PWR : rval = hwahc_pwrlvl3(hwahcp); break; } done: mutex_exit(&hwahcp->hwahc_mutex); return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE); } /* * hwahc_pwrlvl0: * Functions to handle power transition for OS levels 0 -> 3 * OS 0 <--> USB D3, no or minimal power */ static int hwahc_pwrlvl0(hwahc_state_t *hwahcp) { int rval; USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_pwrlvl0: %d", hwahcp->hwahc_pm->hwahc_pm_busy); switch (hwahcp->hwahc_dev_state) { case USB_DEV_ONLINE: /* Deny the powerdown request if the device is busy */ if (hwahcp->hwahc_pm->hwahc_pm_busy != 0) { USB_DPRINTF_L2(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_pwrlvl0: hwahc_pm is busy"); return (USB_FAILURE); } /* * only when final_stop gets called, we allow the system * to do PM on us. At this moment, we don't need to do * more operations other than those in final_stop. */ /* Issue USB D3 command to the device here */ rval = usb_set_device_pwrlvl3(hwahcp->hwahc_dip); ASSERT(rval == USB_SUCCESS); hwahcp->hwahc_dev_state = USB_DEV_PWRED_DOWN; hwahcp->hwahc_pm->hwahc_current_power = USB_DEV_OS_PWR_OFF; break; case USB_DEV_DISCONNECTED: case USB_DEV_SUSPENDED: case USB_DEV_PWRED_DOWN: default: break; } return (USB_SUCCESS); } /* * hwahc_pwrlvl1: * Functions to handle power transition to OS levels -> 2 * OS level 1 <--> D2 */ static int hwahc_pwrlvl1(hwahc_state_t *hwahcp) { int rval; USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_pwrlvl1:"); /* Issue USB D2 command to the device here */ rval = usb_set_device_pwrlvl2(hwahcp->hwahc_dip); ASSERT(rval == USB_SUCCESS); return (USB_FAILURE); } /* * hwahc_pwrlvl2: * Functions to handle power transition to OS levels -> 1 * OS leve 2 <--> D1 */ static int hwahc_pwrlvl2(hwahc_state_t *hwahcp) { int rval; USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_pwrlvl2:"); /* Issue USB D1 command to the device here */ rval = usb_set_device_pwrlvl1(hwahcp->hwahc_dip); ASSERT(rval == USB_SUCCESS); return (USB_FAILURE); } /* * hwahc_pwrlvl3: * Functions to handle power transition to OS level -> 0 * OS level 3 <--> D0 (full power) */ static int hwahc_pwrlvl3(hwahc_state_t *hwahcp) { USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_pwrlvl3: %d", hwahcp->hwahc_pm->hwahc_pm_busy); ASSERT(mutex_owned(&hwahcp->hwahc_mutex)); switch (hwahcp->hwahc_dev_state) { case USB_DEV_PWRED_DOWN: /* Issue USB D0 command to the device here */ (void) usb_set_device_pwrlvl0(hwahcp->hwahc_dip); /* * Due to our current PM policy, it's not possible * for hwa to be in USB_DEV_PWRED_DOWN between * initial_start and final_stop. If it's PWRED_DOWN, * it should not start. We don't need to resume * soft or hardware state in this case. */ if (hwahcp->hwahc_hw_state == HWAHC_HW_STOPPED) { /* no need to start hc */ hwahcp->hwahc_dev_state = USB_DEV_ONLINE; hwahcp->hwahc_pm->hwahc_current_power = USB_DEV_OS_FULL_PWR; return (USB_SUCCESS); } hwahcp->hwahc_pm->hwahc_current_power = USB_DEV_OS_FULL_PWR; /* FALLTHRU */ case USB_DEV_ONLINE: /* we are already in full power */ /* FALLTHRU */ case USB_DEV_DISCONNECTED: case USB_DEV_SUSPENDED: /* * PM framework tries to put you in full power * during system shutdown. If we are disconnected * return success. Also, we should not change state * when we are disconnected or suspended or about to * transition to that state */ return (USB_SUCCESS); default: USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_pwrlvl3: illegal dev_state=%d", hwahcp->hwahc_dev_state); return (USB_FAILURE); } } /* * Host power management: stop channel * See Section 4.16.2.1 for details * See Section 8.1.0 for HWA suspend/resume */ static int hwahc_hc_channel_suspend(hwahc_state_t *hwahcp) { int rval; USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_hc_channel_suspend:"); ASSERT(mutex_owned(&hwahcp->hwahc_mutex)); /* no need to suspend if host hw was not started */ if (hwahcp->hwahc_hw_state != HWAHC_HW_STARTED) { USB_DPRINTF_L3(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_hc_channel_suspend: hw already stopped"); return (USB_SUCCESS); } if (hwahcp->hwahc_hw_state == HWAHC_HW_CH_SUSPEND) { USB_DPRINTF_L3(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_hc_channel_suspend: already suspended"); return (USB_SUCCESS); } mutex_exit(&hwahcp->hwahc_mutex); /* suspend host, refer to WUSB 1.0 spec 8.5.3.14 */ rval = wusb_hc_stop_ch(&hwahcp->hwahc_hc_data, 10000); /* 10ms */ mutex_enter(&hwahcp->hwahc_mutex); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_PM, hwahcp->hwahc_log_handle, "hwahc_hc_channel_suspend: wusb channel stop fails"); return (rval); } hwahcp->hwahc_hw_state = HWAHC_HW_CH_SUSPEND; return (USB_SUCCESS); } /* * Parse security descriptors, see T.8-43 * put result in secrt_data */ static int hwahc_parse_security_data(wusb_secrt_data_t *secrt_data, usb_cfg_data_t *cfg_data) { int i, j; usb_cvs_data_t *cvs_data; size_t count, len; if ((secrt_data == NULL) || (cfg_data == NULL)) { return (USB_INVALID_ARGS); } for (i = 0; i < cfg_data->cfg_n_cvs; i++) { cvs_data = &cfg_data->cfg_cvs[i]; if (cvs_data == NULL) { continue; } if (cvs_data->cvs_buf[1] == USB_DESCR_TYPE_SECURITY) { count = usb_parse_data("ccsc", cvs_data->cvs_buf, cvs_data->cvs_buf_len, (void *)&secrt_data->secrt_descr, (size_t)USB_SECURITY_DESCR_SIZE); if (count != USB_SECURITY_DESCR_SIZE) { return (USB_FAILURE); } else { secrt_data->secrt_n_encry = secrt_data->secrt_descr.bNumEncryptionTypes; len = sizeof (usb_encryption_descr_t) * secrt_data->secrt_n_encry; secrt_data->secrt_encry_descr = (usb_encryption_descr_t *)kmem_alloc(len, KM_SLEEP); for (j = 0; j < secrt_data->secrt_n_encry; j++) { cvs_data = &cfg_data->cfg_cvs[i + j + 1]; if (cvs_data->cvs_buf[1] != USB_DESCR_TYPE_ENCRYPTION) { kmem_free(secrt_data-> secrt_encry_descr, len); return (USB_FAILURE); } /* Table 7-34 */ count = usb_parse_data("ccccc", cvs_data->cvs_buf, cvs_data->cvs_buf_len, (void *)&secrt_data-> secrt_encry_descr[j], USB_ENCRYPTION_DESCR_SIZE); if (count != USB_ENCRYPTION_DESCR_SIZE) { kmem_free(secrt_data-> secrt_encry_descr, len); return (USB_FAILURE); } } return (USB_SUCCESS); } } } return (USB_FAILURE); } /* initialize wusb_hc_data_t structure */ static void hwahc_hc_data_init(hwahc_state_t *hwahcp) { wusb_hc_data_t *hc_data = &hwahcp->hwahc_hc_data; hc_data->hc_dip = hwahcp->hwahc_dip; hc_data->hc_private_data = (void *)hwahcp; (void) memset(hc_data->hc_chid, 0, sizeof (hc_data->hc_chid)); hc_data->hc_num_mmcies = hwahcp->hwahc_wa_data.wa_descr.bNumMMCIEs; ASSERT(hc_data->hc_num_mmcies != 0); hc_data->hc_mmcie_list = kmem_zalloc((hc_data->hc_num_mmcies * sizeof (wusb_ie_header_t *)), KM_SLEEP); /* initialize frequently used IE */ hc_data->hc_alive_ie.bIEIdentifier = WUSB_IE_DEV_KEEPALIVE; /* register callbacks */ hc_data->disconnect_dev = hwahc_disconnect_dev; hc_data->reconnect_dev = hwahc_reconnect_dev; hc_data->create_child = hwahc_create_child; hc_data->destroy_child = hwahc_destroy_child; /* HWA HC operation functions */ hc_data->set_encrypt = hwahc_set_encrypt; hc_data->set_ptk = hwahc_set_ptk; hc_data->set_gtk = hwahc_set_gtk; hc_data->set_device_info = hwahc_set_device_info; hc_data->set_cluster_id = hwahc_set_cluster_id; hc_data->set_stream_idx = hwahc_set_stream_idx; hc_data->set_wusb_mas = hwahc_set_wusb_mas; hc_data->add_mmc_ie = hwahc_add_mmc_ie; hc_data->rem_mmc_ie = hwahc_remove_mmc_ie; hc_data->stop_ch = hwahc_stop_ch; hc_data->set_num_dnts = hwahc_set_num_dnts; hc_data->get_time = hwahc_get_time; hc_data->hc_num_ports = hwahcp->hwahc_wa_data.wa_descr.bNumPorts; hc_data->hc_cd_list_length = (sizeof (dev_info_t **)) * (hc_data->hc_num_ports + 1); hc_data->hc_children_dips = (dev_info_t **)kmem_zalloc( hc_data->hc_cd_list_length, KM_SLEEP); hc_data->hc_usba_devices = (usba_device_t **)kmem_zalloc( hc_data->hc_cd_list_length, KM_SLEEP); hc_data->hc_dev_infos = (wusb_dev_info_t **)kmem_zalloc( hc_data->hc_cd_list_length, KM_SLEEP); mutex_init(&hc_data->hc_mutex, NULL, MUTEX_DRIVER, NULL); } /* deinitialize wusb_hc_data_t structure */ static void hwahc_hc_data_fini(hwahc_state_t *hwahcp) { int i; wusb_hc_data_t *hc_data = &hwahcp->hwahc_hc_data; wusb_ie_header_t *hdr; #ifdef DEBUG usb_port_t port; #endif if (hc_data->hc_mmcie_list) { /* Free all recorded IEs except statically allocated IEs */ for (i = 0; i < hc_data->hc_num_mmcies; i++) { if (hc_data->hc_mmcie_list[i] != NULL) { hdr = hc_data->hc_mmcie_list[i]; if ((hdr->bIEIdentifier != WUSB_IE_DEV_KEEPALIVE)) { kmem_free(hdr, hdr->bLength); } hc_data->hc_mmcie_list[i] = NULL; } } kmem_free(hc_data->hc_mmcie_list, hc_data->hc_num_mmcies * sizeof (wusb_ie_header_t *)); } if (hc_data->hc_cluster_id) { wusb_hc_free_cluster_id(hc_data->hc_cluster_id); } if (hc_data->hc_cc_list) { wusb_hc_free_cc_list(hc_data->hc_cc_list); } #ifdef DEBUG for (port = 1; port <= hc_data->hc_num_ports; port++) { ASSERT(hc_data->hc_usba_devices[port] == NULL); ASSERT(hc_data->hc_children_dips[port] == NULL); ASSERT(hc_data->hc_dev_infos[port] == NULL); } #endif kmem_free(hc_data->hc_children_dips, hc_data->hc_cd_list_length); kmem_free(hc_data->hc_usba_devices, hc_data->hc_cd_list_length); kmem_free(hc_data->hc_dev_infos, hc_data->hc_cd_list_length); mutex_destroy(&hc_data->hc_mutex); } /* fully start the HWA hw */ static int hwahc_hc_initial_start(hwahc_state_t *hwahcp) { uint8_t stream_idx; uint8_t mas[WUSB_SET_WUSB_MAS_LEN]; int rval; uint8_t cluster_id = 0; USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start:"); ASSERT(mutex_owned(&hwahcp->hwahc_mutex)); if (hwahcp->hwahc_dev_state != USB_DEV_ONLINE) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: invalid dev state = %d", hwahcp->hwahc_dev_state); return (USB_INVALID_REQUEST); } if (hwahcp->hwahc_hw_state != HWAHC_HW_STOPPED) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: invalid hw state"); return (USB_INVALID_REQUEST); } /* * start beacon of radio layer * We're not sure if previouse channel is occupied or not. So, let * UWB allocates a free channel for this hwa. Then we can start * beacon. */ hwahcp->hwahc_hc_data.hc_channel = uwb_allocate_channel(hwahcp->hwahc_dip); if (hwahcp->hwahc_hc_data.hc_channel == 0) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "wusb_hc_initial_start: channel = %d", hwahcp->hwahc_hc_data.hc_channel); return (USB_FAILURE); } if ((rval = uwb_start_beacon(hwahcp->hwahc_dip, hwahcp->hwahc_hc_data.hc_channel)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "wusb_hc_initial_start: start uwb beacon failed"); return (rval); } mutex_exit(&hwahcp->hwahc_mutex); /* reset wire adapter */ rval = wusb_wa_reset(&hwahcp->hwahc_wa_data, hwahcp->hwahc_default_pipe); mutex_enter(&hwahcp->hwahc_mutex); if (rval != SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: reset wa fails"); goto err; } /* reuse the old cluster id or assign one */ if (hwahcp->hwahc_hc_data.hc_cluster_id) { cluster_id = hwahcp->hwahc_hc_data.hc_cluster_id; } else { cluster_id = wusb_hc_get_cluster_id(); if (cluster_id == 0) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: cannot get cluster id"); rval = USB_NO_RESOURCES; goto err; } } mutex_exit(&hwahcp->hwahc_mutex); /* set cluster id for the wusb channel */ rval = wusb_hc_set_cluster_id(&hwahcp->hwahc_hc_data, cluster_id); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: set cluster id %d fails", cluster_id); mutex_enter(&hwahcp->hwahc_mutex); goto err; } /* UWB should be responsible for assigning stream index */ stream_idx = 1; rval = wusb_hc_set_stream_idx(&hwahcp->hwahc_hc_data, stream_idx); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: set stream idx %d fails", stream_idx); mutex_enter(&hwahcp->hwahc_mutex); goto err; } /* set dnts slot */ rval = wusb_hc_set_num_dnts(&hwahcp->hwahc_hc_data, HWAHC_DEFAULT_DNTS_INTERVAL, HWAHC_DEFAULT_DNTS_SLOT_NUM); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: set num dnts fails"); mutex_enter(&hwahcp->hwahc_mutex); goto err; } /* set host info IE */ rval = wusb_hc_add_host_info(&hwahcp->hwahc_hc_data, stream_idx); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: add hostinfo ie fails"); mutex_enter(&hwahcp->hwahc_mutex); goto err; } /* reserve MAS slots for the host, need a way to assign */ (void) memset(mas, 0xff, WUSB_SET_WUSB_MAS_LEN); mas[0] = 0xf0; /* the first 4 slots are for beacons */ rval = wusb_hc_set_wusb_mas(&hwahcp->hwahc_hc_data, mas); mutex_enter(&hwahcp->hwahc_mutex); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: set wusb mas fails"); goto err; } /* record the available MAS slots */ (void) memcpy(hwahcp->hwahc_hc_data.hc_mas, mas, WUSB_SET_WUSB_MAS_LEN); /* Set initial GTK/TKID to random values */ (void) random_get_pseudo_bytes(dft_gtk, 16); (void) random_get_pseudo_bytes(dft_gtkid, 3); /* set default GTK, need a way to dynamically compute it */ mutex_exit(&hwahcp->hwahc_mutex); rval = wusb_hc_set_gtk(&hwahcp->hwahc_hc_data, dft_gtk, dft_gtkid); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: set gtk fails"); mutex_enter(&hwahcp->hwahc_mutex); goto err; } /* enable wire adapter */ rval = wusb_wa_enable(&hwahcp->hwahc_wa_data, hwahcp->hwahc_default_pipe); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: enable wa fails"); mutex_enter(&hwahcp->hwahc_mutex); goto err; } /* Start Notification endpoint */ rval = wusb_wa_start_nep(&hwahcp->hwahc_wa_data, USB_FLAGS_SLEEP); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: start notification ep fails"); (void) wusb_wa_disable(&hwahcp->hwahc_wa_data, hwahcp->hwahc_default_pipe); mutex_enter(&hwahcp->hwahc_mutex); goto err; } mutex_enter(&hwahcp->hwahc_mutex); /* * Handle transfer results on bulk-in ep * The bulk-in ep needs to be polled no matter the completion * notification is received or not to avoid miss result. */ rval = hwahc_start_result_thread(hwahcp); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: start result thread fails, " "rval = %d", rval); mutex_exit(&hwahcp->hwahc_mutex); wusb_wa_stop_nep(&hwahcp->hwahc_wa_data); (void) wusb_wa_disable(&hwahcp->hwahc_wa_data, hwahcp->hwahc_default_pipe); mutex_enter(&hwahcp->hwahc_mutex); goto err; } USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_initial_start: start result thread success"); hwahcp->hwahc_hw_state = HWAHC_HW_STARTED; hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_OPERATIONAL_STATE; /* Don't do PM on an active beacon hwa until explicitly stopped */ mutex_exit(&hwahcp->hwahc_mutex); hwahc_pm_busy_component(hwahcp); mutex_enter(&hwahcp->hwahc_mutex); return (USB_SUCCESS); err: if (cluster_id != 0) { wusb_hc_free_cluster_id(cluster_id); } mutex_exit(&hwahcp->hwahc_mutex); (void) uwb_stop_beacon(hwahcp->hwahc_dip); mutex_enter(&hwahcp->hwahc_mutex); return (rval); } /* entirely stop the HWA from working */ static int hwahc_hc_final_stop(hwahc_state_t *hwahcp) { USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_final_stop:"); ASSERT(mutex_owned(&hwahcp->hwahc_mutex)); if (hwahcp->hwahc_hw_state == HWAHC_HW_STOPPED) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_final_stop: already stopped"); return (USB_SUCCESS); } if (hwahcp->hwahc_dev_state == USB_DEV_SUSPENDED) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_final_stop: invalid dev state = %d", hwahcp->hwahc_dev_state); return (USB_INVALID_REQUEST); } /* might have been powered down before detaching */ mutex_exit(&hwahcp->hwahc_mutex); (void) pm_raise_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_FULL_PWR); mutex_enter(&hwahcp->hwahc_mutex); if (hwahcp->hwahc_dev_state != USB_DEV_DISCONNECTED) { /* notify children the host is going to stop */ (void) hwahc_hc_channel_suspend(hwahcp); /* release mutex here to avoid deadlock with exc_cb */ mutex_exit(&hwahcp->hwahc_mutex); /* stop notification endpoint */ wusb_wa_stop_nep(&hwahcp->hwahc_wa_data); mutex_enter(&hwahcp->hwahc_mutex); /* stop bulk-in ept from listening result */ hwahc_stop_result_thread(hwahcp); /* drain the device notifications */ hwahc_drain_notif_queue(hwahcp); /* disable wire adapter */ mutex_exit(&hwahcp->hwahc_mutex); (void) wusb_wa_disable(&hwahcp->hwahc_wa_data, hwahcp->hwahc_default_pipe); /* stop beaconing. Not necessary to unreserve mas */ (void) uwb_stop_beacon(hwahcp->hwahc_dip); wusb_hc_rem_host_info(&hwahcp->hwahc_hc_data); /* Manually remove all connected children */ hwahc_run_callbacks(hwahcp, USBA_EVENT_TAG_HOT_REMOVAL); /* delete all the children */ (void) hwahc_cleanup_child(hwahcp->hwahc_dip); mutex_enter(&hwahcp->hwahc_mutex); } /* * we make it busy at hwahc_hc_initial_start(). This idle operation * is to match that busy operation. * All other busy/idle operations should have been matched. */ if ((hwahcp->hwahc_hw_state == HWAHC_HW_STARTED) && (hwahcp->hwahc_hc_soft_state == HWAHC_CTRL_OPERATIONAL_STATE)) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_final_stop: pm_busy=%d", hwahcp->hwahc_pm->hwahc_pm_busy); mutex_exit(&hwahcp->hwahc_mutex); hwahc_pm_idle_component(hwahcp); mutex_enter(&hwahcp->hwahc_mutex); } hwahcp->hwahc_hw_state = HWAHC_HW_STOPPED; if (hwahcp->hwahc_hc_soft_state == HWAHC_CTRL_OPERATIONAL_STATE) { hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_INIT_STATE; } return (USB_SUCCESS); } /* * init WUSB channel, this is only part of the full hw start operations * including setting wusb channel stream idx, wusb MAS slots reservation * and adding host info IE */ static int hwahc_hc_channel_start(hwahc_state_t *hwahcp) { uint8_t stream_idx; uint8_t mas[WUSB_SET_WUSB_MAS_LEN]; int rval; USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_channel_start:"); ASSERT(mutex_owned(&hwahcp->hwahc_mutex)); if (hwahcp->hwahc_dev_state != USB_DEV_ONLINE) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_channel_start: invalid dev_state = %d", hwahcp->hwahc_dev_state); return (USB_INVALID_REQUEST); } if (hwahcp->hwahc_hw_state != HWAHC_HW_CH_STOPPED) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_channel_start: invalid hw state"); return (USB_INVALID_REQUEST); } /* set stream idx */ stream_idx = 1; mutex_exit(&hwahcp->hwahc_mutex); rval = wusb_hc_set_stream_idx(&hwahcp->hwahc_hc_data, stream_idx); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_channel_start: set stream idx %d fails", stream_idx); mutex_enter(&hwahcp->hwahc_mutex); return (rval); } /* reserve MAS slots for the host. Should be allocated by UWB */ (void) memset(mas, 0xff, WUSB_SET_WUSB_MAS_LEN); mas[0] = 0xf0; /* for beacons */ rval = wusb_hc_set_wusb_mas(&hwahcp->hwahc_hc_data, mas); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_channel_start: set wusb mas fails"); mutex_enter(&hwahcp->hwahc_mutex); return (rval); } (void) memcpy(hwahcp->hwahc_hc_data.hc_mas, mas, WUSB_SET_WUSB_MAS_LEN); /* set host info IE */ rval = wusb_hc_add_host_info(&hwahcp->hwahc_hc_data, stream_idx); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_channel_start: add hostinfo ie fails"); mutex_enter(&hwahcp->hwahc_mutex); return (rval); } mutex_enter(&hwahcp->hwahc_mutex); hwahcp->hwahc_hw_state = HWAHC_HW_STARTED; hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_OPERATIONAL_STATE; /* do not PM this device, once we're ready to accept DN */ mutex_exit(&hwahcp->hwahc_mutex); hwahc_pm_busy_component(hwahcp); mutex_enter(&hwahcp->hwahc_mutex); return (USB_SUCCESS); } /* * stop WUSB channel, this only stops part of the hw function * it mainly unreserve the MAS slots and remove the host info IE */ static int hwahc_hc_channel_stop(hwahc_state_t *hwahcp) { uint8_t stream_idx; uint8_t mas[WUSB_SET_WUSB_MAS_LEN]; int rval; USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_channel_stop:"); ASSERT(mutex_owned(&hwahcp->hwahc_mutex)); if (hwahcp->hwahc_dev_state != USB_DEV_ONLINE) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_channel_stop: invalid dev state %d", hwahcp->hwahc_dev_state); return (USB_INVALID_REQUEST); } if (hwahcp->hwahc_hw_state == HWAHC_HW_CH_STOPPED) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_channel_stop: already partially stopped"); return (USB_SUCCESS); } if (hwahcp->hwahc_hw_state == HWAHC_HW_STOPPED) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_channel_stop: already stopped, invalid state"); return (USB_INVALID_REQUEST); } /* send host disconect IE so that the children know to disconnect */ mutex_exit(&hwahcp->hwahc_mutex); rval = wusb_hc_send_host_disconnect(&hwahcp->hwahc_hc_data); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_channel_stop: send host disconnect ie fails"); mutex_enter(&hwahcp->hwahc_mutex); return (rval); } /* remove host info IE */ wusb_hc_rem_host_info(&hwahcp->hwahc_hc_data); /* unset stream idx */ stream_idx = 0; rval = wusb_hc_set_stream_idx(&hwahcp->hwahc_hc_data, stream_idx); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_channel_stop: set stream idx 0 fails"); mutex_enter(&hwahcp->hwahc_mutex); return (rval); } /* unreserve MAS slots */ (void) memset(mas, 0, WUSB_SET_WUSB_MAS_LEN); rval = wusb_hc_set_wusb_mas(&hwahcp->hwahc_hc_data, mas); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hc_channel_stop: set null wusb mas fails"); mutex_enter(&hwahcp->hwahc_mutex); return (rval); } mutex_enter(&hwahcp->hwahc_mutex); (void) memcpy(hwahcp->hwahc_hc_data.hc_mas, mas, WUSB_SET_WUSB_MAS_LEN); hwahcp->hwahc_hw_state = HWAHC_HW_CH_STOPPED; /* Channel is stopped, can be PM'ed */ mutex_exit(&hwahcp->hwahc_mutex); hwahc_pm_idle_component(hwahcp); mutex_enter(&hwahcp->hwahc_mutex); return (USB_SUCCESS); } /* initialize data transfer related resources */ static int hwahc_wa_start(hwahc_state_t *hwahcp) { int rval; USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_wa_start:"); /* get all rpipe descrs */ if ((rval = wusb_wa_get_rpipe_descrs(&hwahcp->hwahc_wa_data, hwahcp->hwahc_default_pipe, PRINT_MASK_ATTA, hwahcp->hwahc_log_handle)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_wa_start: get rpipe descrs fails, rval=%d", rval); return (rval); } /* open all data transfer epts */ if ((rval = wusb_wa_open_pipes(&hwahcp->hwahc_wa_data)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_wa_start: open pipes fails, rval=%d", rval); (void) wusb_wa_disable(&hwahcp->hwahc_wa_data, hwahcp->hwahc_default_pipe); return (rval); } /* init notification list */ usba_init_list(&hwahcp->hwahc_dn_notif_queue, NULL, hwahcp->hwahc_dev_data->dev_iblock_cookie); return (USB_SUCCESS); } /* deinitialize data transfer related resources */ static void hwahc_wa_stop(hwahc_state_t *hwahcp) { USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_wa_stop:"); usba_destroy_list(&hwahcp->hwahc_dn_notif_queue); wusb_wa_close_pipes(&hwahcp->hwahc_wa_data); } /* * HUBD related initialization * To mimic standard hub attach process to create a fake "root hub" * for HWA */ static int hwahc_hub_attach(hwahc_state_t *hwahcp) { hubd_t *hubd = NULL; dev_info_t *dip = hwahcp->hwahc_dip; int instance = ddi_get_instance(dip); int i; int rval; USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hub_attach:"); if (ndi_prop_create_boolean(DDI_DEV_T_NONE, dip, "wire-adapter") != NDI_SUCCESS) { return (USB_FAILURE); } /* allocate hubd structure */ hubd = hwahcp->hwahc_hubd = kmem_zalloc(sizeof (hubd_t), KM_SLEEP); hubd->h_log_handle = usb_alloc_log_hdl(dip, "husb", &hubd_errlevel, &hubd_errmask, &hubd_instance_debug, 0); hubd->h_usba_device = usba_get_usba_device(dip); hubd->h_usba_device->usb_is_wa = TRUE; hubd->h_dip = dip; hubd->h_instance = instance; hubd->h_ignore_pwr_budget = B_TRUE; hubd->h_cleanup_child = hwahc_cleanup_child; mutex_enter(&hubd->h_usba_device->usb_mutex); hubd->h_usba_device->usb_root_hubd = hubd; mutex_exit(&hubd->h_usba_device->usb_mutex); if (usb_get_dev_data(dip, &hubd->h_dev_data, USB_PARSE_LVL_IF, 0) != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_MASK_ATTA, hubd->h_log_handle, "cannot get dev_data"); goto fail; } /* init hubd mutex */ mutex_init(HUBD_MUTEX(hubd), NULL, MUTEX_DRIVER, hubd->h_dev_data->dev_iblock_cookie); usb_free_descr_tree(dip, hubd->h_dev_data); hubd->h_init_state |= HUBD_LOCKS_DONE; /* register the instance to usba HUBDI */ rval = usba_hubdi_register(dip, 0); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_MASK_ATTA, hubd->h_log_handle, "usba_hubdi_register failed"); goto fail; } mutex_enter(HUBD_MUTEX(hubd)); hubd->h_init_state |= HUBD_HUBDI_REGISTERED; hubd->h_ancestry_str = (char *)kmem_zalloc(HUBD_APID_NAMELEN, KM_SLEEP); hubd_get_ancestry_str(hubd); /* create cfgadm minor nodes */ for (i = 1; i <= hwahcp->hwahc_wa_data.wa_descr.bNumPorts; i++) { char ap_name[HUBD_APID_NAMELEN]; (void) snprintf(ap_name, HUBD_APID_NAMELEN, "%s%d", hubd->h_ancestry_str, i); USB_DPRINTF_L3(DPRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "ap_name=%s", ap_name); if (ddi_create_minor_node(dip, ap_name, S_IFCHR, (instance << HWAHC_MINOR_INSTANCE_SHIFT) | i, DDI_NT_USB_ATTACHMENT_POINT, 0) != DDI_SUCCESS) { USB_DPRINTF_L2(DPRINT_MASK_ATTA, hubd->h_log_handle, "cannot create attachment point node (%d)", instance); mutex_exit(HUBD_MUTEX(hubd)); goto fail; } } i = hwahcp->hwahc_wa_data.wa_descr.bNumPorts; mutex_exit(HUBD_MUTEX(hubd)); /* create hubd minor node */ if (ddi_create_minor_node(dip, "hubd", S_IFCHR, instance << HWAHC_MINOR_INSTANCE_SHIFT, DDI_NT_NEXUS, 0) != DDI_SUCCESS) { USB_DPRINTF_L2(DPRINT_MASK_ATTA, hubd->h_log_handle, "cannot create devctl minor node (%d)", instance); goto fail; } mutex_enter(HUBD_MUTEX(hubd)); hubd->h_init_state |= HUBD_MINOR_NODE_CREATED; mutex_exit(HUBD_MUTEX(hubd)); if (ndi_prop_update_int(DDI_DEV_T_NONE, dip, "usb-port-count", i) != DDI_PROP_SUCCESS) { USB_DPRINTF_L2(DPRINT_MASK_ATTA, hubd->h_log_handle, "usb-port-count update failed"); } return (USB_SUCCESS); fail: if (hwahc_hub_detach(hwahcp) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "fail to cleanup after hub attach failure"); } return (USB_FAILURE); } /* HUBD related deinitialization */ static int hwahc_hub_detach(hwahc_state_t *hwahcp) { hubd_t *hubd = hwahcp->hwahc_hubd; dev_info_t *dip = hwahcp->hwahc_dip; USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_hub_detach:"); if ((hubd->h_init_state & HUBD_LOCKS_DONE) == 0) { goto done; } if (hubd->h_init_state & HUBD_MINOR_NODE_CREATED) { /* remove minor nodes */ ddi_remove_minor_node(dip, NULL); } if (hubd->h_init_state & HUBD_HUBDI_REGISTERED) { /* unregister with usba HUBDI */ (void) usba_hubdi_unregister(dip); } if (hubd->h_init_state & HUBD_LOCKS_DONE) { mutex_destroy(HUBD_MUTEX(hubd)); } if (hubd->h_ancestry_str) { kmem_free(hubd->h_ancestry_str, HUBD_APID_NAMELEN); } done: if (hubd->h_dev_data) { /* unregister client from usba */ usb_client_detach(dip, hubd->h_dev_data); } usb_free_log_hdl(hubd->h_log_handle); kmem_free(hubd, sizeof (hubd_t)); ddi_prop_remove_all(dip); return (USB_SUCCESS); } /* print security descrs */ static void hwahc_print_secrt_data(hwahc_state_t *hwahcp) { int i; wusb_secrt_data_t *secrt_data = &hwahcp->hwahc_secrt_data; USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "The Host Wire Adapter security descriptor:"); USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "bLength = 0x%x\t\t bDescriptorType = 0x%x", secrt_data->secrt_descr.bLength, secrt_data->secrt_descr.bDescriptorType); USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "wTotalLength = 0x%x\t bNumEncryptionTypes = 0x%x", secrt_data->secrt_descr.wTotalLength, secrt_data->secrt_descr.bNumEncryptionTypes); for (i = 0; i < secrt_data->secrt_n_encry; i++) { USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "The Host Wire Adapter encryption descriptor %d:", i + 1); USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "bLength = 0x%x\t\t bDescriptorType = 0x%x", secrt_data->secrt_encry_descr[i].bLength, secrt_data->secrt_encry_descr[i].bDescriptorType); USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "bEncryptionType = 0x%x\t bEncryptionValue = 0x%x", secrt_data->secrt_encry_descr[i].bEncryptionType, secrt_data->secrt_encry_descr[i].bEncryptionValue); USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "bAuthKeyIndex = 0x%x", secrt_data->secrt_encry_descr[i].bAuthKeyIndex); } } /* drain device notifications */ static void hwahc_drain_notif_queue(hwahc_state_t *hwahcp) { int i; ASSERT(mutex_owned(&hwahcp->hwahc_mutex)); USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_drain_notif_queue: started"); if ((hwahcp->hwahc_notif_thread_id == NULL) && (usba_list_entry_count(&hwahcp->hwahc_dn_notif_queue) != 0)) { /* kick off a notif thread to drain the queue */ if (usb_async_req(hwahcp->hwahc_dip, hwahc_notif_thread, (void *)hwahcp, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_drain_notif_queue: no notif thread started"); } else { hwahcp->hwahc_notif_thread_id = (kthread_t *)1; } } for (i = 0; i < HWAHC_NOTIF_DRAIN_TIMEOUT; i++) { /* loop until the queue is completed or it timeouts */ if ((hwahcp->hwahc_notif_thread_id == NULL) && (usba_list_entry_count(&hwahcp->hwahc_dn_notif_queue) == 0)) { break; } mutex_exit(&hwahcp->hwahc_mutex); delay(drv_usectohz(1000000)); mutex_enter(&hwahcp->hwahc_mutex); } /* cleanup the queue if not completed */ while (usba_list_entry_count(&hwahcp->hwahc_dn_notif_queue) != 0) { hwahc_dn_notif_list_t *nlist; nlist = (hwahc_dn_notif_list_t *)usba_rm_first_pvt_from_list( &hwahcp->hwahc_dn_notif_queue); ASSERT(nlist != NULL); ASSERT(nlist->dn_notif != NULL); usba_destroy_list(&nlist->notif_list); kmem_free(nlist->dn_notif, nlist->dn_notif->bLength); kmem_free(nlist, sizeof (hwahc_dn_notif_list_t)); } USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_drain_notif_queue: ended"); } /* normal callback for notification ept */ static void hwahc_intr_cb(usb_pipe_handle_t ph, struct usb_intr_req *reqp) { dev_info_t *dip = (USBA_REQ2WRP(reqp))->wr_dip; hwahc_state_t *hwahcp; mblk_t *data = reqp->intr_data; ASSERT(dip != NULL); hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip)); ASSERT(hwahcp != NULL); USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_intr_cb: ph = 0x%p reqp = 0x%p", (void *)ph, (void *)reqp); ASSERT((reqp->intr_cb_flags & USB_CB_INTR_CONTEXT) == 0); if (data == NULL) { usb_free_intr_req(reqp); return; } /* handle the notification */ hwahc_handle_notif(hwahcp, data); usb_free_intr_req(reqp); } /* * See Section 8.3.3.3 for Transfer Notification format and * Section 8.5.4 for HWA specific notifications. * Three kinds of Notifications: * - Transfer Completion * - DN Received * - BPST ADJ */ /* handle the notification according to notification type */ static void hwahc_handle_notif(hwahc_state_t *hwahcp, mblk_t *data) { int len; uint8_t *p; wa_notif_header_t *hdr; if (data == NULL) { return; } len = MBLKL(data); p = data->b_rptr; USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_handle_notif: data len = %d", len); /* * according to WUSB 1.0/8.1.2, multiple notifications might be sent * at a time, need to parse one by one */ while (len > 0) { if (len < 2) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_handle_notif: short packet len = %d", len); break; } hdr = (wa_notif_header_t *)p; if (len < hdr->bLength) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_handle_notif: length not match, " "hdr length = %d, actual length = %d", hdr->bLength, len); break; } switch (hdr->bNotifyType) { case WA_NOTIF_TYPE_TRANSFER: { uint8_t ept = p[2]; /* deal with transfer completion notification */ hwahc_handle_xfer_result(hwahcp, ept); break; } case HWA_NOTIF_TYPE_DN_RECEIVED: { hwa_notif_dn_recvd_t *dn_notif; dn_notif = kmem_alloc(hdr->bLength, KM_NOSLEEP); (void) memcpy(dn_notif, p, hdr->bLength); /* deal with device notification */ hwahc_handle_dn_notif(hwahcp, dn_notif); break; } case HWA_NOTIF_TYPE_BPST_ADJ: USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_handle_notif: received BPST adjust " "notification, bAdjustment = %d", p[2]); break; default: USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_handle_notif: unknown notification 0x%x", hdr->bNotifyType); break; } p += hdr->bLength; len -= hdr->bLength; } } /* * start listening on bulk-in ept for transfer result * * Dispatches a task to read the BULK IN endpoint to get the result of * last request. usb_async_req() will have system_taskq to process the tasks. */ int hwahc_start_result_thread(hwahc_state_t *hwahcp) { wusb_wa_data_t *wa_data; USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_start_result_thread:"); ASSERT(mutex_owned(&hwahcp->hwahc_mutex)); if (hwahcp->hwahc_result_thread_id != 0) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_start_result_thread: already started"); return (USB_SUCCESS); } wa_data = &hwahcp->hwahc_wa_data; mutex_enter(&wa_data->wa_mutex); if ((wa_data->wa_bulkin_ph != NULL) && (wa_data->wa_bulkin_pipe_state != WA_PIPE_STOPPED)) { mutex_exit(&wa_data->wa_mutex); return (USB_INVALID_PIPE); } mutex_exit(&wa_data->wa_mutex); if (wa_data->wa_bulkin_ph == NULL) { mutex_exit(&hwahcp->hwahc_mutex); if (usb_pipe_open(wa_data->wa_dip, &wa_data->wa_bulkin_ept, &wa_data->wa_pipe_policy, USB_FLAGS_SLEEP, &wa_data->wa_bulkin_ph) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_start_result_thread: open pipe failed"); mutex_enter(&hwahcp->hwahc_mutex); return (USB_FAILURE); } mutex_enter(&hwahcp->hwahc_mutex); mutex_enter(&wa_data->wa_mutex); wa_data->wa_bulkin_pipe_state = WA_PIPE_STOPPED; mutex_exit(&wa_data->wa_mutex); } /* kick off an asynchronous thread to handle transfer result */ if (usb_async_req(hwahcp->hwahc_dip, hwahc_result_thread, (void *)hwahcp, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_start_result_thread: failed to start result thread"); return (USB_FAILURE); } hwahcp->hwahc_result_thread_id = (kthread_t *)1; /* pipe state is active while the result thread is on */ mutex_enter(&wa_data->wa_mutex); wa_data->wa_bulkin_pipe_state = WA_PIPE_ACTIVE; mutex_exit(&wa_data->wa_mutex); return (USB_SUCCESS); } /* stop the bulk-in ept from listening */ static void hwahc_stop_result_thread(hwahc_state_t *hwahcp) { wusb_wa_data_t *wa_data; ASSERT(mutex_owned(&hwahcp->hwahc_mutex)); USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_stop_result_thread:"); if (hwahcp->hwahc_result_thread_id == 0) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_stop_result_thread: already stopped"); return; } wa_data = &hwahcp->hwahc_wa_data; mutex_enter(&wa_data->wa_mutex); if ((wa_data->wa_bulkin_ph == NULL) || (wa_data->wa_bulkin_pipe_state != WA_PIPE_ACTIVE)) { USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_stop_result_thread: invalid pipe state"); mutex_exit(&wa_data->wa_mutex); return; } mutex_exit(&wa_data->wa_mutex); USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_stop_result_thread: reset hwa bulk-in pipe"); mutex_exit(&hwahcp->hwahc_mutex); usb_pipe_reset(wa_data->wa_dip, wa_data->wa_bulkin_ph, USB_FLAGS_SLEEP, NULL, NULL); /* * have to close pipe here to fail the bulk-in transfer * that never timeouts */ USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_stop_result_thread: close hwa bulk-in pipe"); usb_pipe_close(wa_data->wa_dip, wa_data->wa_bulkin_ph, USB_FLAGS_SLEEP, NULL, NULL); mutex_enter(&hwahcp->hwahc_mutex); mutex_enter(&wa_data->wa_mutex); wa_data->wa_bulkin_ph = NULL; wa_data->wa_bulkin_pipe_state = WA_PIPE_STOPPED; mutex_exit(&wa_data->wa_mutex); while (hwahcp->hwahc_result_thread_id != 0) { /* wait the result thread to exit */ cv_wait(&hwahcp->hwahc_result_thread_cv, &hwahcp->hwahc_mutex); } } /* * keep listening for transfer result by setting timeout to 0 while the * bulk-in pipe is active * the thread would be stopped by closing bulk-in pipe or encountering * transaction error, eg, hot-removal of hwa device */ static void hwahc_result_thread(void *arg) { hwahc_state_t *hwahcp = (hwahc_state_t *)arg; wusb_wa_data_t *wa_data = &hwahcp->hwahc_wa_data; int rval; uint8_t retry = 0; mutex_enter(&hwahcp->hwahc_mutex); ASSERT(hwahcp->hwahc_result_thread_id == (kthread_t *)1); hwahcp->hwahc_result_thread_id = curthread; USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_result_thread: started, thread_id=0x%p", (void *)hwahcp->hwahc_result_thread_id); /* keep polling the bulk IN endpoint to get the result */ mutex_enter(&wa_data->wa_mutex); while (wa_data->wa_bulkin_pipe_state == WA_PIPE_ACTIVE) { mutex_exit(&wa_data->wa_mutex); mutex_exit(&hwahcp->hwahc_mutex); if ((rval = wusb_wa_get_xfer_result(wa_data)) != USB_SUCCESS) { retry++; USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_result_thread: get xfer result failed, " "rval = %d, retry = %d", rval, retry); /* retry 3 times upon failure */ if (retry >= 3) { mutex_enter(&hwahcp->hwahc_mutex); mutex_enter(&wa_data->wa_mutex); break; } } mutex_enter(&hwahcp->hwahc_mutex); mutex_enter(&wa_data->wa_mutex); } hwahcp->hwahc_result_thread_id = 0; wa_data->wa_bulkin_pipe_state = WA_PIPE_STOPPED; mutex_exit(&wa_data->wa_mutex); /* signal to the thread requesting stopping if any */ cv_signal(&hwahcp->hwahc_result_thread_cv); USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle, "hwahc_result_thread: ended"); mutex_exit(&hwahcp->hwahc_mutex); } /* * nothing to do here, just check if the ept number in the transfer * completion notification is valid * the actual handling of transfer result is performed by the result thread */ static void hwahc_handle_xfer_result(hwahc_state_t *hwahcp, uint8_t ept) { usb_ep_descr_t *epdt; USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_handle_xfer_result: result on ept %d", ept); epdt = &hwahcp->hwahc_wa_data.wa_bulkin_ept; /* the result should be on the bulk-in ept */ if ((epdt->bEndpointAddress & USB_EP_NUM_MASK) != ept) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_handle_xfer_result: ept number not match"); return; } } /* * Section 8.5.4.2. * Copy the DN Notification and add it to the instance's global * nofication list. If the worker thread is not started yet, start * it. */ static void hwahc_handle_dn_notif(hwahc_state_t *hwahcp, hwa_notif_dn_recvd_t *dn_notif) { hwahc_dn_notif_list_t *nlist; USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_handle_dn_notif: notif = 0x%p", (void *)dn_notif); nlist = kmem_zalloc(sizeof (hwahc_dn_notif_list_t), KM_NOSLEEP); mutex_enter(&hwahcp->hwahc_mutex); nlist->dn_notif = dn_notif; usba_init_list(&nlist->notif_list, (usb_opaque_t)nlist, hwahcp->hwahc_dev_data->dev_iblock_cookie); /* queue the new notification to the list */ usba_add_to_list(&hwahcp->hwahc_dn_notif_queue, &nlist->notif_list); /* handle the notification queue with an asynchronous thread */ if (hwahcp->hwahc_notif_thread_id == 0) { if (usb_async_req(hwahcp->hwahc_dip, hwahc_notif_thread, (void *)hwahcp, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_handle_dn_notif: no notif thread started"); mutex_exit(&hwahcp->hwahc_mutex); return; } hwahcp->hwahc_notif_thread_id = (kthread_t *)1; } mutex_exit(&hwahcp->hwahc_mutex); } /* handle the notifications in the notification queue in sequence */ static void hwahc_notif_thread(void *arg) { hwahc_state_t *hwahcp = (hwahc_state_t *)arg; hwahc_dn_notif_list_t *nlist; hwa_notif_dn_recvd_t *dn_notif; mutex_enter(&hwahcp->hwahc_mutex); ASSERT(hwahcp->hwahc_notif_thread_id == (kthread_t *)1); hwahcp->hwahc_notif_thread_id = curthread; USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_notif_thread: started, thread_id=0x%p", (void *)hwahcp->hwahc_notif_thread_id); while (usba_list_entry_count(&hwahcp->hwahc_dn_notif_queue) != 0) { /* * first in first out, only one notification will be handled * at a time, so it assures no racing in attach or detach */ if ((nlist = (hwahc_dn_notif_list_t *)usba_rm_first_pvt_from_list( &hwahcp->hwahc_dn_notif_queue)) == NULL) { continue; } dn_notif = nlist->dn_notif; mutex_exit(&hwahcp->hwahc_mutex); hwahc_handle_dn(hwahcp, dn_notif); usba_destroy_list(&nlist->notif_list); kmem_free(nlist, sizeof (hwahc_dn_notif_list_t)); mutex_enter(&hwahcp->hwahc_mutex); } hwahcp->hwahc_notif_thread_id = 0; USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_notif_thread: ended"); mutex_exit(&hwahcp->hwahc_mutex); } /* Set the child device's active bit to 1 */ static void hwahc_set_device_active(hwahc_state_t *hwahcp, uint8_t devaddr) { wusb_dev_info_t *dev_info; wusb_hc_data_t *hc_data = &hwahcp->hwahc_hc_data; int i; mutex_enter(&hc_data->hc_mutex); for (i = 1; i <= hc_data->hc_num_ports; i++) { dev_info = hc_data->hc_dev_infos[i]; if ((dev_info != NULL) && (dev_info->wdev_addr == devaddr)) { dev_info->wdev_active = 1; USB_DPRINTF_L3(DPRINT_MASK_EVENTS, hwahcp->hwahc_log_handle, "hwahc_set_device_active:device(%p) updated ", (void *)dev_info); break; } } mutex_exit(&hc_data->hc_mutex); } /* * handle a specific device notification * assuming the raw data in HWA DN_RECEIVED notification pkt includes * no more than one dn pkt */ static void hwahc_handle_dn(hwahc_state_t *hwahcp, hwa_notif_dn_recvd_t *dn_notif) { uint8_t *p; size_t len; uint8_t dntype; int circ; wusb_hc_data_t *hc_data = &hwahcp->hwahc_hc_data; if (dn_notif->bLength < 4) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_handle_dn: bLength too short %d", dn_notif->bLength); kmem_free(dn_notif, dn_notif->bLength); return; } p = dn_notif->notifdata; len = dn_notif->bLength - 4; /* * WUSB Errata 06.12 specifies that the raw data in the DN_RECEIVED * notification must not include the WUSB header, but only the bType * and Notification specific data */ if (len == 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_handle_dn: no raw data"); kmem_free(dn_notif, dn_notif->bLength); return; } dntype = *p; /* update the device's status bit, no matter what the DN is */ hwahc_set_device_active(hwahcp, dn_notif->bSourceDeviceAddr); ndi_devi_enter(hwahcp->hwahc_dip, &circ); switch (dntype) { case WUSB_DN_CONNECT: /* DN_Connect */ wusb_hc_handle_dn_connect( hc_data, hwahcp->hwahc_default_pipe, hwahcp->hwahc_wa_data.wa_ifno, p, len, &hwahcp->hwahc_secrt_data); break; case WUSB_DN_DISCONNECT: /* DN_Disconnect */ wusb_hc_handle_dn_disconnect( hc_data, dn_notif->bSourceDeviceAddr, p, len); break; case WUSB_DN_ALIVE: /* We only send KeepAlive IE to one device at a comment */ mutex_enter(&hc_data->hc_mutex); if (dn_notif->bSourceDeviceAddr == hc_data->hc_alive_ie.bDeviceAddress[0]) { mutex_exit(&hc_data->hc_mutex); wusb_hc_rem_ie(hc_data, (wusb_ie_header_t *)&hc_data->hc_alive_ie); mutex_enter(&hc_data->hc_mutex); } mutex_exit(&hc_data->hc_mutex); break; case WUSB_DN_EPRDY: case WUSB_DN_MASAVAILCHANGED: case WUSB_DN_REMOTEWAKEUP: case WUSB_DN_SLEEP: default: USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_handle_dn: dn type 0x%x not supported yet", dntype); break; } kmem_free(dn_notif, dn_notif->bLength); ndi_devi_exit(hwahcp->hwahc_dip, circ); } /* exceptional callback for notification ept */ /* ARGSUSED */ static void hwahc_intr_exc_cb(usb_pipe_handle_t ph, struct usb_intr_req *reqp) { dev_info_t *dip = (USBA_REQ2WRP(reqp))->wr_dip; hwahc_state_t *hwahcp; ASSERT(dip != NULL); hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip)); ASSERT(hwahcp != NULL); USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_intr_exc_cb: receive intr exception cb, cr=%d", reqp->intr_completion_reason); ASSERT((reqp->intr_cb_flags & USB_CB_INTR_CONTEXT) == 0); mutex_enter(&hwahcp->hwahc_mutex); switch (reqp->intr_completion_reason) { case USB_CR_PIPE_RESET: /* only restart nep after autoclearing */ if (hwahcp->hwahc_dev_state == USB_DEV_ONLINE) { hwahcp->hwahc_wa_data.wa_intr_pipe_state = WA_PIPE_STOPPED; mutex_exit(&hwahcp->hwahc_mutex); (void) wusb_wa_start_nep(&hwahcp->hwahc_wa_data, USB_FLAGS_NOSLEEP); mutex_enter(&hwahcp->hwahc_mutex); } break; case USB_CR_DEV_NOT_RESP: case USB_CR_STOPPED_POLLING: case USB_CR_PIPE_CLOSING: case USB_CR_UNSPECIFIED_ERR: /* never restart nep on these conditions */ default: /* for all others, wait for the autoclearing PIPE_RESET cb */ break; } usb_free_intr_req(reqp); mutex_exit(&hwahcp->hwahc_mutex); } /* * callback function called by WA to resubmit a periodic request for * interrupt polling or isochronous transfer. */ static int hwahc_pipe_submit_periodic_req(wusb_wa_data_t *wa_data, usba_pipe_handle_data_t *ph) { hwahc_state_t *hwahcp = wa_data->wa_private_data; hwahc_pipe_private_t *pp = (hwahc_pipe_private_t *)ph->p_hcd_private; int rval; mutex_enter(&hwahcp->hwahc_mutex); USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_pipe_submit_periodic_req: hwahcp=0x%p, pp=0x%p," " pipe state = %d", (void *)hwahcp, (void *)pp, pp->pp_state); if (pp->pp_state != HWAHC_PIPE_STATE_ACTIVE) { /* pipe error or pipe closing, don't resubmit any more */ USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_pipe_submit_periodic_req: pipe not active = %d", pp->pp_state); mutex_exit(&hwahcp->hwahc_mutex); return (USB_PIPE_ERROR); } mutex_exit(&hwahcp->hwahc_mutex); /* re-submit the original request */ rval = wusb_wa_intr_xfer(wa_data, pp->pp_rp, ph, (usb_intr_req_t *)pp->pp_client_periodic_in_reqp, 0); return (rval); } /* call HCD callback for completion handling */ static void hwahc_rpipe_xfer_cb(dev_info_t *dip, usba_pipe_handle_data_t *ph, wusb_wa_trans_wrapper_t *wr, usb_cr_t cr) { hwahc_state_t *hwahcp; hwahc_pipe_private_t *pp; usb_opaque_t req; wusb_hc_data_t *hc_data; hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip)); if (hwahcp == NULL) { return; } hc_data = &hwahcp->hwahc_hc_data; mutex_enter(&hwahcp->hwahc_mutex); USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_rpipe_xfer_cb: ph = 0x%p, wr = 0x%p cr = 0x%x", (void *)ph, (void *)wr, cr); pp = (hwahc_pipe_private_t *)ph->p_hcd_private; mutex_enter(&hc_data->hc_mutex); pp->pp_wdev->wdev_active = 1; /* this device is active on xfer */ mutex_exit(&hc_data->hc_mutex); switch (cr) { case USB_CR_OK: break; case USB_CR_NOT_SUPPORTED: case USB_CR_NO_RESOURCES: case USB_CR_PIPE_RESET: case USB_CR_STOPPED_POLLING: pp->pp_state = HWAHC_PIPE_STATE_IDLE; break; case USB_CR_PIPE_CLOSING: break; default: pp->pp_state = HWAHC_PIPE_STATE_ERROR; break; } if (wr && wr->wr_reqp) { req = wr->wr_reqp; mutex_enter(&wr->wr_rp->rp_mutex); wr->wr_reqp = NULL; mutex_exit(&wr->wr_rp->rp_mutex); } else { /* periodic pipe cleanup */ /* the original request is cleared and returned to client */ req = pp->pp_client_periodic_in_reqp; pp->pp_client_periodic_in_reqp = NULL; } mutex_exit(&hwahcp->hwahc_mutex); USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_rpipe_xfer_cb: call usba_hcdi_cb for req= 0x%p", (void *)req); usba_hcdi_cb(ph, req, cr); } /* post disconnect event to child on a certain port */ static void hwahc_disconnect_dev(dev_info_t *dip, usb_port_t port) { hwahc_state_t *hwahcp; int circ; dev_info_t *child_dip; if ((hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip))) == NULL) { return; } ndi_devi_enter(dip, &circ); mutex_enter(&hwahcp->hwahc_mutex); child_dip = hwahcp->hwahc_hc_data.hc_children_dips[port]; if ((hwahcp->hwahc_dev_state == USB_DEV_ONLINE) && child_dip) { mutex_exit(&hwahcp->hwahc_mutex); /* if the child driver remains attached */ if (i_ddi_devi_attached(child_dip)) { hwahc_post_event(hwahcp, port, USBA_EVENT_TAG_HOT_REMOVAL); } mutex_enter(&hwahcp->hwahc_mutex); } mutex_exit(&hwahcp->hwahc_mutex); ndi_devi_exit(dip, circ); } /* post reconect event to child on a certain port */ static void hwahc_reconnect_dev(dev_info_t *dip, usb_port_t port) { hwahc_state_t *hwahcp; int circ; if ((hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip))) == NULL) { return; } ndi_devi_enter(dip, &circ); mutex_enter(&hwahcp->hwahc_mutex); if ((hwahcp->hwahc_dev_state == USB_DEV_ONLINE) && (hwahcp->hwahc_hc_data.hc_children_dips[port])) { mutex_exit(&hwahcp->hwahc_mutex); hwahc_post_event(hwahcp, port, USBA_EVENT_TAG_HOT_INSERTION); mutex_enter(&hwahcp->hwahc_mutex); } mutex_exit(&hwahcp->hwahc_mutex); ndi_devi_exit(dip, circ); } /* * Device TrustTimeout timer operations: * hwahc_start_trust_timer: start the trust timer for a newly connected device * hwahc_trust_timeout_handler: timer handler * hwahc_stop_trust_timer: stop a device's trust timer */ static void hwahc_start_trust_timer(wusb_dev_info_t *dev) { if (hwahc_enable_trust_timeout == 0) { return; } if (dev->wdev_trust_timer == NULL) { dev->wdev_trust_timer = timeout(hwahc_trust_timeout_handler, (void *)dev, drv_usectohz(WUSB_TRUST_TIMEOUT_US)); } } /* timeout handler for device TrustTimeout. See section 4.14 */ static void hwahc_trust_timeout_handler(void *arg) { wusb_dev_info_t *dev = (wusb_dev_info_t *)arg; usb_port_t port; uint16_t dev_addr; wusb_hc_data_t *hc_data = dev->wdev_hc; uint8_t retry = 3; int rval; mutex_enter(&hc_data->hc_mutex); dev->wdev_trust_timer = 0; dev_addr = dev->wdev_addr; if (dev->wdev_active == 1) { /* device is active during the past period. Restart the timer */ dev->wdev_active = 0; /* expect device DN set it to 1 */ } else { /* send a KeepAlive IE to query the device */ for (retry = 0; retry < 3; retry++) { mutex_exit(&hc_data->hc_mutex); rval = wusb_hc_send_keepalive_ie(hc_data, dev_addr); mutex_enter(&hc_data->hc_mutex); if (rval == USB_SUCCESS) { break; } /* retry 3 times if fail to send KeepAlive IE */ } if (dev->wdev_active == 0) { /* still no activity! Delete this device */ if (wusb_hc_is_dev_connected(hc_data, dev->wdev_cdid, &port)) { mutex_exit(&hc_data->hc_mutex); (void) hwahc_destroy_child(hc_data->hc_dip, port); /* the device comes to the end of its life */ return; } } } /* active or we received DN during query */ hwahc_start_trust_timer(dev); mutex_exit(&hc_data->hc_mutex); } /* stop a child device's trust timeout handler */ void hwahc_stop_trust_timer(wusb_dev_info_t *dev) { timeout_id_t tid; wusb_hc_data_t *hc_data = dev->wdev_hc; ASSERT(mutex_owned(&hc_data->hc_mutex)); if (hwahc_enable_trust_timeout == 0) { return; } tid = dev->wdev_trust_timer; dev->wdev_trust_timer = NULL; mutex_exit(&hc_data->hc_mutex); if (tid != NULL) { (void) untimeout(tid); } mutex_enter(&hc_data->hc_mutex); } /* configure child device and attach child on a certain port */ static int hwahc_create_child(dev_info_t *dip, usb_port_t port) { hwahc_state_t *hwahcp; wusb_hc_data_t *hc_data; wusb_dev_info_t *dev_info; usb_pipe_handle_t ph; int rval; dev_info_t *child_dip; usba_device_t *child_ud = NULL; mblk_t *pdata = NULL; usb_cr_t completion_reason; usb_cb_flags_t cb_flags; size_t size; uint8_t address; int user_conf_index; uint_t config_index; int prh_circ, rh_circ, circ; dev_info_t *rh_dip; usb_dev_descr_t usb_dev_descr; if ((hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip))) == NULL) { return (USB_INVALID_ARGS); } rh_dip = hwahcp->hwahc_hubd->h_usba_device->usb_root_hub_dip; ndi_hold_devi(dip); /* avoid racing with dev detach */ /* exclude other threads */ ndi_devi_enter(ddi_get_parent(rh_dip), &prh_circ); ndi_devi_enter(rh_dip, &rh_circ); ndi_devi_enter(dip, &circ); _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*dev_info)); hc_data = &hwahcp->hwahc_hc_data; mutex_enter(&hc_data->hc_mutex); dev_info = hc_data->hc_dev_infos[port]; /* Created in whcdi.c before authed */ child_dip = hc_data->hc_children_dips[port]; child_ud = usba_get_usba_device(child_dip); ph = dev_info->wdev_ph; mutex_exit(&hc_data->hc_mutex); /* * HWA maintains the address space as a separate bus and * will not occupy parent's address space */ address = child_ud->usb_addr; if (address < 0x80) { USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_create_child: reconnecting, address = %d", address); } else { /* SetAddress(0) */ if ((rval = usb_pipe_sync_ctrl_xfer(child_dip, ph, USB_DEV_REQ_HOST_TO_DEV, USB_REQ_SET_ADDRESS, /* bRequest */ 0, /* wValue */ 0, /* wIndex */ 0, /* wLength */ NULL, 0, &completion_reason, &cb_flags, 0)) != USB_SUCCESS) { char buffer[64]; USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "setting address failed (cr=%s cb_flags=%s " "rval=%d)", usb_str_cr(completion_reason), usb_str_cb_flags(cb_flags, buffer, sizeof (buffer)), rval); goto done; } USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "set address 0 done"); usb_pipe_close(child_dip, ph, USB_FLAGS_SLEEP | USBA_FLAGS_PRIVILEGED, NULL, NULL); child_ud->usb_addr = 0; dev_info->wdev_addr = 0; dev_info->wdev_ph = NULL; /* need to be called each time dev addr is changed */ if ((rval = wusb_hc_set_device_info(&hwahcp->hwahc_hc_data, port)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "update device info failed, rval = %d", rval); goto done; } /* new ph is stored in usba_device */ if ((rval = usb_pipe_open(child_dip, NULL, NULL, USB_FLAGS_SLEEP | USBA_FLAGS_PRIVILEGED, &ph)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "usb_pipe_open failed (%d)", rval); goto done; } /* provide at least 2ms time for address change, 7.3.1.3 */ delay(drv_usectohz(2000)); /* start normal enumeration process */ /* * wusb bus address has 1:1 relationship with port number * and wusb bus address starts from 2, so as to follow * the convention that USB bus address 1 is reserved for * host controller device. As such, only 126 WUSB devices * are supported on a WUSB host */ address = port + 1; if (address >= 0x80) { USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_create_child: address for port %d exceeds " "0x80", port); rval = USB_FAILURE; goto done; } /* Set the address of the device */ if ((rval = usb_pipe_sync_ctrl_xfer(child_dip, ph, USB_DEV_REQ_HOST_TO_DEV, USB_REQ_SET_ADDRESS, /* bRequest */ address, /* wValue */ 0, /* wIndex */ 0, /* wLength */ NULL, 0, &completion_reason, &cb_flags, 0)) != USB_SUCCESS) { char buffer[64]; USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "setting address failed (cr=%s cb_flags=%s " "rval=%d)", usb_str_cr(completion_reason), usb_str_cb_flags(cb_flags, buffer, sizeof (buffer)), rval); goto done; } USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "set address 0x%x done", address); usb_pipe_close(child_dip, ph, USB_FLAGS_SLEEP | USBA_FLAGS_PRIVILEGED, NULL, NULL); child_ud->usb_addr = address; dev_info->wdev_addr = address; dev_info->wdev_ph = NULL; if ((rval = wusb_hc_set_device_info(&hwahcp->hwahc_hc_data, port)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "update device info failed, rval = %d", rval); goto done; } /* new ph is stored in usba_device */ if ((rval = usb_pipe_open(child_dip, NULL, NULL, USB_FLAGS_SLEEP | USBA_FLAGS_PRIVILEGED, &ph)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "usb_pipe_open failed (%d)", rval); goto done; } /* provide at least 2ms time for address change, 7.3.1.3 */ delay(drv_usectohz(2000)); } /* get device descriptor ignoring device reconnection */ rval = usb_pipe_sync_ctrl_xfer(child_dip, ph, USB_DEV_REQ_DEV_TO_HOST | USB_DEV_REQ_TYPE_STANDARD, USB_REQ_GET_DESCR, /* bRequest */ USB_DESCR_TYPE_SETUP_DEV, /* wValue */ 0, /* wIndex */ 512, /* wLength */ &pdata, USB_ATTRS_SHORT_XFER_OK, &completion_reason, &cb_flags, 0); if (rval != USB_SUCCESS) { if (pdata) { freemsg(pdata); pdata = NULL; } USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_create_child: get device descriptor failed " "(%s 0x%x %d)", usb_str_cr(completion_reason), cb_flags, rval); goto done; } ASSERT(pdata != NULL); size = usb_parse_dev_descr( pdata->b_rptr, MBLKL(pdata), &usb_dev_descr, sizeof (usb_dev_descr_t)); freemsg(pdata); if (size < USB_DEV_DESCR_SIZE) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_create_child: get device descriptor size = %lu " "expected size = %u", size, USB_DEV_DESCR_SIZE); rval = USB_FAILURE; goto done; } bcopy(&usb_dev_descr, child_ud->usb_dev_descr, sizeof (usb_dev_descr_t)); child_ud->usb_n_cfgs = usb_dev_descr.bNumConfigurations; if (usb_dev_descr.bNumConfigurations == 0) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "device descriptor:\n\t" "l=0x%x type=0x%x USB=0x%x class=0x%x subclass=0x%x\n\t" "protocol=0x%x maxpktsize=0x%x " "Vid=0x%x Pid=0x%x rel=0x%x\n\t" "Mfg=0x%x P=0x%x sn=0x%x #config=0x%x", usb_dev_descr.bLength, usb_dev_descr.bDescriptorType, usb_dev_descr.bcdUSB, usb_dev_descr.bDeviceClass, usb_dev_descr.bDeviceSubClass, usb_dev_descr.bDeviceProtocol, usb_dev_descr.bMaxPacketSize0, usb_dev_descr.idVendor, usb_dev_descr.idProduct, usb_dev_descr.bcdDevice, usb_dev_descr.iManufacturer, usb_dev_descr.iProduct, usb_dev_descr.iSerialNumber, usb_dev_descr.bNumConfigurations); rval = USB_FAILURE; goto done; } /* get the device string descriptor(s) */ usba_get_dev_string_descrs(child_dip, child_ud); /* retrieve config cloud for all configurations */ rval = hubd_get_all_device_config_cloud(hwahcp->hwahc_hubd, child_dip, child_ud); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "failed to get configuration descriptor(s)"); goto done; } /* get the preferred configuration for this device */ user_conf_index = hubd_select_device_configuration(hwahcp->hwahc_hubd, port, child_dip, child_ud); /* Check if the user selected configuration index is in range */ if ((user_conf_index >= usb_dev_descr.bNumConfigurations) || (user_conf_index < 0)) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "Configuration index for device idVendor=%d " "idProduct=%d is=%d, and is out of range[0..%d]", usb_dev_descr.idVendor, usb_dev_descr.idProduct, user_conf_index, usb_dev_descr.bNumConfigurations - 1); /* treat this as user didn't specify configuration */ user_conf_index = USBA_DEV_CONFIG_INDEX_UNDEFINED; } if (user_conf_index == USBA_DEV_CONFIG_INDEX_UNDEFINED) { if (child_ud->usb_preferred_driver) { /* * It is the job of the "preferred driver" to put the * device in the desired configuration. Till then * put the device in config index 0. */ /* h_ignore_pwr_budget = TRUE, not care the power */ if ((rval = usba_hubdi_check_power_budget(dip, child_ud, USB_DEV_DEFAULT_CONFIG_INDEX)) != USB_SUCCESS) { goto done; } child_dip = hubd_ready_device(hwahcp->hwahc_hubd, child_dip, child_ud, USB_DEV_DEFAULT_CONFIG_INDEX); /* * Assign the dip before onlining to avoid race * with busctl */ mutex_enter(&hc_data->hc_mutex); hc_data->hc_children_dips[port] = child_dip; mutex_exit(&hc_data->hc_mutex); (void) usba_bind_driver(child_dip); } else { /* * loop through all the configurations to see if we * can find a driver for any one config. If not, set * the device in config_index 0 */ rval = USB_FAILURE; for (config_index = 0; (config_index < usb_dev_descr.bNumConfigurations) && (rval != USB_SUCCESS); config_index++) { child_dip = hubd_ready_device( hwahcp->hwahc_hubd, child_dip, child_ud, config_index); /* * Assign the dip before onlining to avoid race * with busctl */ mutex_enter(&hc_data->hc_mutex); hc_data->hc_children_dips[port] = child_dip; mutex_exit(&hc_data->hc_mutex); rval = usba_bind_driver(child_dip); if (rval == USB_SUCCESS) { /* always succeed for WUSB device */ if ((usba_hubdi_check_power_budget(dip, child_ud, config_index)) != USB_SUCCESS) { rval = USB_FAILURE; goto done; } } } if (rval != USB_SUCCESS) { if ((usba_hubdi_check_power_budget(dip, child_ud, 0)) != USB_SUCCESS) { goto done; } child_dip = hubd_ready_device( hwahcp->hwahc_hubd, child_dip, child_ud, 0); mutex_enter(&hc_data->hc_mutex); hc_data->hc_children_dips[port] = child_dip; mutex_exit(&hc_data->hc_mutex); } } /* end else loop all configs */ } else { if ((usba_hubdi_check_power_budget(dip, child_ud, (uint_t)user_conf_index)) != USB_SUCCESS) { rval = USB_FAILURE; goto done; } child_dip = hubd_ready_device(hwahcp->hwahc_hubd, child_dip, child_ud, (uint_t)user_conf_index); /* * Assign the dip before onlining to avoid race * with busctl */ mutex_enter(&hc_data->hc_mutex); hc_data->hc_children_dips[port] = child_dip; mutex_exit(&hc_data->hc_mutex); (void) usba_bind_driver(child_dip); rval = USB_SUCCESS; } /* workaround for non response after ctrl write */ usb_pipe_close(child_dip, ph, USB_FLAGS_SLEEP | USBA_FLAGS_PRIVILEGED, NULL, NULL); if ((rval = usb_pipe_open(child_dip, NULL, NULL, USB_FLAGS_SLEEP | USBA_FLAGS_PRIVILEGED, &ph)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "usb_pipe_open failed (%d)", rval); goto done; } done: _NOTE(NOW_VISIBLE_TO_OTHER_THREADS(*dev_info)); ndi_devi_exit(dip, circ); ndi_devi_exit(rh_dip, rh_circ); ndi_devi_exit(ddi_get_parent(rh_dip), prh_circ); (void) devfs_clean(rh_dip, NULL, 0); if (rval == USB_SUCCESS) { (void) ndi_devi_online(child_dip, 0); USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_create_child: create timer for child %p", (void *)dev_info); mutex_enter(&hc_data->hc_mutex); hwahc_start_trust_timer(dev_info); mutex_exit(&hc_data->hc_mutex); } ndi_rele_devi(dip); return (rval); } /* offline child on a certain port */ static int hwahc_destroy_child(dev_info_t *dip, usb_port_t port) { hwahc_state_t *hwahcp; if ((hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip))) == NULL) { return (USB_INVALID_ARGS); } hwahc_post_event(hwahcp, port, USBA_EVENT_TAG_HOT_REMOVAL); USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_destroy_child: scheduling cleanup"); /* schedule cleanup thread */ hubd_schedule_cleanup(hwahcp->hwahc_hubd->h_usba_device-> usb_root_hub_dip); return (USB_SUCCESS); } /* * called by cleanup thread to offline child and cleanup child resources * Child's callback functions have been called before calling this routine. * dip - hwahc's dip */ static int hwahc_cleanup_child(dev_info_t *dip) { hwahc_state_t *hwahcp; wusb_hc_data_t *hc_data; usb_port_t port; if ((hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip))) == NULL) { return (USB_INVALID_ARGS); } hc_data = &hwahcp->hwahc_hc_data; mutex_enter(&hc_data->hc_mutex); for (port = 1; port <= hc_data->hc_num_ports; port++) { dev_info_t *cdip = hc_data->hc_children_dips[port]; if (cdip == NULL || DEVI_IS_DEVICE_REMOVED(cdip) == 0) { continue; } /* * child's callback has been called and its dip has been * marked REMOVED. Do further cleanup in hwa driver for * this child. */ mutex_exit(&hc_data->hc_mutex); (void) hwahc_delete_child(dip, port, NDI_DEVI_REMOVE, B_TRUE); mutex_enter(&hc_data->hc_mutex); } mutex_exit(&hc_data->hc_mutex); return (USB_SUCCESS); } /* offline child and cleanup child resources */ static int hwahc_delete_child(dev_info_t *dip, usb_port_t port, uint_t flag, boolean_t retry) { hwahc_state_t *hwahcp; dev_info_t *child_dip; usba_device_t *usba_device; wusb_hc_data_t *hc_data; int rval; if ((hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip))) == NULL) { return (USB_INVALID_ARGS); } child_dip = hwahc_get_child_dip(hwahcp, port); if (child_dip == NULL) { return (USB_SUCCESS); } usba_device = usba_get_usba_device(child_dip); hc_data = &hwahcp->hwahc_hc_data; USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle, "hwahc_delete_child: port=%d, dip=0x%p usba_device=0x%p", port, (void *)child_dip, (void *)usba_device); if (usba_device) { usba_hubdi_incr_power_budget(dip, usba_device); } /* remove this child's dip. If it's <DS_INITIALIZED, free it */ rval = usba_destroy_child_devi(child_dip, flag); if ((rval == USB_SUCCESS) && (flag & NDI_DEVI_REMOVE)) { /* * if the child was still < DS_INITIALIZED * then our bus_unconfig was not called and * we have to zap the child here */ mutex_enter(&hc_data->hc_mutex); if (hc_data->hc_children_dips[port] == child_dip) { usba_device_t *ud = hc_data->hc_usba_devices[port]; wusb_dev_info_t *dev_info = hc_data->hc_dev_infos[port]; hc_data->hc_children_dips[port] = NULL; if (ud) { mutex_exit(&hc_data->hc_mutex); mutex_enter(&ud->usb_mutex); ud->usb_ref_count = 0; mutex_exit(&ud->usb_mutex); usba_free_usba_device(ud); mutex_enter(&hc_data->hc_mutex); hc_data->hc_usba_devices[port] = NULL; } /* free the child's wusb_dev_info data */ if (dev_info) { wusb_secrt_data_t *secrt_data; if (dev_info-> wdev_secrt_data.secrt_encry_descr) { secrt_data = &dev_info->wdev_secrt_data; kmem_free(secrt_data->secrt_encry_descr, sizeof (usb_encryption_descr_t) * secrt_data->secrt_n_encry); } if (dev_info->wdev_uwb_descr) { kmem_free(dev_info->wdev_uwb_descr, sizeof (usb_uwb_cap_descr_t)); } kmem_free(dev_info, sizeof (wusb_dev_info_t)); hc_data->hc_dev_infos[port] = NULL; } } mutex_exit(&hc_data->hc_mutex); } if ((rval != USB_SUCCESS) && retry) { hubd_schedule_cleanup(usba_device->usb_root_hub_dip); } return (rval); } /* * Set encryption type for WUSB host, refer to WUSB 1.0/8.5.3.6 * index = port number - 1 */ int hwahc_set_dev_encrypt(usb_pipe_handle_t ph, uint8_t ifc, usb_port_t index, wusb_secrt_data_t *secrt_data, uint8_t type) { int16_t value; usb_ctrl_setup_t setup; usb_cr_t cr; usb_cb_flags_t cb_flags; USB_DPRINTF_L4(DPRINT_MASK_WHCDI, whcdi_log_handle, "hwahc_set_dev_encrypt: device index = %d", index); if (type == USB_ENC_TYPE_UNSECURE) { value = 0; } else if (type == USB_ENC_TYPE_CCM_1) { if (secrt_data == NULL) { return (USB_INVALID_ARGS); } value = wusb_get_ccm_encryption_value(secrt_data); if (value == -1) { USB_DPRINTF_L2(DPRINT_MASK_WHCDI, whcdi_log_handle, "hwahc_set_dev_encrypt: cannot find ccm " "encryption type"); return (USB_FAILURE); } USB_DPRINTF_L2(DPRINT_MASK_WHCDI, whcdi_log_handle, "hwahc_set_dev_encrypt: ccm encryption value is %d", value); } else { USB_DPRINTF_L2(DPRINT_MASK_WHCDI, whcdi_log_handle, "hwahc_set_dev_encrypt: unsupported encryption type %d", type); return (USB_INVALID_ARGS); } setup.bmRequestType = USB_DEV_REQ_HOST_TO_DEV | USB_DEV_REQ_TYPE_CLASS | USB_DEV_REQ_RCPT_IF; setup.bRequest = USB_REQ_SET_ENCRYPTION; setup.wValue = (uint16_t)value; setup.wIndex = (index << 8) | ifc; setup.wLength = 0; setup.attrs = USB_ATTRS_NONE; USB_DPRINTF_L2(DPRINT_MASK_WHCDI, whcdi_log_handle, "bmRequestType=0x%x, bRequest=0x%x, wValue=0x%x, wIndex=0x%x", setup.bmRequestType, setup.bRequest, setup.wValue, setup.wIndex); return (usb_pipe_ctrl_xfer_wait(ph, &setup, NULL, &cr, &cb_flags, USB_FLAGS_SLEEP)); }