| /*********************************************************************** |
| ** Copyright (C) 2003 ACX100 Open Source Project |
| ** |
| ** The contents of this file are subject to the Mozilla Public |
| ** License Version 1.1 (the "License"); you may not use this file |
| ** except in compliance with the License. You may obtain a copy of |
| ** the License at http://www.mozilla.org/MPL/ |
| ** |
| ** Software distributed under the License is distributed on an "AS |
| ** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or |
| ** implied. See the License for the specific language governing |
| ** rights and limitations under the License. |
| ** |
| ** Alternatively, the contents of this file may be used under the |
| ** terms of the GNU Public License version 2 (the "GPL"), in which |
| ** case the provisions of the GPL are applicable instead of the |
| ** above. If you wish to allow the use of your version of this file |
| ** only under the terms of the GPL and not to allow others to use |
| ** your version of this file under the MPL, indicate your decision |
| ** by deleting the provisions above and replace them with the notice |
| ** and other provisions required by the GPL. If you do not delete |
| ** the provisions above, a recipient may use your version of this |
| ** file under either the MPL or the GPL. |
| ** --------------------------------------------------------------------- |
| ** Inquiries regarding the ACX100 Open Source Project can be |
| ** made directly to: |
| ** |
| ** acx100-users@lists.sf.net |
| ** http://acx100.sf.net |
| ** --------------------------------------------------------------------- |
| */ |
| |
| /*********************************************************************** |
| ** USB support for TI ACX100 based devices. Many parts are taken from |
| ** the PCI driver. |
| ** |
| ** Authors: |
| ** Martin Wawro <martin.wawro AT uni-dortmund.de> |
| ** Andreas Mohr <andi AT lisas.de> |
| ** |
| ** LOCKING |
| ** callback functions called by USB core are running in interrupt context |
| ** and thus have names with _i_. |
| */ |
| #define ACX_USB 1 |
| |
| #include <linux/version.h> |
| #include <linux/types.h> |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/kernel.h> |
| #include <linux/usb.h> |
| #include <linux/netdevice.h> |
| #include <linux/rtnetlink.h> |
| #include <linux/etherdevice.h> |
| #include <linux/wireless.h> |
| #include <net/iw_handler.h> |
| #include <linux/vmalloc.h> |
| #include <linux/workqueue.h> |
| |
| #include "acx.h" |
| |
| |
| /*********************************************************************** |
| */ |
| /* number of endpoints of an interface */ |
| #define NUM_EP(intf) (intf)->altsetting[0].desc.bNumEndpoints |
| #define EP(intf, nr) (intf)->altsetting[0].endpoint[(nr)].desc |
| #define GET_DEV(udev) usb_get_dev((udev)) |
| #define PUT_DEV(udev) usb_put_dev((udev)) |
| #define SET_NETDEV_OWNER(ndev, owner) /* not needed anymore ??? */ |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14) |
| /* removed in 2.6.14. We will use fake value for now */ |
| #define URB_ASYNC_UNLINK 0 |
| #endif |
| |
| |
| /*********************************************************************** |
| */ |
| /* ACX100 (TNETW1100) USB device: D-Link DWL-120+ */ |
| #define ACX100_VENDOR_ID 0x2001 |
| #define ACX100_PRODUCT_ID_UNBOOTED 0x3B01 |
| #define ACX100_PRODUCT_ID_BOOTED 0x3B00 |
| |
| /* TNETW1450 USB devices */ |
| #define VENDOR_ID_DLINK 0x07b8 /* D-Link Corp. */ |
| #define PRODUCT_ID_WUG2400 0xb21a /* AboCom WUG2400 or SafeCom SWLUT-54125 */ |
| #define VENDOR_ID_AVM_GMBH 0x057c |
| #define PRODUCT_ID_AVM_WLAN_USB 0x5601 |
| #define VENDOR_ID_ZCOM 0x0cde |
| #define PRODUCT_ID_ZCOM_XG750 0x0017 /* not tested yet */ |
| #define VENDOR_ID_TI 0x0451 |
| #define PRODUCT_ID_TI_UNKNOWN 0x60c5 /* not tested yet */ |
| |
| #define ACX_USB_CTRL_TIMEOUT 5500 /* steps in ms */ |
| |
| /* Buffer size for fw upload, same for both ACX100 USB and TNETW1450 */ |
| #define USB_RWMEM_MAXLEN 2048 |
| |
| /* The number of bulk URBs to use */ |
| #define ACX_TX_URB_CNT 8 |
| #define ACX_RX_URB_CNT 2 |
| |
| /* Should be sent to the bulkout endpoint */ |
| #define ACX_USB_REQ_UPLOAD_FW 0x10 |
| #define ACX_USB_REQ_ACK_CS 0x11 |
| #define ACX_USB_REQ_CMD 0x12 |
| |
| /*********************************************************************** |
| ** Prototypes |
| */ |
| static int acxusb_e_probe(struct usb_interface *, const struct usb_device_id *); |
| static void acxusb_e_disconnect(struct usb_interface *); |
| static void acxusb_i_complete_tx(struct urb *); |
| static void acxusb_i_complete_rx(struct urb *); |
| static int acxusb_e_open(struct net_device *); |
| static int acxusb_e_close(struct net_device *); |
| static void acxusb_i_set_rx_mode(struct net_device *); |
| static int acxusb_boot(struct usb_device *, int is_tnetw1450, int *radio_type); |
| |
| static void acxusb_l_poll_rx(acx_device_t *adev, usb_rx_t* rx); |
| |
| static void acxusb_i_tx_timeout(struct net_device *); |
| |
| #if ACX_DEBUG |
| static int acxusb_s_issue_cmd_timeo_debug(acx_device_t *adev, unsigned cmd, void *param, unsigned len, unsigned timeout, const char* cmdstr); |
| #else |
| static int acxusb_s_issue_cmd_timeo(acx_device_t *adev, unsigned cmd, void *param, unsigned len, unsigned timeout); |
| #endif |
| static int acxusb_s_read_phy_reg(acx_device_t *adev, u32 reg, u8 *charbuf); |
| static int acxusb_s_write_phy_reg(acx_device_t *adev, u32 reg, u8 value); |
| static tx_t* acxusb_l_alloc_tx(acx_device_t *adev); |
| static void acxusb_l_dealloc_tx(tx_t *tx_opaque); |
| static void* acxusb_l_get_txbuf(acx_device_t *adev, tx_t *tx_opaque); |
| static void acxusb_l_tx_data(acx_device_t *adev, tx_t *tx_opaque, int len); |
| |
| /* static void dump_device(struct usb_device *); */ |
| /* static void dump_device_descriptor(struct usb_device_descriptor *); */ |
| /* static void dump_config_descriptor(struct usb_config_descriptor *); */ |
| |
| #ifdef MODULE_LICENSE |
| MODULE_LICENSE("Dual MPL/GPL"); |
| #endif |
| /* USB had this: MODULE_AUTHOR("Martin Wawro <martin.wawro AT uni-dortmund.de>"); */ |
| MODULE_AUTHOR("ACX100 Open Source Driver development team"); |
| MODULE_DESCRIPTION("Driver for TI ACX1xx based wireless cards (USB)"); |
| |
| |
| /*********************************************************************** |
| ** Module Data |
| */ |
| #define TXBUFSIZE sizeof(usb_txbuffer_t) |
| /* |
| * Now, this is just plain lying, but the device insists in giving us |
| * huge packets. We supply extra space after rxbuffer. Need to understand |
| * it better... |
| */ |
| #define RXBUFSIZE (sizeof(rxbuffer_t) + \ |
| (sizeof(usb_rx_t) - sizeof(struct usb_rx_plain))) |
| |
| static const struct usb_device_id |
| acxusb_ids[] = { |
| { USB_DEVICE(ACX100_VENDOR_ID, ACX100_PRODUCT_ID_BOOTED) }, |
| { USB_DEVICE(ACX100_VENDOR_ID, ACX100_PRODUCT_ID_UNBOOTED) }, |
| { USB_DEVICE(VENDOR_ID_DLINK, PRODUCT_ID_WUG2400) }, |
| { USB_DEVICE(VENDOR_ID_AVM_GMBH, PRODUCT_ID_AVM_WLAN_USB) }, |
| { USB_DEVICE(VENDOR_ID_ZCOM, PRODUCT_ID_ZCOM_XG750) }, |
| { USB_DEVICE(VENDOR_ID_TI, PRODUCT_ID_TI_UNKNOWN) }, |
| {} |
| }; |
| |
| MODULE_DEVICE_TABLE(usb, acxusb_ids); |
| |
| /* USB driver data structure as required by the kernel's USB core */ |
| static struct usb_driver |
| acxusb_driver = { |
| .name = "acx_usb", |
| .probe = acxusb_e_probe, |
| .disconnect = acxusb_e_disconnect, |
| .id_table = acxusb_ids |
| }; |
| |
| |
| /*********************************************************************** |
| ** USB helper |
| ** |
| ** ldd3 ch13 says: |
| ** When the function is usb_kill_urb, the urb lifecycle is stopped. This |
| ** function is usually used when the device is disconnected from the system, |
| ** in the disconnect callback. For some drivers, the usb_unlink_urb function |
| ** should be used to tell the USB core to stop an urb. This function does not |
| ** wait for the urb to be fully stopped before returning to the caller. |
| ** This is useful for stoppingthe urb while in an interrupt handler or when |
| ** a spinlock is held, as waiting for a urb to fully stop requires the ability |
| ** for the USB core to put the calling process to sleep. This function requires |
| ** that the URB_ASYNC_UNLINK flag value be set in the urb that is being asked |
| ** to be stopped in order to work properly. |
| ** |
| ** (URB_ASYNC_UNLINK is obsolete, usb_unlink_urb will always be |
| ** asynchronous while usb_kill_urb is synchronous and should be called |
| ** directly (drivers/usb/core/urb.c)) |
| ** |
| ** In light of this, timeout is just for paranoid reasons... |
| ** |
| ** Actually, it's useful for debugging. If we reach timeout, we're doing |
| ** something wrong with the urbs. |
| */ |
| static void |
| acxusb_unlink_urb(struct urb* urb) |
| { |
| if (!urb) |
| return; |
| |
| if (urb->status == -EINPROGRESS) { |
| int timeout = 10; |
| |
| usb_unlink_urb(urb); |
| while (--timeout && urb->status == -EINPROGRESS) { |
| mdelay(1); |
| } |
| if (!timeout) { |
| printk("acx_usb: urb unlink timeout!\n"); |
| } |
| } |
| } |
| |
| |
| /*********************************************************************** |
| ** EEPROM and PHY read/write helpers |
| */ |
| /*********************************************************************** |
| ** acxusb_s_read_phy_reg |
| */ |
| static int |
| acxusb_s_read_phy_reg(acx_device_t *adev, u32 reg, u8 *charbuf) |
| { |
| /* mem_read_write_t mem; */ |
| |
| FN_ENTER; |
| |
| printk("read_phy_reg doesn't seem to work yet, disabled\n"); |
| |
| /* |
| mem.addr = cpu_to_le16(reg); |
| mem.type = cpu_to_le16(0x82); |
| mem.len = cpu_to_le32(4); |
| acx_s_issue_cmd(adev, ACX1xx_CMD_MEM_READ, &mem, sizeof(mem)); |
| *charbuf = mem.data; |
| log(L_DEBUG, "read radio PHY[0x%04X]=0x%02X\n", reg, *charbuf); |
| */ |
| |
| FN_EXIT1(OK); |
| return OK; |
| } |
| |
| |
| /*********************************************************************** |
| */ |
| static int |
| acxusb_s_write_phy_reg(acx_device_t *adev, u32 reg, u8 value) |
| { |
| mem_read_write_t mem; |
| |
| FN_ENTER; |
| |
| mem.addr = cpu_to_le16(reg); |
| mem.type = cpu_to_le16(0x82); |
| mem.len = cpu_to_le32(4); |
| mem.data = value; |
| acx_s_issue_cmd(adev, ACX1xx_CMD_MEM_WRITE, &mem, sizeof(mem)); |
| log(L_DEBUG, "write radio PHY[0x%04X]=0x%02X\n", reg, value); |
| |
| FN_EXIT1(OK); |
| return OK; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxusb_s_issue_cmd_timeo |
| ** Excecutes a command in the command mailbox |
| ** |
| ** buffer = a pointer to the data. |
| ** The data must not include 4 byte command header |
| */ |
| |
| /* TODO: ideally we shall always know how much we need |
| ** and this shall be 0 */ |
| #define BOGUS_SAFETY_PADDING 0x40 |
| |
| #undef FUNC |
| #define FUNC "issue_cmd" |
| |
| static int |
| #if !ACX_DEBUG |
| acxusb_s_issue_cmd_timeo( |
| acx_device_t *adev, |
| unsigned cmd, |
| void *buffer, |
| unsigned buflen, |
| unsigned timeout) |
| #else |
| acxusb_s_issue_cmd_timeo_debug( |
| acx_device_t *adev, |
| unsigned cmd, |
| void *buffer, |
| unsigned buflen, |
| unsigned timeout, |
| const char* cmdstr) |
| #endif |
| { |
| /* USB ignores timeout param */ |
| |
| struct usb_device *usbdev; |
| struct { |
| u16 cmd; |
| u16 status; |
| u8 data[1]; |
| } ACX_PACKED *loc; |
| const char *devname; |
| int acklen, blocklen, inpipe, outpipe; |
| int cmd_status; |
| int result; |
| |
| FN_ENTER; |
| |
| devname = adev->ndev->name; |
| /* no "wlan%%d: ..." please */ |
| if (!devname || !devname[0] || devname[3]=='%') |
| devname = "acx"; |
| |
| log(L_CTL, FUNC"(cmd:%s,buflen:%u,type:0x%04X)\n", |
| cmdstr, buflen, |
| buffer ? le16_to_cpu(((acx_ie_generic_t *)buffer)->type) : -1); |
| |
| loc = kmalloc(buflen + 4 + BOGUS_SAFETY_PADDING, GFP_KERNEL); |
| if (!loc) { |
| printk("%s: "FUNC"(): no memory for data buffer\n", devname); |
| goto bad; |
| } |
| |
| /* get context from acx_device */ |
| usbdev = adev->usbdev; |
| |
| /* check which kind of command was issued */ |
| loc->cmd = cpu_to_le16(cmd); |
| loc->status = 0; |
| |
| /* NB: buflen == frmlen + 4 |
| ** |
| ** Interrogate: write 8 bytes: (cmd,status,rid,frmlen), then |
| ** read (cmd,status,rid,frmlen,data[frmlen]) back |
| ** |
| ** Configure: write (cmd,status,rid,frmlen,data[frmlen]) |
| ** |
| ** Possibly bogus special handling of ACX1xx_IE_SCAN_STATUS removed |
| */ |
| |
| /* now write the parameters of the command if needed */ |
| acklen = buflen + 4 + BOGUS_SAFETY_PADDING; |
| blocklen = buflen; |
| if (buffer && buflen) { |
| /* if it's an INTERROGATE command, just pass the length |
| * of parameters to read, as data */ |
| if (cmd == ACX1xx_CMD_INTERROGATE) { |
| blocklen = 4; |
| acklen = buflen + 4; |
| } |
| memcpy(loc->data, buffer, blocklen); |
| } |
| blocklen += 4; /* account for cmd,status */ |
| |
| /* obtain the I/O pipes */ |
| outpipe = usb_sndctrlpipe(usbdev, 0); |
| inpipe = usb_rcvctrlpipe(usbdev, 0); |
| log(L_CTL, "ctrl inpipe=0x%X outpipe=0x%X\n", inpipe, outpipe); |
| log(L_CTL, "sending USB control msg (out) (blocklen=%d)\n", blocklen); |
| if (acx_debug & L_DATA) |
| acx_dump_bytes(loc, blocklen); |
| |
| result = usb_control_msg(usbdev, outpipe, |
| ACX_USB_REQ_CMD, /* request */ |
| USB_TYPE_VENDOR|USB_DIR_OUT, /* requesttype */ |
| 0, /* value */ |
| 0, /* index */ |
| loc, /* dataptr */ |
| blocklen, /* size */ |
| ACX_USB_CTRL_TIMEOUT /* timeout in ms */ |
| ); |
| |
| if (result == -ENODEV) { |
| log(L_CTL, "no device present (unplug?)\n"); |
| goto good; |
| } |
| |
| log(L_CTL, "wrote %d bytes\n", result); |
| if (result < 0) { |
| goto bad; |
| } |
| |
| /* check for device acknowledge */ |
| log(L_CTL, "sending USB control msg (in) (acklen=%d)\n", acklen); |
| loc->status = 0; /* delete old status flag -> set to IDLE */ |
| /* shall we zero out the rest? */ |
| result = usb_control_msg(usbdev, inpipe, |
| ACX_USB_REQ_CMD, /* request */ |
| USB_TYPE_VENDOR|USB_DIR_IN, /* requesttype */ |
| 0, /* value */ |
| 0, /* index */ |
| loc, /* dataptr */ |
| acklen, /* size */ |
| ACX_USB_CTRL_TIMEOUT /* timeout in ms */ |
| ); |
| if (result < 0) { |
| printk("%s: "FUNC"(): USB read error %d\n", devname, result); |
| goto bad; |
| } |
| if (acx_debug & L_CTL) { |
| printk("read %d bytes: ", result); |
| acx_dump_bytes(loc, result); |
| } |
| |
| /* |
| check for result==buflen+4? Was seen: |
| |
| interrogate(type:ACX100_IE_DOT11_ED_THRESHOLD,len:4) |
| issue_cmd(cmd:ACX1xx_CMD_INTERROGATE,buflen:8,type:4111) |
| ctrl inpipe=0x80000280 outpipe=0x80000200 |
| sending USB control msg (out) (blocklen=8) |
| 01 00 00 00 0F 10 04 00 |
| wrote 8 bytes |
| sending USB control msg (in) (acklen=12) sizeof(loc->data |
| read 4 bytes <==== MUST BE 12!! |
| */ |
| |
| cmd_status = le16_to_cpu(loc->status); |
| if (cmd_status != 1) { |
| printk("%s: "FUNC"(): cmd_status is not SUCCESS: %d (%s)\n", |
| devname, cmd_status, acx_cmd_status_str(cmd_status)); |
| /* TODO: goto bad; ? */ |
| } |
| if ((cmd == ACX1xx_CMD_INTERROGATE) && buffer && buflen) { |
| memcpy(buffer, loc->data, buflen); |
| log(L_CTL, "response frame: cmd=0x%04X status=%d\n", |
| le16_to_cpu(loc->cmd), |
| cmd_status); |
| } |
| good: |
| kfree(loc); |
| FN_EXIT1(OK); |
| return OK; |
| bad: |
| /* Give enough info so that callers can avoid |
| ** printing their own diagnostic messages */ |
| #if ACX_DEBUG |
| printk("%s: "FUNC"(cmd:%s) FAILED\n", devname, cmdstr); |
| #else |
| printk("%s: "FUNC"(cmd:0x%04X) FAILED\n", devname, cmd); |
| #endif |
| dump_stack(); |
| kfree(loc); |
| FN_EXIT1(NOT_OK); |
| return NOT_OK; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxusb_boot() |
| ** Inputs: |
| ** usbdev -> Pointer to kernel's usb_device structure |
| ** |
| ** Returns: |
| ** (int) Errorcode or 0 on success |
| ** |
| ** This function triggers the loading of the firmware image from harddisk |
| ** and then uploads the firmware to the USB device. After uploading the |
| ** firmware and transmitting the checksum, the device resets and appears |
| ** as a new device on the USB bus (the device we can finally deal with) |
| */ |
| static inline int |
| acxusb_fw_needs_padding(firmware_image_t *fw_image, unsigned int usb_maxlen) |
| { |
| unsigned int num_xfers = ((fw_image->size - 1) / usb_maxlen) + 1; |
| |
| return ((num_xfers % 2) == 0); |
| } |
| |
| static int |
| acxusb_boot(struct usb_device *usbdev, int is_tnetw1450, int *radio_type) |
| { |
| char filename[sizeof("tiacx1NNusbcRR")]; |
| |
| firmware_image_t *fw_image = NULL; |
| char *usbbuf; |
| unsigned int offset; |
| unsigned int blk_len, inpipe, outpipe; |
| u32 num_processed; |
| u32 img_checksum, sum; |
| u32 file_size; |
| int result = -EIO; |
| int i; |
| |
| FN_ENTER; |
| |
| /* dump_device(usbdev); */ |
| |
| usbbuf = kmalloc(USB_RWMEM_MAXLEN, GFP_KERNEL); |
| if (!usbbuf) { |
| printk(KERN_ERR "acx: no memory for USB transfer buffer (%d bytes)\n", USB_RWMEM_MAXLEN); |
| result = -ENOMEM; |
| goto end; |
| } |
| if (is_tnetw1450) { |
| /* Obtain the I/O pipes */ |
| outpipe = usb_sndbulkpipe(usbdev, 1); |
| inpipe = usb_rcvbulkpipe(usbdev, 2); |
| |
| printk(KERN_DEBUG "wait for device ready\n"); |
| for (i = 0; i <= 2; i++) { |
| result = usb_bulk_msg(usbdev, inpipe, |
| usbbuf, |
| USB_RWMEM_MAXLEN, |
| &num_processed, |
| 2000 |
| ); |
| |
| if ((*(u32 *)&usbbuf[4] == 0x40000001) |
| && (*(u16 *)&usbbuf[2] == 0x1) |
| && ((*(u16 *)usbbuf & 0x3fff) == 0) |
| && ((*(u16 *)usbbuf & 0xc000) == 0xc000)) |
| break; |
| msleep(10); |
| } |
| if (i == 2) |
| goto fw_end; |
| |
| *radio_type = usbbuf[8]; |
| } else { |
| /* Obtain the I/O pipes */ |
| outpipe = usb_sndctrlpipe(usbdev, 0); |
| inpipe = usb_rcvctrlpipe(usbdev, 0); |
| |
| /* FIXME: shouldn't be hardcoded */ |
| *radio_type = RADIO_MAXIM_0D; |
| } |
| |
| snprintf(filename, sizeof(filename), "tiacx1%02dusbc%02X", |
| is_tnetw1450 * 11, *radio_type); |
| |
| fw_image = acx_s_read_fw(&usbdev->dev, filename, &file_size); |
| if (!fw_image) { |
| result = -EIO; |
| goto end; |
| } |
| log(L_INIT, "firmware size: %d bytes\n", file_size); |
| |
| img_checksum = le32_to_cpu(fw_image->chksum); |
| |
| if (is_tnetw1450) { |
| u8 cmdbuf[20]; |
| const u8 *p; |
| u8 need_padding; |
| u32 tmplen, val; |
| |
| memset(cmdbuf, 0, 16); |
| |
| need_padding = acxusb_fw_needs_padding(fw_image, USB_RWMEM_MAXLEN); |
| tmplen = need_padding ? file_size-4 : file_size-8; |
| *(u16 *)&cmdbuf[0] = 0xc000; |
| *(u16 *)&cmdbuf[2] = 0x000b; |
| *(u32 *)&cmdbuf[4] = tmplen; |
| *(u32 *)&cmdbuf[8] = file_size-8; |
| *(u32 *)&cmdbuf[12] = img_checksum; |
| |
| result = usb_bulk_msg(usbdev, outpipe, cmdbuf, 16, &num_processed, HZ); |
| if (result < 0) |
| goto fw_end; |
| |
| p = (const u8 *)&fw_image->size; |
| |
| /* first calculate checksum for image size part */ |
| sum = p[0]+p[1]+p[2]+p[3]; |
| p += 4; |
| |
| /* now continue checksum for firmware data part */ |
| tmplen = le32_to_cpu(fw_image->size); |
| for (i = 0; i < tmplen /* image size */; i++) { |
| sum += *p++; |
| } |
| |
| if (sum != le32_to_cpu(fw_image->chksum)) { |
| printk("acx: FATAL: firmware upload: " |
| "checksums don't match! " |
| "(0x%08x vs. 0x%08x)\n", |
| sum, fw_image->chksum); |
| goto fw_end; |
| } |
| |
| offset = 8; |
| while (offset < file_size) { |
| blk_len = file_size - offset; |
| if (blk_len > USB_RWMEM_MAXLEN) { |
| blk_len = USB_RWMEM_MAXLEN; |
| } |
| |
| log(L_INIT, "uploading firmware (%d bytes, offset=%d)\n", |
| blk_len, offset); |
| memcpy(usbbuf, ((u8 *)fw_image) + offset, blk_len); |
| |
| p = usbbuf; |
| for (i = 0; i < blk_len; i += 4) { |
| *(u32 *)p = be32_to_cpu(*(u32 *)p); |
| p += 4; |
| } |
| |
| result = usb_bulk_msg(usbdev, outpipe, usbbuf, blk_len, &num_processed, HZ); |
| if ((result < 0) || (num_processed != blk_len)) |
| goto fw_end; |
| offset += blk_len; |
| } |
| if (need_padding) { |
| printk(KERN_DEBUG "send padding\n"); |
| memset(usbbuf, 0, 4); |
| result = usb_bulk_msg(usbdev, outpipe, usbbuf, 4, &num_processed, HZ); |
| if ((result < 0) || (num_processed != 4)) |
| goto fw_end; |
| } |
| printk(KERN_DEBUG "read firmware upload result\n"); |
| memset(cmdbuf, 0, 20); /* additional memset */ |
| result = usb_bulk_msg(usbdev, inpipe, cmdbuf, 20, &num_processed, 2000); |
| if (result < 0) |
| goto fw_end; |
| if (*(u32 *)&cmdbuf[4] == 0x40000003) |
| goto fw_end; |
| if (*(u32 *)&cmdbuf[4]) |
| goto fw_end; |
| if (*(u16 *)&cmdbuf[16] != 1) |
| goto fw_end; |
| |
| val = *(u32 *)&cmdbuf[0]; |
| if ((val & 0x3fff) |
| || ((val & 0xc000) != 0xc000)) |
| goto fw_end; |
| |
| val = *(u32 *)&cmdbuf[8]; |
| if (val & 2) { |
| result = usb_bulk_msg(usbdev, inpipe, cmdbuf, 20, &num_processed, 2000); |
| if (result < 0) |
| goto fw_end; |
| val = *(u32 *)&cmdbuf[8]; |
| } |
| /* yup, no "else" here! */ |
| if (val & 1) { |
| memset(usbbuf, 0, 4); |
| result = usb_bulk_msg(usbdev, outpipe, usbbuf, 4, &num_processed, HZ); |
| if ((result < 0) || (!num_processed)) |
| goto fw_end; |
| } |
| |
| printk("TNETW1450 firmware upload successful!\n"); |
| result = 0; |
| goto end; |
| fw_end: |
| result = -EIO; |
| goto end; |
| } else { |
| /* ACX100 USB */ |
| |
| /* now upload the firmware, slice the data into blocks */ |
| offset = 8; |
| while (offset < file_size) { |
| blk_len = file_size - offset; |
| if (blk_len > USB_RWMEM_MAXLEN) { |
| blk_len = USB_RWMEM_MAXLEN; |
| } |
| log(L_INIT, "uploading firmware (%d bytes, offset=%d)\n", |
| blk_len, offset); |
| memcpy(usbbuf, ((u8 *)fw_image) + offset, blk_len); |
| result = usb_control_msg(usbdev, outpipe, |
| ACX_USB_REQ_UPLOAD_FW, |
| USB_TYPE_VENDOR|USB_DIR_OUT, |
| (file_size - 8) & 0xffff, /* value */ |
| (file_size - 8) >> 16, /* index */ |
| usbbuf, /* dataptr */ |
| blk_len, /* size */ |
| 3000 /* timeout in ms */ |
| ); |
| offset += blk_len; |
| if (result < 0) { |
| printk(KERN_ERR "acx: error %d during upload " |
| "of firmware, aborting\n", result); |
| goto end; |
| } |
| } |
| |
| /* finally, send the checksum and reboot the device */ |
| /* does this trigger the reboot? */ |
| result = usb_control_msg(usbdev, outpipe, |
| ACX_USB_REQ_UPLOAD_FW, |
| USB_TYPE_VENDOR|USB_DIR_OUT, |
| img_checksum & 0xffff, /* value */ |
| img_checksum >> 16, /* index */ |
| NULL, /* dataptr */ |
| 0, /* size */ |
| 3000 /* timeout in ms */ |
| ); |
| if (result < 0) { |
| printk(KERN_ERR "acx: error %d during tx of checksum, " |
| "aborting\n", result); |
| goto end; |
| } |
| result = usb_control_msg(usbdev, inpipe, |
| ACX_USB_REQ_ACK_CS, |
| USB_TYPE_VENDOR|USB_DIR_IN, |
| img_checksum & 0xffff, /* value */ |
| img_checksum >> 16, /* index */ |
| usbbuf, /* dataptr */ |
| 8, /* size */ |
| 3000 /* timeout in ms */ |
| ); |
| if (result < 0) { |
| printk(KERN_ERR "acx: error %d during ACK of checksum, " |
| "aborting\n", result); |
| goto end; |
| } |
| if (*usbbuf != 0x10) { |
| printk(KERN_ERR "acx: invalid checksum?\n"); |
| result = -EINVAL; |
| goto end; |
| } |
| result = 0; |
| } |
| |
| end: |
| vfree(fw_image); |
| kfree(usbbuf); |
| |
| FN_EXIT1(result); |
| return result; |
| } |
| |
| |
| /* FIXME: maybe merge it with usual eeprom reading, into common code? */ |
| static void |
| acxusb_s_read_eeprom_version(acx_device_t *adev) |
| { |
| u8 eeprom_ver[0x8]; |
| |
| memset(eeprom_ver, 0, sizeof(eeprom_ver)); |
| acx_s_interrogate(adev, &eeprom_ver, ACX1FF_IE_EEPROM_VER); |
| |
| /* FIXME: which one of those values to take? */ |
| adev->eeprom_version = eeprom_ver[5]; |
| } |
| |
| |
| /* |
| * temporary helper function to at least fill important cfgopt members with |
| * useful replacement values until we figure out how one manages to fetch |
| * the configoption struct in the USB device case... |
| */ |
| static int |
| acxusb_s_fill_configoption(acx_device_t *adev) |
| { |
| adev->cfgopt_probe_delay = 200; |
| adev->cfgopt_dot11CCAModes = 4; |
| adev->cfgopt_dot11Diversity = 1; |
| adev->cfgopt_dot11ShortPreambleOption = 1; |
| adev->cfgopt_dot11PBCCOption = 1; |
| adev->cfgopt_dot11ChannelAgility = 0; |
| adev->cfgopt_dot11PhyType = 5; |
| adev->cfgopt_dot11TempType = 1; |
| return OK; |
| } |
| |
| /* Dummy function. */ |
| static void |
| acxusb_s_delete_dma_regions(acx_device_t *adev) |
| { |
| } |
| |
| /*********************************************************************** |
| ** acxusb_e_probe() |
| ** |
| ** This function is invoked by the kernel's USB core whenever a new device is |
| ** attached to the system or the module is loaded. It is presented a usb_device |
| ** structure from which information regarding the device is obtained and evaluated. |
| ** In case this driver is able to handle one of the offered devices, it returns |
| ** a non-null pointer to a driver context and thereby claims the device. |
| */ |
| |
| static int |
| acxusb_e_probe(struct usb_interface *intf, const struct usb_device_id *devID) |
| { |
| struct usb_device *usbdev = interface_to_usbdev(intf); |
| acx_device_t *adev = NULL; |
| struct net_device *ndev = NULL; |
| struct usb_config_descriptor *config; |
| struct usb_endpoint_descriptor *epdesc; |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11) |
| struct usb_host_endpoint *ep; |
| #endif |
| struct usb_interface_descriptor *ifdesc; |
| const char* msg; |
| int numconfigs, numfaces, numep; |
| int result = OK; |
| int i; |
| int radio_type; |
| /* This one needs to be more precise in case there appears |
| ** a TNETW1450 from the same vendor */ |
| int is_tnetw1450 = (usbdev->descriptor.idVendor != ACX100_VENDOR_ID); |
| |
| FN_ENTER; |
| |
| if (is_tnetw1450) { |
| /* Boot the device (i.e. upload the firmware) */ |
| acxusb_boot(usbdev, is_tnetw1450, &radio_type); |
| |
| /* TNETW1450-based cards will continue right away with |
| * the same USB ID after booting */ |
| } else { |
| /* First check if this is the "unbooted" hardware */ |
| if (usbdev->descriptor.idProduct == ACX100_PRODUCT_ID_UNBOOTED) { |
| |
| /* Boot the device (i.e. upload the firmware) */ |
| acxusb_boot(usbdev, is_tnetw1450, &radio_type); |
| |
| /* DWL-120+ will first boot the firmware, |
| * then later have a *separate* probe() run |
| * since its USB ID will have changed after |
| * firmware boot! |
| * Since the first probe() run has no |
| * other purpose than booting the firmware, |
| * simply return immediately. |
| */ |
| log(L_INIT, "finished booting, returning from probe()\n"); |
| result = OK; /* success */ |
| goto end; |
| } else { |
| if (usbdev->descriptor.idProduct != ACX100_PRODUCT_ID_BOOTED) |
| /* device not unbooted, but invalid USB ID!? */ |
| goto end_nodev; |
| } |
| } |
| |
| /* Ok, so it's our device and it has already booted */ |
| |
| /* Allocate memory for a network device */ |
| |
| ndev = alloc_ieee80211softmac(sizeof(*adev)); |
| /* (NB: memsets to 0 entire area) */ |
| if (!ndev) { |
| msg = "acx: no memory for netdev\n"; |
| goto end_nomem; |
| } |
| |
| /* Register the callbacks for the network device functions */ |
| ndev->open = &acxusb_e_open; |
| ndev->stop = &acxusb_e_close; |
| //sm ndev->hard_start_xmit = (void *)&acx_i_start_xmit; |
| ndev->get_stats = (void *)&acx_e_get_stats; |
| #if IW_HANDLER_VERSION <= 5 |
| ndev->get_wireless_stats = (void *)&acx_e_get_wireless_stats; |
| #endif |
| ndev->wireless_handlers = (struct iw_handler_def *)&acx_ioctl_handler_def; |
| ndev->set_multicast_list = (void *)&acxusb_i_set_rx_mode; |
| #ifdef HAVE_TX_TIMEOUT |
| ndev->tx_timeout = &acxusb_i_tx_timeout; |
| ndev->watchdog_timeo = 4 * HZ; |
| #endif |
| ndev->change_mtu = &acx_e_change_mtu; |
| SET_MODULE_OWNER(ndev); |
| |
| /* Setup private driver context */ |
| |
| adev = ndev2adev(ndev); |
| adev->ndev = ndev; |
| |
| adev->dev_type = DEVTYPE_USB; |
| adev->radio_type = radio_type; |
| if (is_tnetw1450) { |
| /* well, actually it's a TNETW1450, but since it |
| * seems to be sufficiently similar to TNETW1130, |
| * I don't want to change large amounts of code now */ |
| adev->chip_type = CHIPTYPE_ACX111; |
| } else { |
| adev->chip_type = CHIPTYPE_ACX100; |
| } |
| |
| adev->usbdev = usbdev; |
| spin_lock_init(&adev->lock); /* initial state: unlocked */ |
| sema_init(&adev->sem, 1); /* initial state: 1 (upped) */ |
| //SM |
| adev->ieee = netdev_priv(ndev); |
| /* default to sw encryption for now */ |
| adev->ieee->host_build_iv = 0; |
| adev->ieee->host_encrypt = 1; |
| adev->ieee->host_decrypt = 1; |
| adev->ieee->iw_mode = IW_MODE_INFRA; |
| adev->ieee->tx_headroom = 0; |
| adev->ieee->set_security = acx_e_ieee80211_set_security; |
| adev->ieee->hard_start_xmit = acx_i_ieee80211_start_xmit; |
| adev->softmac = ieee80211_priv(ndev); |
| adev->softmac->set_channel = acx_e_ieee80211_set_chan; |
| |
| adev->ieee->sec.enabled = 0; |
| adev->ieee->sec.encrypt = 0; |
| adev->ieee->sec.auth_mode = WLAN_AUTH_OPEN; |
| |
| adev->ops.create_dma_regions = acx100_s_create_dma_regions; |
| adev->ops.delete_dma_regions = acxusb_s_delete_dma_regions; |
| #if ACX_DEBUG |
| adev->ops.issue_cmd = acxusb_s_issue_cmd_timeo_debug; |
| #else |
| adev->ops.issue_cmd = acxusb_s_issue_cmd_timeo; |
| #endif /* ACX_DEBUG */ |
| adev->ops.alloc_tx = acxusb_l_alloc_tx; |
| adev->ops.dealloc_tx = acxusb_l_dealloc_tx; |
| adev->ops.get_txbuf = acxusb_l_get_txbuf; |
| adev->ops.tx_data = acxusb_l_tx_data; |
| adev->ops.write_phy_reg = acxusb_s_write_phy_reg; |
| adev->ops.read_phy_reg = acxusb_s_read_phy_reg; |
| |
| /* Check that this is really the hardware we know about. |
| ** If not sure, at least notify the user that he |
| ** may be in trouble... |
| */ |
| numconfigs = (int)usbdev->descriptor.bNumConfigurations; |
| if (numconfigs != 1) |
| printk("acx: number of configurations is %d, " |
| "this driver only knows how to handle 1, " |
| "be prepared for surprises\n", numconfigs); |
| |
| config = &usbdev->config->desc; |
| numfaces = config->bNumInterfaces; |
| if (numfaces != 1) |
| printk("acx: number of interfaces is %d, " |
| "this driver only knows how to handle 1, " |
| "be prepared for surprises\n", numfaces); |
| |
| ifdesc = &intf->altsetting->desc; |
| numep = ifdesc->bNumEndpoints; |
| log(L_DEBUG, "# of endpoints: %d\n", numep); |
| |
| if (is_tnetw1450) { |
| adev->bulkoutep = 1; |
| adev->bulkinep = 2; |
| } else { |
| /* obtain information about the endpoint |
| ** addresses, begin with some default values |
| */ |
| adev->bulkoutep = 1; |
| adev->bulkinep = 1; |
| for (i = 0; i < numep; i++) { |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11) |
| ep = usbdev->ep_in[i]; |
| if (!ep) |
| continue; |
| epdesc = &ep->desc; |
| #else |
| epdesc = usb_epnum_to_ep_desc(usbdev, i); |
| if (!epdesc) |
| continue; |
| #endif |
| if (epdesc->bmAttributes & USB_ENDPOINT_XFER_BULK) { |
| if (epdesc->bEndpointAddress & 0x80) |
| adev->bulkinep = epdesc->bEndpointAddress & 0xF; |
| else |
| adev->bulkoutep = epdesc->bEndpointAddress & 0xF; |
| } |
| } |
| } |
| log(L_DEBUG, "bulkout ep: 0x%X\n", adev->bulkoutep); |
| log(L_DEBUG, "bulkin ep: 0x%X\n", adev->bulkinep); |
| |
| /* already done by memset: adev->rxtruncsize = 0; */ |
| log(L_DEBUG, "TXBUFSIZE=%d RXBUFSIZE=%d\n", |
| (int) TXBUFSIZE, (int) RXBUFSIZE); |
| |
| /* Allocate the RX/TX containers. */ |
| adev->usb_tx = kmalloc(sizeof(usb_tx_t) * ACX_TX_URB_CNT, GFP_KERNEL); |
| if (!adev->usb_tx) { |
| msg = "acx: no memory for tx container"; |
| goto end_nomem; |
| } |
| adev->usb_rx = kmalloc(sizeof(usb_rx_t) * ACX_RX_URB_CNT, GFP_KERNEL); |
| if (!adev->usb_rx) { |
| msg = "acx: no memory for rx container"; |
| goto end_nomem; |
| } |
| |
| /* Setup URBs for bulk-in/out messages */ |
| for (i = 0; i < ACX_RX_URB_CNT; i++) { |
| adev->usb_rx[i].urb = usb_alloc_urb(0, GFP_KERNEL); |
| if (!adev->usb_rx[i].urb) { |
| msg = "acx: no memory for input URB\n"; |
| goto end_nomem; |
| } |
| adev->usb_rx[i].urb->status = 0; |
| adev->usb_rx[i].adev = adev; |
| adev->usb_rx[i].busy = 0; |
| } |
| |
| for (i = 0; i< ACX_TX_URB_CNT; i++) { |
| adev->usb_tx[i].urb = usb_alloc_urb(0, GFP_KERNEL); |
| if (!adev->usb_tx[i].urb) { |
| msg = "acx: no memory for output URB\n"; |
| goto end_nomem; |
| } |
| adev->usb_tx[i].urb->status = 0; |
| adev->usb_tx[i].adev = adev; |
| adev->usb_tx[i].busy = 0; |
| } |
| adev->tx_free = ACX_TX_URB_CNT; |
| |
| usb_set_intfdata(intf, adev); |
| SET_NETDEV_DEV(ndev, &intf->dev); |
| |
| /* TODO: move all of fw cmds to open()? But then we won't know our MAC addr |
| until ifup (it's available via reading ACX1xx_IE_DOT11_STATION_ID)... */ |
| |
| /* put acx out of sleep mode and initialize it */ |
| acx_s_issue_cmd(adev, ACX1xx_CMD_WAKE, NULL, 0); |
| |
| result = acx_s_init_mac(adev); |
| if (result) |
| goto end; |
| |
| /* TODO: see similar code in pci.c */ |
| acxusb_s_read_eeprom_version(adev); |
| acxusb_s_fill_configoption(adev); |
| acx_s_set_defaults(adev); |
| acx_s_get_firmware_version(adev); |
| acx_display_hardware_details(adev); |
| |
| /* Register the network device */ |
| log(L_INIT, "registering network device\n"); |
| result = register_netdev(ndev); |
| if (result) { |
| msg = "acx: failed to register USB network device " |
| "(error %d)\n"; |
| goto end_nomem; |
| } |
| |
| acx_proc_register_entries(ndev); |
| |
| acx_stop_queue(ndev, "on probe"); |
| acx_carrier_off(ndev, "on probe"); |
| |
| printk("acx: USB module " ACX_RELEASE " loaded successfully\n"); |
| |
| #if CMD_DISCOVERY |
| great_inquisitor(adev); |
| #endif |
| |
| /* Everything went OK, we are happy now */ |
| result = OK; |
| goto end; |
| |
| end_nomem: |
| printk(msg, result); |
| |
| if (ndev) { |
| if (adev->usb_rx) { |
| for (i = 0; i < ACX_RX_URB_CNT; i++) |
| usb_free_urb(adev->usb_rx[i].urb); |
| kfree(adev->usb_rx); |
| } |
| if (adev->usb_tx) { |
| for (i = 0; i < ACX_TX_URB_CNT; i++) |
| usb_free_urb(adev->usb_tx[i].urb); |
| kfree(adev->usb_tx); |
| } |
| free_netdev(ndev); |
| } |
| |
| result = -ENOMEM; |
| goto end; |
| |
| end_nodev: |
| /* no device we could handle, return error. */ |
| result = -EIO; |
| |
| end: |
| FN_EXIT1(result); |
| return result; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxusb_e_disconnect() |
| ** |
| ** This function is invoked whenever the user pulls the plug from the USB |
| ** device or the module is removed from the kernel. In these cases, the |
| ** network devices have to be taken down and all allocated memory has |
| ** to be freed. |
| */ |
| static void |
| acxusb_e_disconnect(struct usb_interface *intf) |
| { |
| acx_device_t *adev = usb_get_intfdata(intf); |
| unsigned long flags; |
| int i; |
| |
| FN_ENTER; |
| |
| /* No WLAN device... no sense */ |
| if (!adev) |
| goto end; |
| |
| /* Unregister network device |
| * |
| * If the interface is up, unregister_netdev() will take |
| * care of calling our close() function, which takes |
| * care of unlinking the urbs, sending the device to |
| * sleep, etc... |
| * This can't be called with sem or lock held because |
| * _close() will try to grab it as well if it's called, |
| * deadlocking the machine. |
| */ |
| unregister_netdev(adev->ndev); |
| |
| acx_sem_lock(adev); |
| acx_lock(adev, flags); |
| /* This device exists no more */ |
| usb_set_intfdata(intf, NULL); |
| acx_proc_unregister_entries(adev->ndev); |
| |
| /* |
| * Here we only free them. _close() took care of |
| * unlinking them. |
| */ |
| for (i = 0; i < ACX_RX_URB_CNT; ++i) { |
| usb_free_urb(adev->usb_rx[i].urb); |
| } |
| for (i = 0; i< ACX_TX_URB_CNT; ++i) { |
| usb_free_urb(adev->usb_tx[i].urb); |
| } |
| |
| /* Freeing containers */ |
| kfree(adev->usb_rx); |
| kfree(adev->usb_tx); |
| |
| acx_unlock(adev, flags); |
| acx_sem_unlock(adev); |
| |
| free_netdev(adev->ndev); |
| end: |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxusb_e_open() |
| ** This function is called when the user sets up the network interface. |
| ** It initializes a management timer, sets up the USB card and starts |
| ** the network tx queue and USB receive. |
| */ |
| static int |
| acxusb_e_open(struct net_device *ndev) |
| { |
| acx_device_t *adev = ndev2adev(ndev); |
| unsigned long flags; |
| int i; |
| |
| FN_ENTER; |
| |
| acx_sem_lock(adev); |
| |
| /* put the ACX100 out of sleep mode */ |
| acx_s_issue_cmd(adev, ACX1xx_CMD_WAKE, NULL, 0); |
| |
| acx_init_task_scheduler(adev); |
| |
| init_timer(&adev->mgmt_timer); |
| adev->mgmt_timer.function = acx_i_timer; |
| adev->mgmt_timer.data = (unsigned long)adev; |
| |
| /* acx_s_start needs it */ |
| SET_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP); |
| acx_s_start(adev); |
| |
| /* don't acx_start_queue() here, we need to associate first */ |
| |
| acx_lock(adev, flags); |
| for (i = 0; i < ACX_RX_URB_CNT; i++) { |
| adev->usb_rx[i].urb->status = 0; |
| } |
| |
| acxusb_l_poll_rx(adev, &adev->usb_rx[0]); |
| |
| acx_unlock(adev, flags); |
| |
| acx_sem_unlock(adev); |
| |
| FN_EXIT0; |
| return 0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxusb_e_close() |
| ** |
| ** This function stops the network functionality of the interface (invoked |
| ** when the user calls ifconfig <wlan> down). The tx queue is halted and |
| ** the device is marked as down. In case there were any pending USB bulk |
| ** transfers, these are unlinked (asynchronously). The module in-use count |
| ** is also decreased in this function. |
| */ |
| static int |
| acxusb_e_close(struct net_device *ndev) |
| { |
| acx_device_t *adev = ndev2adev(ndev); |
| unsigned long flags; |
| int i; |
| |
| FN_ENTER; |
| |
| #ifdef WE_STILL_DONT_CARE_ABOUT_IT |
| /* Transmit a disassociate frame */ |
| lock |
| acx_l_transmit_disassoc(adev, &client); |
| unlock |
| #endif |
| |
| acx_sem_lock(adev); |
| |
| CLEAR_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP); |
| |
| /* Code below is remarkably similar to acxpci_s_down(). Maybe we can merge them? */ |
| |
| /* Make sure we don't get any more rx requests */ |
| acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_RX, NULL, 0); |
| acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_TX, NULL, 0); |
| |
| /* |
| * We must do FLUSH *without* holding sem to avoid a deadlock. |
| * See pci.c:acxpci_s_down() for deails. |
| */ |
| acx_sem_unlock(adev); |
| flush_scheduled_work(); |
| acx_sem_lock(adev); |
| |
| /* Power down the device */ |
| acx_s_issue_cmd(adev, ACX1xx_CMD_SLEEP, NULL, 0); |
| |
| /* Stop the transmit queue, mark the device as DOWN */ |
| acx_lock(adev, flags); |
| acx_stop_queue(ndev, "on ifdown"); |
| acx_set_status(adev, ACX_STATUS_0_STOPPED); |
| /* stop pending rx/tx urb transfers */ |
| for (i = 0; i < ACX_TX_URB_CNT; i++) { |
| acxusb_unlink_urb(adev->usb_tx[i].urb); |
| adev->usb_tx[i].busy = 0; |
| } |
| for (i = 0; i < ACX_RX_URB_CNT; i++) { |
| acxusb_unlink_urb(adev->usb_rx[i].urb); |
| adev->usb_rx[i].busy = 0; |
| } |
| adev->tx_free = ACX_TX_URB_CNT; |
| acx_unlock(adev, flags); |
| |
| /* Must do this outside of lock */ |
| del_timer_sync(&adev->mgmt_timer); |
| |
| acx_sem_unlock(adev); |
| |
| FN_EXIT0; |
| return 0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxusb_l_poll_rx |
| ** This function (re)initiates a bulk-in USB transfer on a given urb |
| */ |
| static void |
| acxusb_l_poll_rx(acx_device_t *adev, usb_rx_t* rx) |
| { |
| struct usb_device *usbdev; |
| struct urb *rxurb; |
| int errcode, rxnum; |
| unsigned int inpipe; |
| |
| FN_ENTER; |
| |
| rxurb = rx->urb; |
| usbdev = adev->usbdev; |
| |
| rxnum = rx - adev->usb_rx; |
| |
| inpipe = usb_rcvbulkpipe(usbdev, adev->bulkinep); |
| if (unlikely(rxurb->status == -EINPROGRESS)) { |
| printk(KERN_ERR "acx: error, rx triggered while rx urb in progress\n"); |
| /* FIXME: this is nasty, receive is being cancelled by this code |
| * on the other hand, this should not happen anyway... |
| */ |
| usb_unlink_urb(rxurb); |
| } else |
| if (unlikely(rxurb->status == -ECONNRESET)) { |
| log(L_USBRXTX, "acx_usb: _poll_rx: connection reset\n"); |
| goto end; |
| } |
| rxurb->actual_length = 0; |
| usb_fill_bulk_urb(rxurb, usbdev, inpipe, |
| &rx->bulkin, /* dataptr */ |
| RXBUFSIZE, /* size */ |
| acxusb_i_complete_rx, /* handler */ |
| rx /* handler param */ |
| ); |
| rxurb->transfer_flags = URB_ASYNC_UNLINK; |
| |
| /* ATOMIC: we may be called from complete_rx() usb callback */ |
| errcode = usb_submit_urb(rxurb, GFP_ATOMIC); |
| /* FIXME: evaluate the error code! */ |
| log(L_USBRXTX, "SUBMIT RX (%d) inpipe=0x%X size=%d errcode=%d\n", |
| rxnum, inpipe, (int) RXBUFSIZE, errcode); |
| end: |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxusb_i_complete_rx() |
| ** Inputs: |
| ** urb -> pointer to USB request block |
| ** regs -> pointer to register-buffer for syscalls (see asm/ptrace.h) |
| ** |
| ** This function is invoked by USB subsystem whenever a bulk receive |
| ** request returns. |
| ** The received data is then committed to the network stack and the next |
| ** USB receive is triggered. |
| */ |
| static void |
| acxusb_i_complete_rx(struct urb *urb) |
| { |
| acx_device_t *adev; |
| rxbuffer_t *ptr; |
| rxbuffer_t *inbuf; |
| usb_rx_t *rx; |
| unsigned long flags; |
| int size, remsize, packetsize, rxnum; |
| |
| FN_ENTER; |
| |
| BUG_ON(!urb->context); |
| |
| rx = (usb_rx_t *)urb->context; |
| adev = rx->adev; |
| |
| acx_lock(adev, flags); |
| |
| /* |
| * Happens on disconnect or close. Don't play with the urb. |
| * Don't resubmit it. It will get unlinked by close() |
| */ |
| if (unlikely(!(adev->dev_state_mask & ACX_STATE_IFACE_UP))) { |
| log(L_USBRXTX, "rx: device is down, not doing anything\n"); |
| goto end_unlock; |
| } |
| |
| inbuf = &rx->bulkin; |
| size = urb->actual_length; |
| remsize = size; |
| rxnum = rx - adev->usb_rx; |
| |
| log(L_USBRXTX, "RETURN RX (%d) status=%d size=%d\n", |
| rxnum, urb->status, size); |
| |
| /* Send the URB that's waiting. */ |
| log(L_USBRXTX, "rxnum=%d, sending=%d\n", rxnum, rxnum^1); |
| acxusb_l_poll_rx(adev, &adev->usb_rx[rxnum^1]); |
| |
| if (unlikely(size > sizeof(rxbuffer_t))) |
| printk("acx_usb: rx too large: %d, please report\n", size); |
| |
| /* check if the transfer was aborted */ |
| switch (urb->status) { |
| case 0: /* No error */ |
| break; |
| case -EOVERFLOW: |
| printk(KERN_ERR "acx: rx data overrun\n"); |
| adev->rxtruncsize = 0; /* Not valid anymore. */ |
| goto end_unlock; |
| case -ECONNRESET: |
| adev->rxtruncsize = 0; |
| goto end_unlock; |
| case -ESHUTDOWN: /* rmmod */ |
| adev->rxtruncsize = 0; |
| goto end_unlock; |
| default: |
| adev->rxtruncsize = 0; |
| adev->stats.rx_errors++; |
| printk("acx: rx error (urb status=%d)\n", urb->status); |
| goto end_unlock; |
| } |
| |
| if (unlikely(!size)) |
| printk("acx: warning, encountered zerolength rx packet\n"); |
| |
| if (urb->transfer_buffer != inbuf) |
| goto end_unlock; |
| |
| /* check if previous frame was truncated |
| ** FIXME: this code can only handle truncation |
| ** of consecutive packets! |
| */ |
| ptr = inbuf; |
| if (adev->rxtruncsize) { |
| int tail_size; |
| |
| ptr = &adev->rxtruncbuf; |
| packetsize = RXBUF_BYTES_USED(ptr); |
| if (acx_debug & L_USBRXTX) { |
| printk("handling truncated frame (truncsize=%d size=%d " |
| "packetsize(from trunc)=%d)\n", |
| adev->rxtruncsize, size, packetsize); |
| acx_dump_bytes(ptr, RXBUF_HDRSIZE); |
| acx_dump_bytes(inbuf, RXBUF_HDRSIZE); |
| } |
| |
| /* bytes needed for rxtruncbuf completion: */ |
| tail_size = packetsize - adev->rxtruncsize; |
| |
| if (size < tail_size) { |
| /* there is not enough data to complete this packet, |
| ** simply append the stuff to the truncation buffer |
| */ |
| memcpy(((char *)ptr) + adev->rxtruncsize, inbuf, size); |
| adev->rxtruncsize += size; |
| remsize = 0; |
| } else { |
| /* ok, this data completes the previously |
| ** truncated packet. copy it into a descriptor |
| ** and give it to the rest of the stack */ |
| |
| /* append tail to previously truncated part |
| ** NB: adev->rxtruncbuf (pointed to by ptr) can't |
| ** overflow because this is already checked before |
| ** truncation buffer was filled. See below, |
| ** "if (packetsize > sizeof(rxbuffer_t))..." code */ |
| memcpy(((char *)ptr) + adev->rxtruncsize, inbuf, tail_size); |
| |
| if (acx_debug & L_USBRXTX) { |
| printk("full trailing packet + 12 bytes:\n"); |
| acx_dump_bytes(inbuf, tail_size + RXBUF_HDRSIZE); |
| } |
| acx_l_softmac_process_rxbuf(adev, ptr); //SM |
| adev->rxtruncsize = 0; |
| ptr = (rxbuffer_t *) (((char *)inbuf) + tail_size); |
| remsize -= tail_size; |
| } |
| log(L_USBRXTX, "post-merge size=%d remsize=%d\n", |
| size, remsize); |
| } |
| |
| /* size = USB data block size |
| ** remsize = unprocessed USB bytes left |
| ** ptr = current pos in USB data block |
| */ |
| while (remsize) { |
| if (remsize < RXBUF_HDRSIZE) { |
| printk("acx: truncated rx header (%d bytes)!\n", |
| remsize); |
| if (ACX_DEBUG) |
| acx_dump_bytes(ptr, remsize); |
| break; |
| } |
| |
| packetsize = RXBUF_BYTES_USED(ptr); |
| log(L_USBRXTX, "packet with packetsize=%d\n", packetsize); |
| |
| if (RXBUF_IS_TXSTAT(ptr)) { |
| /* do rate handling */ |
| usb_txstatus_t *stat = (void*)ptr; |
| u16 client_no = (u16)stat->hostdata; |
| |
| log(L_USBRXTX, "tx: stat: mac_cnt_rcvd:%04X " |
| "queue_index:%02X mac_status:%02X hostdata:%08X " |
| "rate:%u ack_failures:%02X rts_failures:%02X " |
| "rts_ok:%02X\n", |
| stat->mac_cnt_rcvd, |
| stat->queue_index, stat->mac_status, stat->hostdata, |
| stat->rate, stat->ack_failures, stat->rts_failures, |
| stat->rts_ok); |
| |
| if (adev->rate_auto && client_no < VEC_SIZE(adev->sta_list)) { |
| client_t *clt = &adev->sta_list[client_no]; |
| u16 cur = stat->hostdata >> 16; |
| |
| if (clt && clt->rate_cur == cur) { |
| acx_l_handle_txrate_auto(adev, clt, |
| cur, /* intended rate */ |
| stat->rate, 0, /* actually used rate */ |
| stat->mac_status, /* error? */ |
| ACX_TX_URB_CNT - adev->tx_free); |
| } |
| } |
| goto next; |
| } |
| |
| if (packetsize > sizeof(rxbuffer_t)) { |
| printk("acx: packet exceeds max wlan " |
| "frame size (%d > %d). size=%d\n", |
| packetsize, (int) sizeof(rxbuffer_t), size); |
| if (ACX_DEBUG) |
| acx_dump_bytes(ptr, 16); |
| /* FIXME: put some real error-handling in here! */ |
| break; |
| } |
| |
| if (packetsize > remsize) { |
| /* frame truncation handling */ |
| if (acx_debug & L_USBRXTX) { |
| printk("need to truncate packet, " |
| "packetsize=%d remsize=%d " |
| "size=%d bytes:", |
| packetsize, remsize, size); |
| acx_dump_bytes(ptr, RXBUF_HDRSIZE); |
| } |
| memcpy(&adev->rxtruncbuf, ptr, remsize); |
| adev->rxtruncsize = remsize; |
| break; |
| } |
| |
| /* packetsize <= remsize */ |
| /* now handle the received data */ |
| acx_l_softmac_process_rxbuf(adev, ptr); //SM |
| next: |
| ptr = (rxbuffer_t *)(((char *)ptr) + packetsize); |
| remsize -= packetsize; |
| if ((acx_debug & L_USBRXTX) && remsize) { |
| printk("more than one packet in buffer, " |
| "second packet hdr:"); |
| acx_dump_bytes(ptr, RXBUF_HDRSIZE); |
| } |
| } |
| |
| end_unlock: |
| acx_unlock(adev, flags); |
| /* end: */ |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acxusb_i_complete_tx() |
| ** Inputs: |
| ** urb -> pointer to USB request block |
| ** regs -> pointer to register-buffer for syscalls (see asm/ptrace.h) |
| ** |
| ** This function is invoked upon termination of a USB transfer. |
| */ |
| static void |
| acxusb_i_complete_tx(struct urb *urb) |
| { |
| acx_device_t *adev; |
| usb_tx_t *tx; |
| unsigned long flags; |
| int txnum; |
| |
| FN_ENTER; |
| |
| BUG_ON(!urb->context); |
| |
| tx = (usb_tx_t *)urb->context; |
| adev = tx->adev; |
| |
| txnum = tx - adev->usb_tx; |
| |
| acx_lock(adev, flags); |
| |
| /* |
| * If the iface isn't up, we don't have any right |
| * to play with them. The urb may get unlinked. |
| */ |
| if (unlikely(!(adev->dev_state_mask & ACX_STATE_IFACE_UP))) { |
| log(L_USBRXTX, "tx: device is down, not doing anything\n"); |
| goto end_unlock; |
| } |
| |
| log(L_USBRXTX, "RETURN TX (%d): status=%d size=%d\n", |
| txnum, urb->status, urb->actual_length); |
| |
| /* handle USB transfer errors */ |
| switch (urb->status) { |
| case 0: /* No error */ |
| break; |
| case -ESHUTDOWN: |
| goto end_unlock; |
| break; |
| case -ECONNRESET: |
| goto end_unlock; |
| break; |
| /* FIXME: real error-handling code here please */ |
| default: |
| printk(KERN_ERR "acx: tx error, urb status=%d\n", urb->status); |
| /* FIXME: real error-handling code here please */ |
| } |
| |
| /* free the URB and check for more data */ |
| tx->busy = 0; |
| adev->tx_free++; |
| if ((adev->tx_free >= TX_START_QUEUE) |
| && (adev->status == ACX_STATUS_4_ASSOCIATED) |
| && (acx_queue_stopped(adev->ndev)) |
| ) { |
| log(L_BUF, "tx: wake queue (%u free txbufs)\n", |
| adev->tx_free); |
| acx_wake_queue(adev->ndev, NULL); |
| } |
| |
| end_unlock: |
| acx_unlock(adev, flags); |
| /* end: */ |
| FN_EXIT0; |
| } |
| |
| |
| /*************************************************************** |
| ** acxusb_l_alloc_tx |
| ** Actually returns a usb_tx_t* ptr |
| */ |
| static tx_t* |
| acxusb_l_alloc_tx(acx_device_t *adev) |
| { |
| usb_tx_t *tx; |
| unsigned head; |
| |
| FN_ENTER; |
| |
| head = adev->tx_head; |
| do { |
| head = (head + 1) % ACX_TX_URB_CNT; |
| if (!adev->usb_tx[head].busy) { |
| log(L_USBRXTX, "allocated tx %d\n", head); |
| tx = &adev->usb_tx[head]; |
| tx->busy = 1; |
| adev->tx_free--; |
| /* Keep a few free descs between head and tail of tx ring. |
| ** It is not absolutely needed, just feels safer */ |
| if (adev->tx_free < TX_STOP_QUEUE) { |
| log(L_BUF, "tx: stop queue " |
| "(%u free txbufs)\n", adev->tx_free); |
| acx_stop_queue(adev->ndev, NULL); |
| } |
| goto end; |
| } |
| } while (likely(head!=adev->tx_head)); |
| tx = NULL; |
| printk_ratelimited("acx: tx buffers full\n"); |
| end: |
| adev->tx_head = head; |
| FN_EXIT0; |
| return (tx_t*)tx; |
| } |
| |
| |
| /*************************************************************** |
| ** Used if alloc_tx()'ed buffer needs to be cancelled without doing tx |
| */ |
| static void |
| acxusb_l_dealloc_tx(tx_t *tx_opaque) |
| { |
| usb_tx_t* tx = (usb_tx_t*)tx_opaque; |
| tx->busy = 0; |
| } |
| |
| |
| /*************************************************************** |
| */ |
| static void* |
| acxusb_l_get_txbuf(acx_device_t *adev, tx_t* tx_opaque) |
| { |
| usb_tx_t* tx = (usb_tx_t*)tx_opaque; |
| return &tx->bulkout.data; |
| } |
| |
| |
| /*************************************************************** |
| ** acxusb_l_tx_data |
| ** |
| ** Can be called from IRQ (rx -> (AP bridging or mgmt response) -> tx). |
| ** Can be called from acx_i_start_xmit (data frames from net core). |
| */ |
| static void |
| acxusb_l_tx_data(acx_device_t *adev, tx_t* tx_opaque, int wlanpkt_len) |
| { |
| struct usb_device *usbdev; |
| struct urb* txurb; |
| usb_tx_t* tx; |
| usb_txbuffer_t* txbuf; |
| client_t *clt; |
| struct ieee80211_hdr_3addr* whdr; |
| unsigned int outpipe; |
| int ucode, txnum; |
| |
| FN_ENTER; |
| |
| tx = ((usb_tx_t *)tx_opaque); |
| txurb = tx->urb; |
| txbuf = &tx->bulkout; |
| whdr = (struct ieee80211_hdr_3addr*)txbuf->data; |
| txnum = tx - adev->usb_tx; |
| |
| log(L_DEBUG, "using buf#%d free=%d len=%d\n", |
| txnum, adev->tx_free, wlanpkt_len); |
| |
| switch (adev->mode) { |
| case ACX_MODE_0_ADHOC: |
| case ACX_MODE_3_AP: |
| clt = NULL; //sm? clt = acx_l_sta_list_get(adev, whdr->addr1); |
| break; |
| case ACX_MODE_2_STA: |
| clt = adev->ap_client; |
| break; |
| default: /* ACX_MODE_OFF, ACX_MODE_MONITOR */ |
| clt = NULL; |
| break; |
| } |
| |
| if (unlikely(clt && !clt->rate_cur)) { |
| printk("acx: driver bug! bad ratemask\n"); |
| goto end; |
| } |
| |
| /* fill the USB transfer header */ |
| txbuf->desc = cpu_to_le16(USB_TXBUF_TXDESC); |
| txbuf->mpdu_len = cpu_to_le16(wlanpkt_len); |
| txbuf->queue_index = 1; |
| if (clt) { |
| txbuf->rate = clt->rate_100; |
| txbuf->hostdata = (clt - adev->sta_list) | (clt->rate_cur << 16); |
| } else { |
| txbuf->rate = adev->rate_bcast100; |
| txbuf->hostdata = ((u16)-1) | (adev->rate_bcast << 16); |
| } |
| txbuf->ctrl1 = DESC_CTL_FIRSTFRAG; |
| if (1 == adev->preamble_cur) |
| SET_BIT(txbuf->ctrl1, DESC_CTL_SHORT_PREAMBLE); |
| txbuf->ctrl2 = 0; |
| txbuf->data_len = cpu_to_le16(wlanpkt_len); |
| |
| if (unlikely(acx_debug & L_DATA)) { |
| printk("dump of bulk out urb:\n"); |
| acx_dump_bytes(txbuf, wlanpkt_len + USB_TXBUF_HDRSIZE); |
| } |
| |
| if (unlikely(txurb->status == -EINPROGRESS)) { |
| printk("acx: trying to submit tx urb while already in progress\n"); |
| } |
| |
| /* now schedule the USB transfer */ |
| usbdev = adev->usbdev; |
| outpipe = usb_sndbulkpipe(usbdev, adev->bulkoutep); |
| |
| usb_fill_bulk_urb(txurb, usbdev, outpipe, |
| txbuf, /* dataptr */ |
| wlanpkt_len + USB_TXBUF_HDRSIZE, /* size */ |
| acxusb_i_complete_tx, /* handler */ |
| tx /* handler param */ |
| ); |
| |
| txurb->transfer_flags = URB_ASYNC_UNLINK|URB_ZERO_PACKET; |
| ucode = usb_submit_urb(txurb, GFP_ATOMIC); |
| log(L_USBRXTX, "SUBMIT TX (%d): outpipe=0x%X buf=%p txsize=%d " |
| "rate=%u errcode=%d\n", txnum, outpipe, txbuf, |
| wlanpkt_len + USB_TXBUF_HDRSIZE, txbuf->rate, ucode); |
| |
| if (unlikely(ucode)) { |
| printk(KERN_ERR "acx: submit_urb() error=%d txsize=%d\n", |
| ucode, wlanpkt_len + USB_TXBUF_HDRSIZE); |
| |
| /* on error, just mark the frame as done and update |
| ** the statistics |
| */ |
| adev->stats.tx_errors++; |
| tx->busy = 0; |
| adev->tx_free++; |
| /* needed? if (adev->tx_free > TX_START_QUEUE) acx_wake_queue(...) */ |
| } |
| end: |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| */ |
| static void |
| acxusb_i_set_rx_mode(struct net_device *ndev) |
| { |
| } |
| |
| |
| /*********************************************************************** |
| */ |
| #ifdef HAVE_TX_TIMEOUT |
| static void |
| acxusb_i_tx_timeout(struct net_device *ndev) |
| { |
| acx_device_t *adev = ndev2adev(ndev); |
| unsigned long flags; |
| int i; |
| |
| FN_ENTER; |
| |
| acx_lock(adev, flags); |
| /* unlink the URBs */ |
| for (i = 0; i < ACX_TX_URB_CNT; i++) { |
| acxusb_unlink_urb(adev->usb_tx[i].urb); |
| adev->usb_tx[i].busy = 0; |
| } |
| adev->tx_free = ACX_TX_URB_CNT; |
| /* TODO: stats update */ |
| acx_unlock(adev, flags); |
| |
| FN_EXIT0; |
| } |
| #endif |
| |
| |
| /*********************************************************************** |
| ** init_module() |
| ** |
| ** This function is invoked upon loading of the kernel module. |
| ** It registers itself at the kernel's USB subsystem. |
| ** |
| ** Returns: Errorcode on failure, 0 on success |
| */ |
| int __init |
| acxusb_e_init_module(void) |
| { |
| log(L_INIT, "USB module " ACX_RELEASE " initialized, " |
| "probing for devices...\n"); |
| return usb_register(&acxusb_driver); |
| } |
| |
| |
| /*********************************************************************** |
| ** cleanup_module() |
| ** |
| ** This function is invoked as last step of the module unloading. It simply |
| ** deregisters this module at the kernel's USB subsystem. |
| */ |
| void __exit |
| acxusb_e_cleanup_module(void) |
| { |
| usb_deregister(&acxusb_driver); |
| } |
| |
| module_init(acxusb_e_init_module); |
| module_exit(acxusb_e_cleanup_module); |
| |
| |
| /*********************************************************************** |
| ** DEBUG STUFF |
| */ |
| #if ACX_DEBUG |
| |
| #ifdef UNUSED |
| static void |
| dump_device(struct usb_device *usbdev) |
| { |
| int i; |
| struct usb_config_descriptor *cd; |
| |
| printk("acx device dump:\n"); |
| printk(" devnum: %d\n", usbdev->devnum); |
| printk(" speed: %d\n", usbdev->speed); |
| printk(" tt: 0x%X\n", (unsigned int)(usbdev->tt)); |
| printk(" ttport: %d\n", (unsigned int)(usbdev->ttport)); |
| printk(" toggle[0]: 0x%X toggle[1]: 0x%X\n", (unsigned int)(usbdev->toggle[0]), (unsigned int)(usbdev->toggle[1])); |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11) |
| /* This saw a change after 2.6.10 */ |
| printk(" ep_in wMaxPacketSize: "); |
| for (i = 0; i < 16; ++i) |
| if (usbdev->ep_in[i] != NULL) |
| printk("%d:%d ", i, usbdev->ep_in[i]->desc.wMaxPacketSize); |
| printk("\n"); |
| printk(" ep_out wMaxPacketSize: "); |
| for (i = 0; i < VEC_SIZE(usbdev->ep_out); ++i) |
| if (usbdev->ep_out[i] != NULL) |
| printk("%d:%d ", i, usbdev->ep_out[i]->desc.wMaxPacketSize); |
| printk("\n"); |
| #else |
| printk(" epmaxpacketin: "); |
| for (i = 0; i < 16; i++) |
| printk("%d ", usbdev->epmaxpacketin[i]); |
| printk("\n"); |
| printk(" epmaxpacketout: "); |
| for (i = 0; i < 16; i++) |
| printk("%d ", usbdev->epmaxpacketout[i]); |
| printk("\n"); |
| #endif |
| printk(" parent: 0x%X\n", (unsigned int)usbdev->parent); |
| printk(" bus: 0x%X\n", (unsigned int)usbdev->bus); |
| #ifdef NO_DATATYPE |
| printk(" configs: "); |
| for (i = 0; i < usbdev->descriptor.bNumConfigurations; i++) |
| printk("0x%X ", usbdev->config[i]); |
| printk("\n"); |
| #endif |
| printk(" actconfig: %p\n", usbdev->actconfig); |
| dump_device_descriptor(&usbdev->descriptor); |
| |
| cd = &usbdev->config->desc; |
| dump_config_descriptor(cd); |
| } |
| |
| |
| /*********************************************************************** |
| */ |
| static void |
| dump_config_descriptor(struct usb_config_descriptor *cd) |
| { |
| printk("Configuration Descriptor:\n"); |
| if (!cd) { |
| printk("NULL\n"); |
| return; |
| } |
| printk(" bLength: %d (0x%X)\n", cd->bLength, cd->bLength); |
| printk(" bDescriptorType: %d (0x%X)\n", cd->bDescriptorType, cd->bDescriptorType); |
| printk(" bNumInterfaces: %d (0x%X)\n", cd->bNumInterfaces, cd->bNumInterfaces); |
| printk(" bConfigurationValue: %d (0x%X)\n", cd->bConfigurationValue, cd->bConfigurationValue); |
| printk(" iConfiguration: %d (0x%X)\n", cd->iConfiguration, cd->iConfiguration); |
| printk(" bmAttributes: %d (0x%X)\n", cd->bmAttributes, cd->bmAttributes); |
| /* printk(" MaxPower: %d (0x%X)\n", cd->bMaxPower, cd->bMaxPower); */ |
| } |
| |
| |
| static void |
| dump_device_descriptor(struct usb_device_descriptor *dd) |
| { |
| printk("Device Descriptor:\n"); |
| if (!dd) { |
| printk("NULL\n"); |
| return; |
| } |
| printk(" bLength: %d (0x%X)\n", dd->bLength, dd->bLength); |
| printk(" bDescriptortype: %d (0x%X)\n", dd->bDescriptorType, dd->bDescriptorType); |
| printk(" bcdUSB: %d (0x%X)\n", dd->bcdUSB, dd->bcdUSB); |
| printk(" bDeviceClass: %d (0x%X)\n", dd->bDeviceClass, dd->bDeviceClass); |
| printk(" bDeviceSubClass: %d (0x%X)\n", dd->bDeviceSubClass, dd->bDeviceSubClass); |
| printk(" bDeviceProtocol: %d (0x%X)\n", dd->bDeviceProtocol, dd->bDeviceProtocol); |
| printk(" bMaxPacketSize0: %d (0x%X)\n", dd->bMaxPacketSize0, dd->bMaxPacketSize0); |
| printk(" idVendor: %d (0x%X)\n", dd->idVendor, dd->idVendor); |
| printk(" idProduct: %d (0x%X)\n", dd->idProduct, dd->idProduct); |
| printk(" bcdDevice: %d (0x%X)\n", dd->bcdDevice, dd->bcdDevice); |
| printk(" iManufacturer: %d (0x%X)\n", dd->iManufacturer, dd->iManufacturer); |
| printk(" iProduct: %d (0x%X)\n", dd->iProduct, dd->iProduct); |
| printk(" iSerialNumber: %d (0x%X)\n", dd->iSerialNumber, dd->iSerialNumber); |
| printk(" bNumConfigurations: %d (0x%X)\n", dd->bNumConfigurations, dd->bNumConfigurations); |
| } |
| #endif /* UNUSED */ |
| |
| #endif /* ACX_DEBUG */ |