| /* |
| * Copyright 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| /* |
| * Copyright (c) 2007 by Lukas Turek <turek@ksvi.mff.cuni.cz> |
| * Copyright (c) 2007 by Jiri Svoboda <jirik.svoboda@seznam.cz> |
| * Copyright (c) 2007 by Martin Krulis <martin.krulis@matfyz.cz> |
| * Copyright (c) 2006 by Damien Bergamini <damien.bergamini@free.fr> |
| * Copyright (c) 2006 by Florian Stoehr <ich@florian-stoehr.de> |
| * |
| * Permission to use, copy, modify, and distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| * |
| */ |
| |
| /* |
| * ZD1211 wLAN driver |
| * USB communication |
| * |
| * Manage USB communication with the ZD-based device. |
| */ |
| |
| #include <sys/byteorder.h> |
| #include <sys/stream.h> |
| #include <sys/strsun.h> |
| #include <sys/mac_provider.h> |
| |
| #include "zyd.h" |
| #include "zyd_reg.h" |
| |
| static int zyd_usb_disconnect(dev_info_t *dip); |
| static int zyd_usb_reconnect(dev_info_t *dip); |
| static zyd_res zyd_usb_data_in_start_request(struct zyd_usb *uc); |
| |
| static zyd_usb_info_t usb_ids[] = { |
| {0x53, 0x5301, ZYD_ZD1211B}, |
| {0x105, 0x145f, ZYD_ZD1211}, |
| {0x411, 0xda, ZYD_ZD1211B}, |
| {0x471, 0x1236, ZYD_ZD1211B}, |
| {0x471, 0x1237, ZYD_ZD1211B}, |
| {0x50d, 0x705c, ZYD_ZD1211B}, |
| {0x586, 0x3401, ZYD_ZD1211}, |
| {0x586, 0x3402, ZYD_ZD1211}, |
| {0x586, 0x3407, ZYD_ZD1211}, |
| {0x586, 0x3409, ZYD_ZD1211}, |
| {0x586, 0x3410, ZYD_ZD1211B}, |
| {0x586, 0x3412, ZYD_ZD1211B}, |
| {0x586, 0x3413, ZYD_ZD1211B}, |
| {0x586, 0x340a, ZYD_ZD1211B}, |
| {0x586, 0x340f, ZYD_ZD1211B}, |
| {0x79b, 0x4a, ZYD_ZD1211}, |
| {0x79b, 0x62, ZYD_ZD1211B}, |
| {0x7b8, 0x6001, ZYD_ZD1211}, |
| {0x83a, 0x4505, ZYD_ZD1211B}, |
| {0xace, 0x1211, ZYD_ZD1211}, |
| {0xace, 0x1215, ZYD_ZD1211B}, |
| {0xb05, 0x170c, ZYD_ZD1211}, |
| {0xb05, 0x171b, ZYD_ZD1211B}, |
| {0xb3b, 0x1630, ZYD_ZD1211}, |
| {0xb3b, 0x5630, ZYD_ZD1211}, |
| {0xbaf, 0x121, ZYD_ZD1211B}, |
| {0xcde, 0x1a, ZYD_ZD1211B}, |
| {0xdf6, 0x9071, ZYD_ZD1211}, |
| {0xdf6, 0x9075, ZYD_ZD1211}, |
| {0x126f, 0xa006, ZYD_ZD1211}, |
| {0x129b, 0x1666, ZYD_ZD1211}, |
| {0x129b, 0x1667, ZYD_ZD1211B}, |
| {0x13b1, 0x1e, ZYD_ZD1211}, |
| {0x13b1, 0x24, ZYD_ZD1211B}, |
| {0x1435, 0x711, ZYD_ZD1211}, |
| {0x14ea, 0xab13, ZYD_ZD1211}, |
| {0x157e, 0x300b, ZYD_ZD1211}, |
| {0x157e, 0x300d, ZYD_ZD1211B}, |
| {0x157e, 0x3204, ZYD_ZD1211}, |
| {0x1582, 0x6003, ZYD_ZD1211B}, |
| {0x1740, 0x2000, ZYD_ZD1211}, |
| {0x2019, 0x5303, ZYD_ZD1211B}, |
| {0x6891, 0xa727, ZYD_ZD1211} |
| }; |
| |
| /* |
| * Get mac rev for usb vendor/product id. |
| */ |
| zyd_mac_rev_t |
| zyd_usb_mac_rev(uint16_t vendor, uint16_t product) |
| { |
| int i; |
| |
| ZYD_DEBUG((ZYD_DBG_USB, "matching device usb%x,%x\n", vendor, product)); |
| for (i = 0; i < sizeof (usb_ids) / sizeof (zyd_usb_info_t); i++) { |
| if (vendor == usb_ids[i].vendor_id && |
| product == usb_ids[i].product_id) |
| return (usb_ids[i].mac_rev); |
| } |
| |
| ZYD_DEBUG((ZYD_DBG_USB, "assuming ZD1211B\n")); |
| return (ZYD_ZD1211B); |
| } |
| |
| /* |
| * Vendor-specific write to the default control pipe. |
| */ |
| static zyd_res |
| zyd_usb_ctrl_send(struct zyd_usb *uc, uint8_t request, uint16_t value, |
| uint8_t *data, uint16_t len) |
| { |
| int err; |
| int retry = 0; |
| mblk_t *msg; |
| usb_ctrl_setup_t setup; |
| |
| /* Always clean structures before use */ |
| bzero(&setup, sizeof (setup)); |
| setup.bmRequestType = |
| USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_HOST_TO_DEV; |
| setup.bRequest = request; |
| setup.wValue = value; |
| setup.wIndex = 0; |
| setup.wLength = len; |
| setup.attrs = USB_ATTRS_NONE; |
| |
| if ((msg = allocb(len, BPRI_HI)) == NULL) |
| return (ZYD_FAILURE); |
| |
| bcopy(data, msg->b_wptr, len); |
| msg->b_wptr += len; |
| |
| while ((err = usb_pipe_ctrl_xfer_wait(uc->cdata->dev_default_ph, |
| &setup, &msg, NULL, NULL, 0)) != USB_SUCCESS) { |
| if (retry++ > 3) |
| break; |
| } |
| |
| freemsg(msg); |
| |
| if (err != USB_SUCCESS) { |
| ZYD_DEBUG((ZYD_DBG_USB, |
| "control pipe send failure (%d)\n", err)); |
| return (ZYD_FAILURE); |
| } |
| |
| return (ZYD_SUCCESS); |
| } |
| |
| /* |
| * Vendor-specific read from the default control pipe. |
| */ |
| static zyd_res |
| zyd_usb_ctrl_recv(struct zyd_usb *uc, uint8_t request, uint16_t value, |
| uint8_t *data, uint16_t len) |
| { |
| int err; |
| mblk_t *msg, *tmp_msg; |
| usb_ctrl_setup_t setup; |
| size_t msg_len; |
| |
| ASSERT(data != NULL); |
| |
| bzero(&setup, sizeof (setup)); |
| setup.bmRequestType = |
| USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_DEV_TO_HOST; |
| setup.bRequest = request; |
| setup.wValue = value; |
| setup.wIndex = 0; |
| setup.wLength = len; |
| setup.attrs = USB_ATTRS_NONE; |
| |
| /* Pointer msg must be either set to NULL or point to a valid mblk! */ |
| msg = NULL; |
| err = usb_pipe_ctrl_xfer_wait(uc->cdata->dev_default_ph, |
| &setup, &msg, NULL, NULL, 0); |
| |
| if (err != USB_SUCCESS) { |
| ZYD_WARN("control pipe receive failure (%d)\n", err); |
| return (ZYD_FAILURE); |
| } |
| |
| msg_len = msgsize(msg); |
| |
| if (msg_len != len) { |
| ZYD_WARN("control pipe failure: " |
| "received %d bytes, %d expected\n", (int)msg_len, len); |
| return (ZYD_FAILURE); |
| } |
| |
| if (msg->b_cont != NULL) { |
| /* Fragmented message, concatenate */ |
| tmp_msg = msgpullup(msg, -1); |
| freemsg(msg); |
| msg = tmp_msg; |
| } |
| |
| /* |
| * Now we can be sure the message is in a single block |
| * so we can copy it. |
| */ |
| bcopy(msg->b_rptr, data, len); |
| freemsg(msg); |
| |
| return (ZYD_SUCCESS); |
| } |
| |
| /* |
| * Load firmware into the chip. |
| */ |
| zyd_res |
| zyd_usb_loadfirmware(struct zyd_usb *uc, uint8_t *fw, size_t size) |
| { |
| uint16_t addr; |
| uint8_t stat; |
| |
| ZYD_DEBUG((ZYD_DBG_FW, "firmware size: %lu\n", size)); |
| |
| addr = ZYD_FIRMWARE_START_ADDR; |
| while (size > 0) { |
| const uint16_t mlen = (uint16_t)min(size, 4096); |
| |
| if (zyd_usb_ctrl_send(uc, ZYD_DOWNLOADREQ, addr, fw, mlen) |
| != USB_SUCCESS) |
| return (ZYD_FAILURE); |
| |
| addr += mlen / 2; |
| fw += mlen; |
| size -= mlen; |
| } |
| |
| /* check whether the upload succeeded */ |
| if (zyd_usb_ctrl_recv(uc, ZYD_DOWNLOADSTS, 0, &stat, sizeof (stat)) |
| != ZYD_SUCCESS) |
| return (ZYD_FAILURE); |
| |
| return ((stat & 0x80) ? ZYD_FAILURE : ZYD_SUCCESS); |
| } |
| |
| /* |
| * Return a specific alt_if from the device descriptor tree. |
| */ |
| static usb_alt_if_data_t * |
| usb_lookup_alt_if(usb_client_dev_data_t *cdd, uint_t config, |
| uint_t interface, uint_t alt) |
| { |
| usb_cfg_data_t *dcfg; |
| usb_if_data_t *cfgif; |
| usb_alt_if_data_t *ifalt; |
| |
| /* |
| * Assume everything is in the tree for now, |
| * (USB_PARSE_LVL_ALL) |
| * so we can directly index the array. |
| */ |
| |
| /* Descend to configuration, configs are 1-based */ |
| if (config < 1 || config > cdd->dev_n_cfg) |
| return (NULL); |
| dcfg = &cdd->dev_cfg[config - 1]; |
| |
| /* Descend to interface */ |
| if (interface > dcfg->cfg_n_if - 1) |
| return (NULL); |
| cfgif = &dcfg->cfg_if[interface]; |
| |
| /* Descend to alt */ |
| if (alt > cfgif->if_n_alt - 1) |
| return (NULL); |
| ifalt = &cfgif->if_alt[alt]; |
| |
| return (ifalt); |
| } |
| |
| /* |
| * Print all endpoints of an alt_if. |
| */ |
| static void |
| usb_list_all_endpoints(usb_alt_if_data_t *ifalt) |
| { |
| usb_ep_data_t *ep_data; |
| usb_ep_descr_t *ep_descr; |
| int i; |
| |
| for (i = 0; i < ifalt->altif_n_ep; i++) { |
| ep_data = &ifalt->altif_ep[i]; |
| ep_descr = &ep_data->ep_descr; |
| cmn_err(CE_NOTE, "EP: %u\n", ep_descr->bEndpointAddress); |
| } |
| } |
| |
| /* |
| * For the given alt_if, find an endpoint with the given |
| * address and direction. |
| * |
| * ep_direction USB_EP_DIR_IN or USB_EP_DIR_OUT |
| */ |
| static usb_ep_data_t * |
| usb_find_endpoint(usb_alt_if_data_t *alt_if, |
| uint_t ep_address, uint_t ep_direction) |
| { |
| usb_ep_data_t *ep_data; |
| usb_ep_descr_t *ep_descr; |
| uint_t ep_addr, ep_dir; |
| int i; |
| |
| for (i = 0; i < alt_if->altif_n_ep; i++) { |
| ep_data = &alt_if->altif_ep[i]; |
| ep_descr = &ep_data->ep_descr; |
| ep_addr = ep_descr->bEndpointAddress & USB_EP_NUM_MASK; |
| ep_dir = ep_descr->bEndpointAddress & USB_EP_DIR_MASK; |
| |
| if (ep_addr == ep_address && ep_dir == ep_direction) { |
| return (ep_data); |
| } |
| } |
| |
| ZYD_WARN("no endpoint with addr %u, dir %u\n", ep_address, |
| ep_direction); |
| return (NULL); |
| } |
| |
| enum zyd_usb_use_attr |
| { |
| ZYD_USB_USE_ATTR = 1, |
| ZYD_USB_NO_ATTR = 0 |
| }; |
| |
| /* |
| * Open a pipe to a given endpoint address/direction in the given |
| * alt_if. Furthemore, if use_attr == ZYD_USB_USE_ATTR, |
| * check whether the endpoint's transfer type is attr. |
| */ |
| static zyd_res |
| zyd_usb_open_pipe(struct zyd_usb *uc, |
| usb_alt_if_data_t *alt_if, |
| uint_t ep_address, |
| uint_t ep_direction, |
| uint_t attr, |
| enum zyd_usb_use_attr use_attr, |
| usb_pipe_handle_t *pipe, usb_ep_data_t *endpoint) |
| { |
| usb_pipe_policy_t pipe_policy; |
| |
| *endpoint = *usb_find_endpoint(alt_if, ep_address, ep_direction); |
| |
| if ((use_attr == ZYD_USB_USE_ATTR) && |
| (endpoint->ep_descr.bmAttributes & USB_EP_ATTR_MASK) != attr) { |
| |
| ZYD_WARN("endpoint %u/%s is not of type %s\n", ep_address, |
| (ep_direction == USB_EP_DIR_IN) ? "IN" : "OUT", |
| (attr == USB_EP_ATTR_BULK) ? "bulk" : "intr"); |
| return (ZYD_FAILURE); |
| } |
| |
| bzero(&pipe_policy, sizeof (usb_pipe_policy_t)); |
| pipe_policy.pp_max_async_reqs = ZYD_USB_REQ_COUNT; |
| |
| if (usb_pipe_open(uc->dip, &endpoint->ep_descr, |
| &pipe_policy, USB_FLAGS_SLEEP, pipe) != USB_SUCCESS) { |
| ZYD_WARN("failed to open pipe %u\n", ep_address); |
| return (ZYD_FAILURE); |
| } |
| |
| return (ZYD_SUCCESS); |
| } |
| |
| /* |
| * Open communication pipes. |
| * |
| * The following pipes are used by the ZD1211: |
| * |
| * 1/OUT BULK |
| * 2/IN BULK |
| * 3/IN INTR |
| * 4/OUT BULK or INTR |
| */ |
| zyd_res |
| zyd_usb_open_pipes(struct zyd_usb *uc) |
| { |
| usb_alt_if_data_t *alt_if; |
| |
| ZYD_DEBUG((ZYD_DBG_USB, "opening pipes\n")); |
| |
| alt_if = usb_lookup_alt_if(uc->cdata, ZYD_USB_CONFIG_NUMBER, |
| ZYD_USB_IFACE_INDEX, ZYD_USB_ALT_IF_INDEX); |
| |
| if (alt_if == NULL) { |
| ZYD_WARN("alt_if not found\n"); |
| return (ZYD_FAILURE); |
| } |
| |
| #ifdef DEBUG |
| if (zyd_dbg_flags & ZYD_DBG_USB) |
| usb_list_all_endpoints(alt_if); |
| #endif |
| |
| if (zyd_usb_open_pipe(uc, alt_if, 1, USB_EP_DIR_OUT, USB_EP_ATTR_BULK, |
| ZYD_USB_USE_ATTR, &uc->pipe_data_out, &uc->ep_data_out) != |
| ZYD_SUCCESS) { |
| ZYD_WARN("failed to open data OUT pipe\n"); |
| goto fail; |
| } |
| |
| if (zyd_usb_open_pipe(uc, alt_if, 2, USB_EP_DIR_IN, USB_EP_ATTR_BULK, |
| ZYD_USB_USE_ATTR, &uc->pipe_data_in, &uc->ep_data_in) != |
| ZYD_SUCCESS) { |
| ZYD_WARN("failed to open data IN pipe\n"); |
| goto fail; |
| } |
| |
| if (zyd_usb_open_pipe(uc, alt_if, 3, USB_EP_DIR_IN, USB_EP_ATTR_INTR, |
| ZYD_USB_USE_ATTR, &uc->pipe_cmd_in, &uc->ep_cmd_in) != |
| ZYD_SUCCESS) { |
| ZYD_WARN("failed to open command IN pipe\n"); |
| goto fail; |
| } |
| |
| /* |
| * Pipe 4/OUT is either a bulk or interrupt pipe. |
| */ |
| if (zyd_usb_open_pipe(uc, alt_if, 4, USB_EP_DIR_OUT, 0, |
| ZYD_USB_NO_ATTR, &uc->pipe_cmd_out, &uc->ep_cmd_out) != |
| ZYD_SUCCESS) { |
| ZYD_WARN("failed to open command OUT pipe\n"); |
| goto fail; |
| } |
| |
| return (ZYD_SUCCESS); |
| |
| fail: |
| zyd_usb_close_pipes(uc); |
| return (ZYD_FAILURE); |
| } |
| |
| /* |
| * Close communication pipes. |
| */ |
| void |
| zyd_usb_close_pipes(struct zyd_usb *uc) |
| { |
| ZYD_DEBUG((ZYD_DBG_USB, "closing pipes\n")); |
| |
| if (uc->pipe_data_out != NULL) { |
| usb_pipe_close(uc->dip, uc->pipe_data_out, USB_FLAGS_SLEEP, |
| NULL, NULL); |
| uc->pipe_data_out = NULL; |
| } |
| |
| if (uc->pipe_data_in != NULL) { |
| usb_pipe_close(uc->dip, uc->pipe_data_in, USB_FLAGS_SLEEP, |
| NULL, NULL); |
| uc->pipe_data_in = NULL; |
| } |
| |
| if (uc->pipe_cmd_in != NULL) { |
| usb_pipe_close(uc->dip, uc->pipe_cmd_in, USB_FLAGS_SLEEP, |
| NULL, NULL); |
| uc->pipe_cmd_in = NULL; |
| } |
| |
| if (uc->pipe_cmd_out != NULL) { |
| usb_pipe_close(uc->dip, uc->pipe_cmd_out, USB_FLAGS_SLEEP, |
| NULL, NULL); |
| uc->pipe_cmd_out = NULL; |
| } |
| } |
| |
| /* |
| * Send a sequence of bytes to a bulk pipe. |
| * |
| * uc pointer to usb module state |
| * data pointer to a buffer of bytes |
| * len size of the buffer (bytes) |
| */ |
| /*ARGSUSED*/ |
| static void |
| zyd_data_out_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req) |
| { |
| struct zyd_softc *sc = (struct zyd_softc *)req->bulk_client_private; |
| struct ieee80211com *ic = &sc->ic; |
| boolean_t resched; |
| |
| if (req->bulk_completion_reason != USB_CR_OK) |
| ZYD_DEBUG((ZYD_DBG_USB, "data OUT exception\n")); |
| |
| (void) zyd_serial_enter(sc, ZYD_NO_SIG); |
| if (sc->tx_queued > 0) |
| sc->tx_queued--; |
| else |
| ZYD_DEBUG((ZYD_DBG_TX, "tx queue underrun\n")); |
| |
| if (sc->resched && (sc->tx_queued < ZYD_TX_LIST_COUNT)) { |
| resched = sc->resched; |
| sc->resched = B_FALSE; |
| } |
| zyd_serial_exit(sc); |
| |
| if (resched) |
| mac_tx_update(ic->ic_mach); |
| |
| usb_free_bulk_req(req); |
| } |
| |
| /* |
| * Called when the transfer from zyd_usb_bulk_pipe_send() terminates |
| * or an exception occurs on the pipe. |
| */ |
| /*ARGSUSED*/ |
| static void |
| zyd_bulk_pipe_cb(usb_pipe_handle_t pipe, struct usb_bulk_req *req) |
| { |
| struct zyd_cb_lock *lock; |
| lock = (struct zyd_cb_lock *)req->bulk_client_private; |
| |
| /* Just signal that something happened */ |
| zyd_cb_lock_signal(lock); |
| } |
| |
| static zyd_res |
| zyd_usb_bulk_pipe_send(struct zyd_usb *uc, |
| usb_pipe_handle_t pipe, const void *data, size_t len) |
| { |
| usb_bulk_req_t *send_req; |
| mblk_t *mblk; |
| int res; |
| struct zyd_cb_lock lock; |
| |
| send_req = usb_alloc_bulk_req(uc->dip, len, USB_FLAGS_SLEEP); |
| if (send_req == NULL) { |
| ZYD_WARN("failed to allocate bulk request\n"); |
| return (ZYD_FAILURE); |
| } |
| send_req->bulk_len = (uint_t)len; |
| send_req->bulk_client_private = (usb_opaque_t)&lock; |
| send_req->bulk_attributes = USB_ATTRS_AUTOCLEARING; |
| send_req->bulk_timeout = 5; |
| send_req->bulk_cb = zyd_bulk_pipe_cb; |
| send_req->bulk_exc_cb = zyd_bulk_pipe_cb; |
| |
| mblk = send_req->bulk_data; |
| bcopy(data, mblk->b_wptr, len); |
| mblk->b_wptr += len; |
| |
| zyd_cb_lock_init(&lock); |
| |
| res = usb_pipe_bulk_xfer(pipe, send_req, USB_FLAGS_NOSLEEP); |
| if (res != USB_SUCCESS) { |
| ZYD_DEBUG((ZYD_DBG_USB, |
| "failed writing to bulk OUT pipe (%d)\n", res)); |
| usb_free_bulk_req(send_req); |
| zyd_cb_lock_destroy(&lock); |
| return (ZYD_FAILURE); |
| } |
| |
| if (zyd_cb_lock_wait(&lock, 1000000) != ZYD_SUCCESS) { |
| ZYD_WARN("timeout - pipe reset\n"); |
| usb_pipe_reset(uc->dip, pipe, USB_FLAGS_SLEEP, NULL, 0); |
| (void) zyd_cb_lock_wait(&lock, -1); |
| res = ZYD_FAILURE; |
| } else { |
| res = (send_req->bulk_completion_reason == USB_CR_OK) ? |
| ZYD_SUCCESS : ZYD_FAILURE; |
| } |
| |
| usb_free_bulk_req(send_req); |
| zyd_cb_lock_destroy(&lock); |
| return (res); |
| } |
| |
| /* |
| * Called when the transfer from zyd_usb_intr_pipe_send() terminates |
| * or an exception occurs on the pipe. |
| */ |
| /*ARGSUSED*/ |
| static void |
| zyd_intr_pipe_cb(usb_pipe_handle_t pipe, struct usb_intr_req *req) |
| { |
| struct zyd_cb_lock *lock; |
| lock = (struct zyd_cb_lock *)req->intr_client_private; |
| |
| /* Just signal that something happened */ |
| zyd_cb_lock_signal(lock); |
| } |
| |
| /* |
| * Send a sequence of bytes to an interrupt pipe. |
| * |
| * uc pointer to usb module state |
| * data pointer to a buffer of bytes |
| * len size of the buffer (bytes) |
| */ |
| static zyd_res |
| zyd_usb_intr_pipe_send(struct zyd_usb *uc, |
| usb_pipe_handle_t pipe, const void *data, size_t len) |
| { |
| usb_intr_req_t *send_req; |
| mblk_t *mblk; |
| int res; |
| struct zyd_cb_lock lock; |
| |
| send_req = usb_alloc_intr_req(uc->dip, len, USB_FLAGS_SLEEP); |
| if (send_req == NULL) { |
| ZYD_WARN("failed to allocate interupt request\n"); |
| return (ZYD_FAILURE); |
| } |
| send_req->intr_len = (uint_t)len; |
| send_req->intr_client_private = (usb_opaque_t)&lock; |
| send_req->intr_attributes = USB_ATTRS_AUTOCLEARING; |
| send_req->intr_timeout = 5; |
| send_req->intr_cb = zyd_intr_pipe_cb; |
| send_req->intr_exc_cb = zyd_intr_pipe_cb; |
| |
| mblk = send_req->intr_data; |
| bcopy(data, mblk->b_wptr, len); |
| mblk->b_wptr += len; |
| |
| zyd_cb_lock_init(&lock); |
| |
| res = usb_pipe_intr_xfer(pipe, send_req, 0); |
| if (res != USB_SUCCESS) { |
| ZYD_DEBUG((ZYD_DBG_USB, |
| "failed writing to intr/out pipe (%d)\n", res)); |
| usb_free_intr_req(send_req); |
| zyd_cb_lock_destroy(&lock); |
| return (ZYD_FAILURE); |
| } |
| |
| if (zyd_cb_lock_wait(&lock, 1000000) != ZYD_SUCCESS) { |
| ZYD_WARN("timeout - pipe reset\n"); |
| usb_pipe_reset(uc->dip, pipe, USB_FLAGS_SLEEP, NULL, 0); |
| (void) zyd_cb_lock_wait(&lock, -1); |
| res = ZYD_FAILURE; |
| } else { |
| res = (send_req->intr_completion_reason == USB_CR_OK) ? |
| ZYD_SUCCESS : ZYD_FAILURE; |
| } |
| |
| usb_free_intr_req(send_req); |
| zyd_cb_lock_destroy(&lock); |
| return (res); |
| } |
| |
| /* |
| * Send a sequence of bytes to the cmd_out pipe. (in a single USB transfer) |
| * |
| * uc pointer to usb module state |
| * data pointer to a buffer of bytes |
| * len size of the buffer (bytes) |
| */ |
| static zyd_res |
| zyd_usb_cmd_pipe_send(struct zyd_usb *uc, const void *data, size_t len) |
| { |
| zyd_res res; |
| uint8_t type; |
| |
| /* Determine the type of cmd_out */ |
| type = uc->ep_cmd_out.ep_descr.bmAttributes & USB_EP_ATTR_MASK; |
| if (type == USB_EP_ATTR_BULK) |
| res = zyd_usb_bulk_pipe_send(uc, uc->pipe_cmd_out, data, len); |
| else |
| res = zyd_usb_intr_pipe_send(uc, uc->pipe_cmd_out, data, len); |
| |
| return (res); |
| } |
| |
| |
| /* |
| * Format and send a command to the cmd_out pipe. |
| * |
| * uc pointer to usb module state |
| * code ZD command code (16-bit) |
| * data raw buffer containing command data |
| * len size of the data buffer (bytes) |
| */ |
| zyd_res |
| zyd_usb_cmd_send(struct zyd_usb *uc, |
| uint16_t code, const void *data, size_t len) |
| { |
| zyd_res res; |
| struct zyd_cmd cmd; |
| |
| cmd.cmd_code = LE_16(code); |
| bcopy(data, cmd.data, len); |
| |
| res = zyd_usb_cmd_pipe_send(uc, &cmd, sizeof (uint16_t) + len); |
| if (res != ZYD_SUCCESS) { |
| ZYD_DEBUG((ZYD_DBG_USB, "failed writing command (%d)\n", res)); |
| return (ZYD_FAILURE); |
| } |
| |
| return (ZYD_SUCCESS); |
| } |
| |
| /* |
| * Issue an ioread request. |
| * |
| * Issues a ZD ioread command (with a vector of addresses passed in raw |
| * form as in_data) and blocks until the response is received |
| * and filled into the response buffer. |
| * |
| * uc pointer to usb module state |
| * in_data pointer to request data |
| * in_len request data size (bytes) |
| * out_data pointer to response buffer |
| * out_len response buffer size (bytes) |
| */ |
| zyd_res |
| zyd_usb_ioread_req(struct zyd_usb *uc, |
| const void *in_data, size_t in_len, void *out_data, size_t out_len) |
| { |
| zyd_res res; |
| int cnt; |
| |
| /* Initialise io_read structure */ |
| uc->io_read.done = B_FALSE; |
| uc->io_read.buffer = out_data; |
| uc->io_read.buf_len = (int)out_len; |
| |
| uc->io_read.pending = B_TRUE; |
| |
| res = zyd_usb_cmd_send(uc, ZYD_CMD_IORD, in_data, in_len); |
| if (res != ZYD_SUCCESS) { |
| ZYD_DEBUG((ZYD_DBG_USB, "IO read request: pipe failure(%d)\n")); |
| return (ZYD_FAILURE); |
| } |
| |
| cnt = 0; |
| while (uc->io_read.done != B_TRUE && cnt < 500) { |
| delay(drv_usectohz(10 * 1000)); |
| ++cnt; |
| } |
| |
| if (uc->io_read.done != B_TRUE) { |
| ZYD_WARN("I/O read request: timeout\n"); |
| return (ZYD_FAILURE); |
| } |
| |
| if (uc->io_read.exc != B_FALSE) { |
| ZYD_DEBUG((ZYD_DBG_USB, "I/O read request: exception\n")); |
| return (ZYD_FAILURE); |
| } |
| |
| return (ZYD_SUCCESS); |
| } |
| |
| |
| /* |
| * Called when data arrives from the cmd_in pipe. |
| */ |
| /*ARGSUSED*/ |
| static void |
| zyd_cmd_in_cb(usb_pipe_handle_t pipe, usb_intr_req_t *req) |
| { |
| struct zyd_usb *uc; |
| struct zyd_ioread *rdp; |
| mblk_t *mblk, *tmp_blk; |
| unsigned char *data; |
| size_t len; |
| uint16_t code; |
| |
| uc = (struct zyd_usb *)req->intr_client_private; |
| ASSERT(uc != NULL); |
| rdp = &uc->io_read; |
| mblk = req->intr_data; |
| |
| if (mblk->b_cont != NULL) { |
| /* Fragmented message, concatenate */ |
| tmp_blk = msgpullup(mblk, -1); |
| data = tmp_blk->b_rptr; |
| len = MBLKL(tmp_blk); |
| } else { |
| /* Non-fragmented message, use directly */ |
| tmp_blk = NULL; |
| data = mblk->b_rptr; |
| len = MBLKL(mblk); |
| } |
| |
| code = LE_16(*(uint16_t *)(uintptr_t)data); |
| if (code != ZYD_RESPONSE_IOREAD) { |
| /* Other response types not handled yet */ |
| usb_free_intr_req(req); |
| return; |
| } |
| |
| if (rdp->pending != B_TRUE) { |
| ZYD_WARN("no ioread pending\n"); |
| usb_free_intr_req(req); |
| return; |
| } |
| rdp->pending = B_FALSE; |
| |
| /* Now move on to the data part */ |
| data += sizeof (uint16_t); |
| len -= sizeof (uint16_t); |
| if (rdp->buf_len > len) { |
| ZYD_WARN("too few bytes received\n"); |
| } |
| |
| bcopy(data, rdp->buffer, rdp->buf_len); |
| |
| if (tmp_blk != NULL) |
| freemsg(tmp_blk); |
| |
| rdp->exc = B_FALSE; |
| rdp->done = B_TRUE; |
| usb_free_intr_req(req); |
| } |
| |
| /* |
| * Called when an exception occurs on the cmd_in pipe. |
| */ |
| /*ARGSUSED*/ |
| static void |
| zyd_cmd_in_exc_cb(usb_pipe_handle_t pipe, usb_intr_req_t *req) |
| { |
| struct zyd_usb *uc; |
| struct zyd_ioread *rdp; |
| |
| ZYD_DEBUG((ZYD_DBG_USB, "command IN exception\n")); |
| |
| uc = (struct zyd_usb *)req->intr_client_private; |
| ASSERT(uc != NULL); |
| rdp = &uc->io_read; |
| |
| if (rdp->pending == B_TRUE) { |
| rdp->exc = B_TRUE; |
| rdp->done = B_TRUE; |
| } |
| usb_free_intr_req(req); |
| } |
| |
| /* |
| * Start interrupt polling on the cmd_in pipe. |
| */ |
| zyd_res |
| zyd_usb_cmd_in_start_polling(struct zyd_usb *uc) |
| { |
| usb_intr_req_t *intr_req; |
| int res; |
| |
| intr_req = usb_alloc_intr_req(uc->dip, 0, USB_FLAGS_SLEEP); |
| if (intr_req == NULL) { |
| ZYD_WARN("failed to allocate interrupt request\n"); |
| return (ZYD_FAILURE); |
| } |
| |
| intr_req->intr_attributes = USB_ATTRS_SHORT_XFER_OK; |
| intr_req->intr_len = uc->ep_cmd_in.ep_descr.wMaxPacketSize; |
| intr_req->intr_cb = zyd_cmd_in_cb; |
| intr_req->intr_exc_cb = zyd_cmd_in_exc_cb; |
| intr_req->intr_client_private = (usb_opaque_t)uc; |
| |
| res = usb_pipe_intr_xfer(uc->pipe_cmd_in, intr_req, USB_FLAGS_NOSLEEP); |
| if (res != USB_SUCCESS) { |
| ZYD_WARN("failed starting command IN polling: pipe failure\n"); |
| usb_free_intr_req(intr_req); |
| return (ZYD_FAILURE); |
| } |
| |
| return (ZYD_SUCCESS); |
| } |
| |
| /* |
| * Stop interrupt polling on the cmd_in pipe. |
| */ |
| void |
| zyd_usb_cmd_in_stop_polling(struct zyd_usb *uc) |
| { |
| ZYD_DEBUG((ZYD_DBG_USB, "stopping command IN polling\n")); |
| |
| usb_pipe_stop_intr_polling(uc->pipe_cmd_in, USB_FLAGS_SLEEP); |
| } |
| |
| /* |
| * Called when data arrives on the data_in pipe. |
| */ |
| /*ARGSUSED*/ |
| static void |
| zyd_data_in_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req) |
| { |
| struct zyd_softc *sc; |
| struct zyd_usb *uc; |
| mblk_t *mblk, *tmp_blk; |
| struct zyd_rx_desc *desc; |
| unsigned char *data; |
| size_t len; |
| |
| uc = (struct zyd_usb *)req->bulk_client_private; |
| ASSERT(uc != NULL); |
| sc = ZYD_USB_TO_SOFTC(uc); |
| ASSERT(sc != NULL); |
| mblk = req->bulk_data; |
| |
| /* Fragmented STREAMS message? */ |
| if (mblk->b_cont != NULL) { |
| /* Fragmented, concatenate it into a single block */ |
| tmp_blk = msgpullup(mblk, -1); |
| if (tmp_blk == NULL) { |
| ZYD_WARN("failed to concatenate fragments\n"); |
| goto error; |
| } |
| data = tmp_blk->b_rptr; |
| len = MBLKL(tmp_blk); |
| } else { |
| /* Not fragmented, use directly */ |
| tmp_blk = NULL; |
| data = mblk->b_rptr; |
| len = MBLKL(mblk); |
| } |
| |
| if (len < 2) { |
| ZYD_WARN("received usb transfer too short\n"); |
| goto error; |
| } |
| |
| /* |
| * If this is a composite packet, the last two bytes contain |
| * two special signature bytes. |
| */ |
| desc = (struct zyd_rx_desc *)(data + len) - 1; |
| /* multi-frame transfer */ |
| if (LE_16(desc->tag) == ZYD_TAG_MULTIFRAME) { |
| const uint8_t *p = data, *end = data + len; |
| int i; |
| |
| ZYD_DEBUG((ZYD_DBG_RX, "composite packet\n")); |
| |
| for (i = 0; i < ZYD_MAX_RXFRAMECNT; i++) { |
| const uint16_t len16 = LE_16(desc->len[i]); |
| if (len16 == 0 || p + len16 > end) |
| break; |
| zyd_receive(ZYD_USB_TO_SOFTC(uc), p, len16); |
| /* next frame is aligned on a 32-bit boundary */ |
| p += (len16 + 3) & ~3; |
| } |
| } else { |
| /* single-frame transfer */ |
| zyd_receive(ZYD_USB_TO_SOFTC(uc), data, MBLKL(mblk)); |
| } |
| |
| error: |
| if (tmp_blk != NULL) |
| freemsg(tmp_blk); |
| |
| usb_free_bulk_req(req); |
| |
| if (!sc->running) |
| return; |
| |
| if (zyd_usb_data_in_start_request(uc) != ZYD_SUCCESS) { |
| ZYD_WARN("error restarting data_in transfer\n"); |
| } |
| } |
| |
| /* |
| * Called when an exception occurs on the data_in pipe. |
| */ |
| /*ARGSUSED*/ |
| static void |
| zyd_data_in_exc_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req) |
| { |
| struct zyd_usb *uc; |
| |
| ZYD_DEBUG((ZYD_DBG_USB, "data IN exception\n")); |
| |
| uc = (struct zyd_usb *)req->bulk_client_private; |
| ASSERT(uc != NULL); |
| |
| usb_free_bulk_req(req); |
| } |
| |
| /* |
| * Start a receive request on the data_in pipe. |
| */ |
| static zyd_res |
| zyd_usb_data_in_start_request(struct zyd_usb *uc) |
| { |
| usb_bulk_req_t *req; |
| int res; |
| |
| req = usb_alloc_bulk_req(uc->dip, ZYD_RX_BUF_SIZE, USB_FLAGS_SLEEP); |
| if (req == NULL) { |
| ZYD_WARN("failed to allocate bulk IN request\n"); |
| return (ZYD_FAILURE); |
| } |
| |
| req->bulk_len = (uint_t)ZYD_RX_BUF_SIZE; |
| req->bulk_timeout = 0; |
| req->bulk_client_private = (usb_opaque_t)uc; |
| req->bulk_attributes = |
| USB_ATTRS_SHORT_XFER_OK | USB_ATTRS_AUTOCLEARING; |
| req->bulk_cb = zyd_data_in_cb; |
| req->bulk_exc_cb = zyd_data_in_exc_cb; |
| |
| res = usb_pipe_bulk_xfer(uc->pipe_data_in, req, USB_FLAGS_NOSLEEP); |
| if (res != USB_SUCCESS) { |
| ZYD_WARN("error starting receive request on data_in pipe\n"); |
| usb_free_bulk_req(req); |
| return (ZYD_FAILURE); |
| } |
| |
| return (ZYD_SUCCESS); |
| } |
| |
| |
| /* |
| * Start receiving packets on the data_in pipe. |
| */ |
| zyd_res |
| zyd_usb_data_in_enable(struct zyd_usb *uc) |
| { |
| for (int i = 0; i < ZYD_RX_LIST_COUNT; i++) { |
| if (zyd_usb_data_in_start_request(uc) != ZYD_SUCCESS) { |
| ZYD_WARN("failed to start data IN requests\n"); |
| return (ZYD_FAILURE); |
| } |
| } |
| return (ZYD_SUCCESS); |
| } |
| |
| /* |
| * Stop receiving packets on the data_in pipe. |
| */ |
| void |
| zyd_usb_data_in_disable(struct zyd_usb *uc) |
| { |
| usb_pipe_reset(uc->dip, uc->pipe_data_in, USB_FLAGS_SLEEP, |
| NULL, NULL); |
| } |
| |
| /* |
| * Send a packet to data_out. |
| * |
| * A packet consists of a zyd_tx_header + the IEEE802.11 frame. |
| */ |
| zyd_res |
| zyd_usb_send_packet(struct zyd_usb *uc, mblk_t *mp) |
| { |
| usb_bulk_req_t *send_req; |
| int res; |
| |
| send_req = usb_alloc_bulk_req(uc->dip, 0, USB_FLAGS_SLEEP); |
| if (send_req == NULL) { |
| ZYD_WARN("failed to allocate bulk request\n"); |
| return (ZYD_FAILURE); |
| } |
| |
| send_req->bulk_len = msgdsize(mp); |
| send_req->bulk_data = mp; |
| send_req->bulk_timeout = 5; |
| send_req->bulk_attributes = USB_ATTRS_AUTOCLEARING; |
| send_req->bulk_client_private = (usb_opaque_t)ZYD_USB_TO_SOFTC(uc); |
| send_req->bulk_cb = zyd_data_out_cb; |
| send_req->bulk_exc_cb = zyd_data_out_cb; |
| send_req->bulk_completion_reason = 0; |
| send_req->bulk_cb_flags = 0; |
| |
| res = usb_pipe_bulk_xfer(uc->pipe_data_out, send_req, 0); |
| if (res != USB_SUCCESS) { |
| ZYD_DEBUG((ZYD_DBG_USB, |
| "failed writing to bulk/out pipe (%d)\n", res)); |
| usb_free_bulk_req(send_req); |
| return (USB_FAILURE); |
| } |
| |
| return (USB_SUCCESS); |
| } |
| |
| /* |
| * Initialize USB device communication and USB module state. |
| * |
| * uc pointer to usb module state |
| * dip pointer to device info structure |
| */ |
| zyd_res |
| zyd_usb_init(struct zyd_softc *sc) |
| { |
| struct zyd_usb *uc = &sc->usb; |
| dev_info_t *dip = sc->dip; |
| int ures; |
| |
| uc->dip = dip; |
| |
| ures = usb_client_attach(uc->dip, USBDRV_VERSION, 0); |
| if (ures != USB_SUCCESS) { |
| ZYD_WARN("usb_client_attach failed, error code: %d\n", ures); |
| return (ZYD_FAILURE); |
| } |
| |
| /* |
| * LVL_ALL is needed for later endpoint scanning, |
| * and the tree must not be freed before that. |
| */ |
| ures = usb_get_dev_data(uc->dip, &uc->cdata, USB_PARSE_LVL_ALL, 0); |
| if (ures != USB_SUCCESS) { |
| ZYD_WARN("usb_get_dev_data failed, error code: %d\n", ures); |
| ASSERT(uc->cdata == NULL); |
| goto fail; |
| } |
| |
| ures = usb_reset_device(uc->dip, USB_RESET_LVL_DEFAULT); |
| if (ures != USB_SUCCESS) { |
| ZYD_WARN("usb_reset_device failed, error code: %d\n", ures); |
| goto fail; |
| } |
| |
| uc->connected = B_TRUE; |
| |
| ures = usb_register_hotplug_cbs(dip, zyd_usb_disconnect, |
| zyd_usb_reconnect); |
| if (ures != USB_SUCCESS) { |
| ZYD_WARN("usb_register_hotplug_cbs failed, error code: %d\n", |
| ures); |
| goto fail; |
| } |
| |
| return (ZYD_SUCCESS); |
| fail: |
| usb_client_detach(uc->dip, uc->cdata); |
| uc->cdata = NULL; |
| return (ZYD_FAILURE); |
| } |
| |
| /* |
| * Deinitialize USB device communication. |
| */ |
| void |
| zyd_usb_deinit(struct zyd_softc *sc) |
| { |
| struct zyd_usb *uc = &sc->usb; |
| |
| usb_unregister_hotplug_cbs(sc->dip); |
| |
| usb_client_detach(uc->dip, uc->cdata); |
| uc->cdata = NULL; |
| uc->connected = B_FALSE; |
| } |
| |
| /* |
| * Device connected |
| */ |
| static int |
| zyd_usb_reconnect(dev_info_t *dip) |
| { |
| struct zyd_softc *sc; |
| struct zyd_usb *uc; |
| |
| sc = ddi_get_soft_state(zyd_ssp, ddi_get_instance(dip)); |
| ASSERT(sc != NULL); |
| uc = &sc->usb; |
| ASSERT(!uc->connected); |
| |
| if (sc->suspended) |
| ZYD_DEBUG((ZYD_DBG_RESUME | ZYD_DBG_USB, |
| "reconnect before resume\n")); |
| |
| /* check device changes after disconnect */ |
| if (usb_check_same_device(sc->dip, NULL, USB_LOG_L2, -1, |
| USB_CHK_BASIC | USB_CHK_CFG, NULL) != USB_SUCCESS) { |
| ZYD_DEBUG((ZYD_DBG_USB, "different device connected\n")); |
| return (DDI_FAILURE); |
| } |
| |
| (void) zyd_serial_enter(sc, ZYD_NO_SIG); |
| if (zyd_hw_init(sc) != ZYD_SUCCESS) { |
| ZYD_WARN("failed to reinit hardware\n"); |
| zyd_serial_exit(sc); |
| return (DDI_FAILURE); |
| } |
| if (sc->running) { |
| if (zyd_hw_start(sc) != ZYD_SUCCESS) { |
| ZYD_WARN("failed to restart hardware\n"); |
| zyd_serial_exit(sc); |
| goto fail; |
| } |
| } |
| zyd_serial_exit(sc); |
| |
| uc->connected = B_TRUE; |
| |
| return (DDI_SUCCESS); |
| fail: |
| usb_client_detach(uc->dip, uc->cdata); |
| uc->cdata = NULL; |
| return (DDI_FAILURE); |
| } |
| |
| static int |
| zyd_usb_disconnect(dev_info_t *dip) |
| { |
| struct zyd_softc *sc; |
| struct zyd_usb *uc; |
| |
| sc = ddi_get_soft_state(zyd_ssp, ddi_get_instance(dip)); |
| ASSERT(sc != NULL); |
| uc = &sc->usb; |
| |
| if (!uc->connected) { |
| ZYD_DEBUG((ZYD_DBG_USB, "different device disconnected\n")); |
| return (DDI_FAILURE); |
| } |
| uc->connected = B_FALSE; |
| |
| if (sc->suspended) { |
| ZYD_DEBUG((ZYD_DBG_USB, "disconnect after suspend\n")); |
| return (DDI_SUCCESS); |
| } |
| ieee80211_new_state(&sc->ic, IEEE80211_S_INIT, -1); |
| |
| (void) zyd_serial_enter(sc, ZYD_NO_SIG); |
| zyd_hw_stop(sc); |
| zyd_hw_deinit(sc); |
| zyd_serial_exit(sc); |
| |
| return (DDI_SUCCESS); |
| } |
| |
| int |
| zyd_suspend(struct zyd_softc *sc) |
| { |
| struct zyd_usb *uc = &sc->usb; |
| |
| if (!uc->connected) { |
| ZYD_DEBUG((ZYD_DBG_USB | ZYD_DBG_RESUME, |
| "suspend after disconnect\n")); |
| sc->suspended = B_TRUE; |
| return (DDI_SUCCESS); |
| } |
| ZYD_DEBUG((ZYD_DBG_RESUME, "suspend\n")); |
| |
| sc->suspended = B_TRUE; |
| ieee80211_new_state(&sc->ic, IEEE80211_S_INIT, -1); |
| |
| (void) zyd_serial_enter(sc, ZYD_NO_SIG); |
| zyd_hw_stop(sc); |
| zyd_hw_deinit(sc); |
| zyd_serial_exit(sc); |
| |
| ZYD_DEBUG((ZYD_DBG_RESUME, "suspend complete\n")); |
| return (DDI_SUCCESS); |
| } |
| |
| int |
| zyd_resume(struct zyd_softc *sc) |
| { |
| struct zyd_usb *uc = &sc->usb; |
| |
| if (!uc->connected) { |
| ZYD_DEBUG((ZYD_DBG_USB | ZYD_DBG_RESUME, |
| "resume after disconnect\n")); |
| sc->suspended = B_FALSE; |
| return (DDI_SUCCESS); |
| } |
| ZYD_DEBUG((ZYD_DBG_RESUME, "resume\n")); |
| |
| /* check device changes after disconnect */ |
| if (usb_check_same_device(sc->dip, NULL, USB_LOG_L2, -1, |
| USB_CHK_BASIC | USB_CHK_CFG, NULL) != USB_SUCCESS) { |
| ZYD_WARN("different device connected to same port\n"); |
| sc->suspended = B_FALSE; |
| uc->connected = B_FALSE; |
| return (DDI_SUCCESS); |
| } |
| |
| (void) zyd_serial_enter(sc, ZYD_NO_SIG); |
| if (zyd_hw_init(sc) != ZYD_SUCCESS) { |
| ZYD_WARN("failed to reinit hardware\n"); |
| zyd_serial_exit(sc); |
| return (DDI_FAILURE); |
| } |
| if (sc->running) { |
| if (zyd_hw_start(sc) != ZYD_SUCCESS) { |
| ZYD_WARN("failed to restart hardware\n"); |
| zyd_serial_exit(sc); |
| return (DDI_FAILURE); |
| } |
| } |
| zyd_serial_exit(sc); |
| |
| sc->suspended = B_FALSE; |
| |
| ZYD_DEBUG((ZYD_DBG_RESUME, "resume complete\n")); |
| return (DDI_SUCCESS); |
| } |