diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
commit | 57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch) | |
tree | 5e910f0e82173f4ef4f51111366a3f1299037a7b /drivers/usb/atm |
Initial import
Diffstat (limited to 'drivers/usb/atm')
-rw-r--r-- | drivers/usb/atm/Kconfig | 68 | ||||
-rw-r--r-- | drivers/usb/atm/Makefile | 8 | ||||
-rw-r--r-- | drivers/usb/atm/cxacru.c | 1380 | ||||
-rw-r--r-- | drivers/usb/atm/speedtch.c | 960 | ||||
-rw-r--r-- | drivers/usb/atm/ueagle-atm.c | 2783 | ||||
-rw-r--r-- | drivers/usb/atm/usbatm.c | 1344 | ||||
-rw-r--r-- | drivers/usb/atm/usbatm.h | 199 | ||||
-rw-r--r-- | drivers/usb/atm/xusbatm.c | 229 |
8 files changed, 6971 insertions, 0 deletions
diff --git a/drivers/usb/atm/Kconfig b/drivers/usb/atm/Kconfig new file mode 100644 index 000000000..0f922942a --- /dev/null +++ b/drivers/usb/atm/Kconfig @@ -0,0 +1,68 @@ +# +# USB/ATM DSL configuration +# + +menuconfig USB_ATM + tristate "USB DSL modem support" + depends on ATM + select CRC32 + default n + help + Say Y here if you want to connect a USB Digital Subscriber Line (DSL) + modem to your computer's USB port. You will then need to choose your + modem from the list below. + + To compile this driver as a module, choose M here: the + module will be called usbatm. + +if USB_ATM + +config USB_SPEEDTOUCH + tristate "Speedtouch USB support" + select FW_LOADER + help + Say Y here if you have an SpeedTouch USB or SpeedTouch 330 + modem. In order to use your modem you will need to install the + two parts of the firmware, extracted by the user space tools; see + <http://www.linux-usb.org/SpeedTouch/> for details. + + To compile this driver as a module, choose M here: the + module will be called speedtch. + +config USB_CXACRU + tristate "Conexant AccessRunner USB support" + select FW_LOADER + help + Say Y here if you have an ADSL USB modem based on the Conexant + AccessRunner chipset. In order to use your modem you will need to + install the firmware, extracted by the user space tools; see + <http://accessrunner.sourceforge.net/> for details. + + To compile this driver as a module, choose M here: the + module will be called cxacru. + +config USB_UEAGLEATM + tristate "ADI 930 and eagle USB DSL modem" + select FW_LOADER + help + Say Y here if you have an ADSL USB modem based on the ADI 930 + or eagle chipset. In order to use your modem you will need to + install firmwares and CMV (Command Management Variables); see + <https://gna.org/projects/ueagleatm/> for details. + + To compile this driver as a module, choose M here: the + module will be called ueagle-atm. + +config USB_XUSBATM + tristate "Other USB DSL modem support" + help + Say Y here if you have a DSL USB modem not explicitly supported by + another USB DSL drivers. In order to use your modem you will need to + pass the vendor ID, product ID, and endpoint numbers for transmission + and reception as module parameters. You may need to initialize + the modem using a user space utility (a firmware loader for example). + + To compile this driver as a module, choose M here: the + module will be called xusbatm. + +endif # USB_ATM diff --git a/drivers/usb/atm/Makefile b/drivers/usb/atm/Makefile new file mode 100644 index 000000000..ac278946b --- /dev/null +++ b/drivers/usb/atm/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for USB ATM/xDSL drivers +# +obj-$(CONFIG_USB_CXACRU) += cxacru.o +obj-$(CONFIG_USB_SPEEDTOUCH) += speedtch.o +obj-$(CONFIG_USB_UEAGLEATM) += ueagle-atm.o +obj-$(CONFIG_USB_ATM) += usbatm.o +obj-$(CONFIG_USB_XUSBATM) += xusbatm.o diff --git a/drivers/usb/atm/cxacru.c b/drivers/usb/atm/cxacru.c new file mode 100644 index 000000000..febfe42ac --- /dev/null +++ b/drivers/usb/atm/cxacru.c @@ -0,0 +1,1380 @@ +/****************************************************************************** + * cxacru.c - driver for USB ADSL modems based on + * Conexant AccessRunner chipset + * + * Copyright (C) 2004 David Woodhouse, Duncan Sands, Roman Kagan + * Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru) + * Copyright (C) 2007 Simon Arlott + * Copyright (C) 2009 Simon Arlott + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +/* + * Credit is due for Josep Comas, who created the original patch to speedtch.c + * to support the different padding used by the AccessRunner (now generalized + * into usbatm), and the userspace firmware loading utility. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/mutex.h> +#include <asm/unaligned.h> + +#include "usbatm.h" + +#define DRIVER_AUTHOR "Roman Kagan, David Woodhouse, Duncan Sands, Simon Arlott" +#define DRIVER_VERSION "0.4" +#define DRIVER_DESC "Conexant AccessRunner ADSL USB modem driver" + +static const char cxacru_driver_name[] = "cxacru"; + +#define CXACRU_EP_CMD 0x01 /* Bulk/interrupt in/out */ +#define CXACRU_EP_DATA 0x02 /* Bulk in/out */ + +#define CMD_PACKET_SIZE 64 /* Should be maxpacket(ep)? */ +#define CMD_MAX_CONFIG ((CMD_PACKET_SIZE / 4 - 1) / 2) + +/* Addresses */ +#define PLLFCLK_ADDR 0x00350068 +#define PLLBCLK_ADDR 0x0035006c +#define SDRAMEN_ADDR 0x00350010 +#define FW_ADDR 0x00801000 +#define BR_ADDR 0x00180600 +#define SIG_ADDR 0x00180500 +#define BR_STACK_ADDR 0x00187f10 + +/* Values */ +#define SDRAM_ENA 0x1 + +#define CMD_TIMEOUT 2000 /* msecs */ +#define POLL_INTERVAL 1 /* secs */ + +/* commands for interaction with the modem through the control channel before + * firmware is loaded */ +enum cxacru_fw_request { + FW_CMD_ERR, + FW_GET_VER, + FW_READ_MEM, + FW_WRITE_MEM, + FW_RMW_MEM, + FW_CHECKSUM_MEM, + FW_GOTO_MEM, +}; + +/* commands for interaction with the modem through the control channel once + * firmware is loaded */ +enum cxacru_cm_request { + CM_REQUEST_UNDEFINED = 0x80, + CM_REQUEST_TEST, + CM_REQUEST_CHIP_GET_MAC_ADDRESS, + CM_REQUEST_CHIP_GET_DP_VERSIONS, + CM_REQUEST_CHIP_ADSL_LINE_START, + CM_REQUEST_CHIP_ADSL_LINE_STOP, + CM_REQUEST_CHIP_ADSL_LINE_GET_STATUS, + CM_REQUEST_CHIP_ADSL_LINE_GET_SPEED, + CM_REQUEST_CARD_INFO_GET, + CM_REQUEST_CARD_DATA_GET, + CM_REQUEST_CARD_DATA_SET, + CM_REQUEST_COMMAND_HW_IO, + CM_REQUEST_INTERFACE_HW_IO, + CM_REQUEST_CARD_SERIAL_DATA_PATH_GET, + CM_REQUEST_CARD_SERIAL_DATA_PATH_SET, + CM_REQUEST_CARD_CONTROLLER_VERSION_GET, + CM_REQUEST_CARD_GET_STATUS, + CM_REQUEST_CARD_GET_MAC_ADDRESS, + CM_REQUEST_CARD_GET_DATA_LINK_STATUS, + CM_REQUEST_MAX, +}; + +/* commands for interaction with the flash memory + * + * read: response is the contents of the first 60 bytes of flash memory + * write: request contains the 60 bytes of data to write to flash memory + * response is the contents of the first 60 bytes of flash memory + * + * layout: PP PP VV VV MM MM MM MM MM MM ?? ?? SS SS SS SS SS SS SS SS + * SS SS SS SS SS SS SS SS 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * + * P: le16 USB Product ID + * V: le16 USB Vendor ID + * M: be48 MAC Address + * S: le16 ASCII Serial Number + */ +enum cxacru_cm_flash { + CM_FLASH_READ = 0xa1, + CM_FLASH_WRITE = 0xa2 +}; + +/* reply codes to the commands above */ +enum cxacru_cm_status { + CM_STATUS_UNDEFINED, + CM_STATUS_SUCCESS, + CM_STATUS_ERROR, + CM_STATUS_UNSUPPORTED, + CM_STATUS_UNIMPLEMENTED, + CM_STATUS_PARAMETER_ERROR, + CM_STATUS_DBG_LOOPBACK, + CM_STATUS_MAX, +}; + +/* indices into CARD_INFO_GET return array */ +enum cxacru_info_idx { + CXINF_DOWNSTREAM_RATE, + CXINF_UPSTREAM_RATE, + CXINF_LINK_STATUS, + CXINF_LINE_STATUS, + CXINF_MAC_ADDRESS_HIGH, + CXINF_MAC_ADDRESS_LOW, + CXINF_UPSTREAM_SNR_MARGIN, + CXINF_DOWNSTREAM_SNR_MARGIN, + CXINF_UPSTREAM_ATTENUATION, + CXINF_DOWNSTREAM_ATTENUATION, + CXINF_TRANSMITTER_POWER, + CXINF_UPSTREAM_BITS_PER_FRAME, + CXINF_DOWNSTREAM_BITS_PER_FRAME, + CXINF_STARTUP_ATTEMPTS, + CXINF_UPSTREAM_CRC_ERRORS, + CXINF_DOWNSTREAM_CRC_ERRORS, + CXINF_UPSTREAM_FEC_ERRORS, + CXINF_DOWNSTREAM_FEC_ERRORS, + CXINF_UPSTREAM_HEC_ERRORS, + CXINF_DOWNSTREAM_HEC_ERRORS, + CXINF_LINE_STARTABLE, + CXINF_MODULATION, + CXINF_ADSL_HEADEND, + CXINF_ADSL_HEADEND_ENVIRONMENT, + CXINF_CONTROLLER_VERSION, + /* dunno what the missing two mean */ + CXINF_MAX = 0x1c, +}; + +enum cxacru_poll_state { + CXPOLL_STOPPING, + CXPOLL_STOPPED, + CXPOLL_POLLING, + CXPOLL_SHUTDOWN +}; + +struct cxacru_modem_type { + u32 pll_f_clk; + u32 pll_b_clk; + int boot_rom_patch; +}; + +struct cxacru_data { + struct usbatm_data *usbatm; + + const struct cxacru_modem_type *modem_type; + + int line_status; + struct mutex adsl_state_serialize; + int adsl_status; + struct delayed_work poll_work; + u32 card_info[CXINF_MAX]; + struct mutex poll_state_serialize; + enum cxacru_poll_state poll_state; + + /* contol handles */ + struct mutex cm_serialize; + u8 *rcv_buf; + u8 *snd_buf; + struct urb *rcv_urb; + struct urb *snd_urb; + struct completion rcv_done; + struct completion snd_done; +}; + +static int cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm, + u8 *wdata, int wsize, u8 *rdata, int rsize); +static void cxacru_poll_status(struct work_struct *work); + +/* Card info exported through sysfs */ +#define CXACRU__ATTR_INIT(_name) \ +static DEVICE_ATTR(_name, S_IRUGO, cxacru_sysfs_show_##_name, NULL) + +#define CXACRU_CMD_INIT(_name) \ +static DEVICE_ATTR(_name, S_IWUSR | S_IRUGO, \ + cxacru_sysfs_show_##_name, cxacru_sysfs_store_##_name) + +#define CXACRU_SET_INIT(_name) \ +static DEVICE_ATTR(_name, S_IWUSR, \ + NULL, cxacru_sysfs_store_##_name) + +#define CXACRU_ATTR_INIT(_value, _type, _name) \ +static ssize_t cxacru_sysfs_show_##_name(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct cxacru_data *instance = to_usbatm_driver_data(\ + to_usb_interface(dev)); \ +\ + if (instance == NULL) \ + return -ENODEV; \ +\ + return cxacru_sysfs_showattr_##_type(instance->card_info[_value], buf); \ +} \ +CXACRU__ATTR_INIT(_name) + +#define CXACRU_ATTR_CREATE(_v, _t, _name) CXACRU_DEVICE_CREATE_FILE(_name) +#define CXACRU_CMD_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name) +#define CXACRU_SET_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name) +#define CXACRU__ATTR_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name) + +#define CXACRU_ATTR_REMOVE(_v, _t, _name) CXACRU_DEVICE_REMOVE_FILE(_name) +#define CXACRU_CMD_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name) +#define CXACRU_SET_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name) +#define CXACRU__ATTR_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name) + +static ssize_t cxacru_sysfs_showattr_u32(u32 value, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", value); +} + +static ssize_t cxacru_sysfs_showattr_s8(s8 value, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", value); +} + +static ssize_t cxacru_sysfs_showattr_dB(s16 value, char *buf) +{ + if (likely(value >= 0)) { + return snprintf(buf, PAGE_SIZE, "%u.%02u\n", + value / 100, value % 100); + } else { + value = -value; + return snprintf(buf, PAGE_SIZE, "-%u.%02u\n", + value / 100, value % 100); + } +} + +static ssize_t cxacru_sysfs_showattr_bool(u32 value, char *buf) +{ + static char *str[] = { "no", "yes" }; + if (unlikely(value >= ARRAY_SIZE(str))) + return snprintf(buf, PAGE_SIZE, "%u\n", value); + return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); +} + +static ssize_t cxacru_sysfs_showattr_LINK(u32 value, char *buf) +{ + static char *str[] = { NULL, "not connected", "connected", "lost" }; + if (unlikely(value >= ARRAY_SIZE(str) || str[value] == NULL)) + return snprintf(buf, PAGE_SIZE, "%u\n", value); + return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); +} + +static ssize_t cxacru_sysfs_showattr_LINE(u32 value, char *buf) +{ + static char *str[] = { "down", "attempting to activate", + "training", "channel analysis", "exchange", "up", + "waiting", "initialising" + }; + if (unlikely(value >= ARRAY_SIZE(str))) + return snprintf(buf, PAGE_SIZE, "%u\n", value); + return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); +} + +static ssize_t cxacru_sysfs_showattr_MODU(u32 value, char *buf) +{ + static char *str[] = { + "", + "ANSI T1.413", + "ITU-T G.992.1 (G.DMT)", + "ITU-T G.992.2 (G.LITE)" + }; + if (unlikely(value >= ARRAY_SIZE(str))) + return snprintf(buf, PAGE_SIZE, "%u\n", value); + return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); +} + +/* + * This could use MAC_ADDRESS_HIGH and MAC_ADDRESS_LOW, but since + * this data is already in atm_dev there's no point. + * + * MAC_ADDRESS_HIGH = 0x????5544 + * MAC_ADDRESS_LOW = 0x33221100 + * Where 00-55 are bytes 0-5 of the MAC. + */ +static ssize_t cxacru_sysfs_show_mac_address(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cxacru_data *instance = to_usbatm_driver_data( + to_usb_interface(dev)); + + if (instance == NULL || instance->usbatm->atm_dev == NULL) + return -ENODEV; + + return snprintf(buf, PAGE_SIZE, "%pM\n", + instance->usbatm->atm_dev->esi); +} + +static ssize_t cxacru_sysfs_show_adsl_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + static char *str[] = { "running", "stopped" }; + struct cxacru_data *instance = to_usbatm_driver_data( + to_usb_interface(dev)); + u32 value; + + if (instance == NULL) + return -ENODEV; + + value = instance->card_info[CXINF_LINE_STARTABLE]; + if (unlikely(value >= ARRAY_SIZE(str))) + return snprintf(buf, PAGE_SIZE, "%u\n", value); + return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); +} + +static ssize_t cxacru_sysfs_store_adsl_state(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cxacru_data *instance = to_usbatm_driver_data( + to_usb_interface(dev)); + int ret; + int poll = -1; + char str_cmd[8]; + int len = strlen(buf); + + if (!capable(CAP_NET_ADMIN)) + return -EACCES; + + ret = sscanf(buf, "%7s", str_cmd); + if (ret != 1) + return -EINVAL; + ret = 0; + + if (instance == NULL) + return -ENODEV; + + if (mutex_lock_interruptible(&instance->adsl_state_serialize)) + return -ERESTARTSYS; + + if (!strcmp(str_cmd, "stop") || !strcmp(str_cmd, "restart")) { + ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_STOP, NULL, 0, NULL, 0); + if (ret < 0) { + atm_err(instance->usbatm, "change adsl state:" + " CHIP_ADSL_LINE_STOP returned %d\n", ret); + + ret = -EIO; + } else { + ret = len; + poll = CXPOLL_STOPPED; + } + } + + /* Line status is only updated every second + * and the device appears to only react to + * START/STOP every second too. Wait 1.5s to + * be sure that restart will have an effect. */ + if (!strcmp(str_cmd, "restart")) + msleep(1500); + + if (!strcmp(str_cmd, "start") || !strcmp(str_cmd, "restart")) { + ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0); + if (ret < 0) { + atm_err(instance->usbatm, "change adsl state:" + " CHIP_ADSL_LINE_START returned %d\n", ret); + + ret = -EIO; + } else { + ret = len; + poll = CXPOLL_POLLING; + } + } + + if (!strcmp(str_cmd, "poll")) { + ret = len; + poll = CXPOLL_POLLING; + } + + if (ret == 0) { + ret = -EINVAL; + poll = -1; + } + + if (poll == CXPOLL_POLLING) { + mutex_lock(&instance->poll_state_serialize); + switch (instance->poll_state) { + case CXPOLL_STOPPED: + /* start polling */ + instance->poll_state = CXPOLL_POLLING; + break; + + case CXPOLL_STOPPING: + /* abort stop request */ + instance->poll_state = CXPOLL_POLLING; + case CXPOLL_POLLING: + case CXPOLL_SHUTDOWN: + /* don't start polling */ + poll = -1; + } + mutex_unlock(&instance->poll_state_serialize); + } else if (poll == CXPOLL_STOPPED) { + mutex_lock(&instance->poll_state_serialize); + /* request stop */ + if (instance->poll_state == CXPOLL_POLLING) + instance->poll_state = CXPOLL_STOPPING; + mutex_unlock(&instance->poll_state_serialize); + } + + mutex_unlock(&instance->adsl_state_serialize); + + if (poll == CXPOLL_POLLING) + cxacru_poll_status(&instance->poll_work.work); + + return ret; +} + +/* CM_REQUEST_CARD_DATA_GET times out, so no show attribute */ + +static ssize_t cxacru_sysfs_store_adsl_config(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cxacru_data *instance = to_usbatm_driver_data( + to_usb_interface(dev)); + int len = strlen(buf); + int ret, pos, num; + __le32 data[CMD_PACKET_SIZE / 4]; + + if (!capable(CAP_NET_ADMIN)) + return -EACCES; + + if (instance == NULL) + return -ENODEV; + + pos = 0; + num = 0; + while (pos < len) { + int tmp; + u32 index; + u32 value; + + ret = sscanf(buf + pos, "%x=%x%n", &index, &value, &tmp); + if (ret < 2) + return -EINVAL; + if (index < 0 || index > 0x7f) + return -EINVAL; + pos += tmp; + + /* skip trailing newline */ + if (buf[pos] == '\n' && pos == len-1) + pos++; + + data[num * 2 + 1] = cpu_to_le32(index); + data[num * 2 + 2] = cpu_to_le32(value); + num++; + + /* send config values when data buffer is full + * or no more data + */ + if (pos >= len || num >= CMD_MAX_CONFIG) { + char log[CMD_MAX_CONFIG * 12 + 1]; /* %02x=%08x */ + + data[0] = cpu_to_le32(num); + ret = cxacru_cm(instance, CM_REQUEST_CARD_DATA_SET, + (u8 *) data, 4 + num * 8, NULL, 0); + if (ret < 0) { + atm_err(instance->usbatm, + "set card data returned %d\n", ret); + return -EIO; + } + + for (tmp = 0; tmp < num; tmp++) + snprintf(log + tmp*12, 13, " %02x=%08x", + le32_to_cpu(data[tmp * 2 + 1]), + le32_to_cpu(data[tmp * 2 + 2])); + atm_info(instance->usbatm, "config%s\n", log); + num = 0; + } + } + + return len; +} + +/* + * All device attributes are included in CXACRU_ALL_FILES + * so that the same list can be used multiple times: + * INIT (define the device attributes) + * CREATE (create all the device files) + * REMOVE (remove all the device files) + * + * With the last two being defined as needed in the functions + * they are used in before calling CXACRU_ALL_FILES() + */ +#define CXACRU_ALL_FILES(_action) \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_RATE, u32, downstream_rate); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_RATE, u32, upstream_rate); \ +CXACRU_ATTR_##_action(CXINF_LINK_STATUS, LINK, link_status); \ +CXACRU_ATTR_##_action(CXINF_LINE_STATUS, LINE, line_status); \ +CXACRU__ATTR_##_action( mac_address); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_SNR_MARGIN, dB, upstream_snr_margin); \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_SNR_MARGIN, dB, downstream_snr_margin); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_ATTENUATION, dB, upstream_attenuation); \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_ATTENUATION, dB, downstream_attenuation); \ +CXACRU_ATTR_##_action(CXINF_TRANSMITTER_POWER, s8, transmitter_power); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_BITS_PER_FRAME, u32, upstream_bits_per_frame); \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_BITS_PER_FRAME, u32, downstream_bits_per_frame); \ +CXACRU_ATTR_##_action(CXINF_STARTUP_ATTEMPTS, u32, startup_attempts); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_CRC_ERRORS, u32, upstream_crc_errors); \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_CRC_ERRORS, u32, downstream_crc_errors); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_FEC_ERRORS, u32, upstream_fec_errors); \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_FEC_ERRORS, u32, downstream_fec_errors); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_HEC_ERRORS, u32, upstream_hec_errors); \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_HEC_ERRORS, u32, downstream_hec_errors); \ +CXACRU_ATTR_##_action(CXINF_LINE_STARTABLE, bool, line_startable); \ +CXACRU_ATTR_##_action(CXINF_MODULATION, MODU, modulation); \ +CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND, u32, adsl_headend); \ +CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND_ENVIRONMENT, u32, adsl_headend_environment); \ +CXACRU_ATTR_##_action(CXINF_CONTROLLER_VERSION, u32, adsl_controller_version); \ +CXACRU_CMD_##_action( adsl_state); \ +CXACRU_SET_##_action( adsl_config); + +CXACRU_ALL_FILES(INIT); + +/* the following three functions are stolen from drivers/usb/core/message.c */ +static void cxacru_blocking_completion(struct urb *urb) +{ + complete(urb->context); +} + +static void cxacru_timeout_kill(unsigned long data) +{ + usb_unlink_urb((struct urb *) data); +} + +static int cxacru_start_wait_urb(struct urb *urb, struct completion *done, + int *actual_length) +{ + struct timer_list timer; + + init_timer(&timer); + timer.expires = jiffies + msecs_to_jiffies(CMD_TIMEOUT); + timer.data = (unsigned long) urb; + timer.function = cxacru_timeout_kill; + add_timer(&timer); + wait_for_completion(done); + del_timer_sync(&timer); + + if (actual_length) + *actual_length = urb->actual_length; + return urb->status; /* must read status after completion */ +} + +static int cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm, + u8 *wdata, int wsize, u8 *rdata, int rsize) +{ + int ret, actlen; + int offb, offd; + const int stride = CMD_PACKET_SIZE - 4; + u8 *wbuf = instance->snd_buf; + u8 *rbuf = instance->rcv_buf; + int wbuflen = ((wsize - 1) / stride + 1) * CMD_PACKET_SIZE; + int rbuflen = ((rsize - 1) / stride + 1) * CMD_PACKET_SIZE; + + if (wbuflen > PAGE_SIZE || rbuflen > PAGE_SIZE) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "requested transfer size too large (%d, %d)\n", + wbuflen, rbuflen); + ret = -ENOMEM; + goto err; + } + + mutex_lock(&instance->cm_serialize); + + /* submit reading urb before the writing one */ + init_completion(&instance->rcv_done); + ret = usb_submit_urb(instance->rcv_urb, GFP_KERNEL); + if (ret < 0) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "submit of read urb for cm %#x failed (%d)\n", + cm, ret); + goto fail; + } + + memset(wbuf, 0, wbuflen); + /* handle wsize == 0 */ + wbuf[0] = cm; + for (offb = offd = 0; offd < wsize; offd += stride, offb += CMD_PACKET_SIZE) { + wbuf[offb] = cm; + memcpy(wbuf + offb + 4, wdata + offd, min_t(int, stride, wsize - offd)); + } + + instance->snd_urb->transfer_buffer_length = wbuflen; + init_completion(&instance->snd_done); + ret = usb_submit_urb(instance->snd_urb, GFP_KERNEL); + if (ret < 0) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "submit of write urb for cm %#x failed (%d)\n", + cm, ret); + goto fail; + } + + ret = cxacru_start_wait_urb(instance->snd_urb, &instance->snd_done, NULL); + if (ret < 0) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "send of cm %#x failed (%d)\n", cm, ret); + goto fail; + } + + ret = cxacru_start_wait_urb(instance->rcv_urb, &instance->rcv_done, &actlen); + if (ret < 0) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "receive of cm %#x failed (%d)\n", cm, ret); + goto fail; + } + if (actlen % CMD_PACKET_SIZE || !actlen) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "invalid response length to cm %#x: %d\n", + cm, actlen); + ret = -EIO; + goto fail; + } + + /* check the return status and copy the data to the output buffer, if needed */ + for (offb = offd = 0; offd < rsize && offb < actlen; offb += CMD_PACKET_SIZE) { + if (rbuf[offb] != cm) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "wrong cm %#x in response to cm %#x\n", + rbuf[offb], cm); + ret = -EIO; + goto fail; + } + if (rbuf[offb + 1] != CM_STATUS_SUCCESS) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "response to cm %#x failed: %#x\n", + cm, rbuf[offb + 1]); + ret = -EIO; + goto fail; + } + if (offd >= rsize) + break; + memcpy(rdata + offd, rbuf + offb + 4, min_t(int, stride, rsize - offd)); + offd += stride; + } + + ret = offd; + usb_dbg(instance->usbatm, "cm %#x\n", cm); +fail: + mutex_unlock(&instance->cm_serialize); +err: + return ret; +} + +static int cxacru_cm_get_array(struct cxacru_data *instance, enum cxacru_cm_request cm, + u32 *data, int size) +{ + int ret, len; + __le32 *buf; + int offb; + unsigned int offd; + const int stride = CMD_PACKET_SIZE / (4 * 2) - 1; + int buflen = ((size - 1) / stride + 1 + size * 2) * 4; + + buf = kmalloc(buflen, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = cxacru_cm(instance, cm, NULL, 0, (u8 *) buf, buflen); + if (ret < 0) + goto cleanup; + + /* len > 0 && len % 4 == 0 guaranteed by cxacru_cm() */ + len = ret / 4; + for (offb = 0; offb < len; ) { + int l = le32_to_cpu(buf[offb++]); + if (l < 0 || l > stride || l > (len - offb) / 2) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "invalid data length from cm %#x: %d\n", + cm, l); + ret = -EIO; + goto cleanup; + } + while (l--) { + offd = le32_to_cpu(buf[offb++]); + if (offd >= size) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "wrong index %#x in response to cm %#x\n", + offd, cm); + ret = -EIO; + goto cleanup; + } + data[offd] = le32_to_cpu(buf[offb++]); + } + } + + ret = 0; + +cleanup: + kfree(buf); + return ret; +} + +static int cxacru_card_status(struct cxacru_data *instance) +{ + int ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0); + if (ret < 0) { /* firmware not loaded */ + usb_dbg(instance->usbatm, "cxacru_adsl_start: CARD_GET_STATUS returned %d\n", ret); + return ret; + } + return 0; +} + +static void cxacru_remove_device_files(struct usbatm_data *usbatm_instance, + struct atm_dev *atm_dev) +{ + struct usb_interface *intf = usbatm_instance->usb_intf; + + #define CXACRU_DEVICE_REMOVE_FILE(_name) \ + device_remove_file(&intf->dev, &dev_attr_##_name); + CXACRU_ALL_FILES(REMOVE); + #undef CXACRU_DEVICE_REMOVE_FILE +} + +static int cxacru_atm_start(struct usbatm_data *usbatm_instance, + struct atm_dev *atm_dev) +{ + struct cxacru_data *instance = usbatm_instance->driver_data; + struct usb_interface *intf = usbatm_instance->usb_intf; + int ret; + int start_polling = 1; + + dev_dbg(&intf->dev, "%s\n", __func__); + + /* Read MAC address */ + ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_MAC_ADDRESS, NULL, 0, + atm_dev->esi, sizeof(atm_dev->esi)); + if (ret < 0) { + atm_err(usbatm_instance, "cxacru_atm_start: CARD_GET_MAC_ADDRESS returned %d\n", ret); + return ret; + } + + #define CXACRU_DEVICE_CREATE_FILE(_name) \ + ret = device_create_file(&intf->dev, &dev_attr_##_name); \ + if (unlikely(ret)) \ + goto fail_sysfs; + CXACRU_ALL_FILES(CREATE); + #undef CXACRU_DEVICE_CREATE_FILE + + /* start ADSL */ + mutex_lock(&instance->adsl_state_serialize); + ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0); + if (ret < 0) + atm_err(usbatm_instance, "cxacru_atm_start: CHIP_ADSL_LINE_START returned %d\n", ret); + + /* Start status polling */ + mutex_lock(&instance->poll_state_serialize); + switch (instance->poll_state) { + case CXPOLL_STOPPED: + /* start polling */ + instance->poll_state = CXPOLL_POLLING; + break; + + case CXPOLL_STOPPING: + /* abort stop request */ + instance->poll_state = CXPOLL_POLLING; + case CXPOLL_POLLING: + case CXPOLL_SHUTDOWN: + /* don't start polling */ + start_polling = 0; + } + mutex_unlock(&instance->poll_state_serialize); + mutex_unlock(&instance->adsl_state_serialize); + + printk(KERN_INFO "%s%d: %s %pM\n", atm_dev->type, atm_dev->number, + usbatm_instance->description, atm_dev->esi); + + if (start_polling) + cxacru_poll_status(&instance->poll_work.work); + return 0; + +fail_sysfs: + usb_err(usbatm_instance, "cxacru_atm_start: device_create_file failed (%d)\n", ret); + cxacru_remove_device_files(usbatm_instance, atm_dev); + return ret; +} + +static void cxacru_poll_status(struct work_struct *work) +{ + struct cxacru_data *instance = + container_of(work, struct cxacru_data, poll_work.work); + u32 buf[CXINF_MAX] = {}; + struct usbatm_data *usbatm = instance->usbatm; + struct atm_dev *atm_dev = usbatm->atm_dev; + int keep_polling = 1; + int ret; + + ret = cxacru_cm_get_array(instance, CM_REQUEST_CARD_INFO_GET, buf, CXINF_MAX); + if (ret < 0) { + if (ret != -ESHUTDOWN) + atm_warn(usbatm, "poll status: error %d\n", ret); + + mutex_lock(&instance->poll_state_serialize); + if (instance->poll_state != CXPOLL_SHUTDOWN) { + instance->poll_state = CXPOLL_STOPPED; + + if (ret != -ESHUTDOWN) + atm_warn(usbatm, "polling disabled, set adsl_state" + " to 'start' or 'poll' to resume\n"); + } + mutex_unlock(&instance->poll_state_serialize); + goto reschedule; + } + + memcpy(instance->card_info, buf, sizeof(instance->card_info)); + + if (instance->adsl_status != buf[CXINF_LINE_STARTABLE]) { + instance->adsl_status = buf[CXINF_LINE_STARTABLE]; + + switch (instance->adsl_status) { + case 0: + atm_printk(KERN_INFO, usbatm, "ADSL state: running\n"); + break; + + case 1: + atm_printk(KERN_INFO, usbatm, "ADSL state: stopped\n"); + break; + + default: + atm_printk(KERN_INFO, usbatm, "Unknown adsl status %02x\n", instance->adsl_status); + break; + } + } + + if (instance->line_status == buf[CXINF_LINE_STATUS]) + goto reschedule; + + instance->line_status = buf[CXINF_LINE_STATUS]; + switch (instance->line_status) { + case 0: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: down\n"); + break; + + case 1: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: attempting to activate\n"); + break; + + case 2: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: training\n"); + break; + + case 3: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: channel analysis\n"); + break; + + case 4: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: exchange\n"); + break; + + case 5: + atm_dev->link_rate = buf[CXINF_DOWNSTREAM_RATE] * 1000 / 424; + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_FOUND); + + atm_info(usbatm, "ADSL line: up (%d kb/s down | %d kb/s up)\n", + buf[CXINF_DOWNSTREAM_RATE], buf[CXINF_UPSTREAM_RATE]); + break; + + case 6: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: waiting\n"); + break; + + case 7: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: initializing\n"); + break; + + default: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_UNKNOWN); + atm_info(usbatm, "Unknown line state %02x\n", instance->line_status); + break; + } +reschedule: + + mutex_lock(&instance->poll_state_serialize); + if (instance->poll_state == CXPOLL_STOPPING && + instance->adsl_status == 1 && /* stopped */ + instance->line_status == 0) /* down */ + instance->poll_state = CXPOLL_STOPPED; + + if (instance->poll_state == CXPOLL_STOPPED) + keep_polling = 0; + mutex_unlock(&instance->poll_state_serialize); + + if (keep_polling) + schedule_delayed_work(&instance->poll_work, + round_jiffies_relative(POLL_INTERVAL*HZ)); +} + +static int cxacru_fw(struct usb_device *usb_dev, enum cxacru_fw_request fw, + u8 code1, u8 code2, u32 addr, const u8 *data, int size) +{ + int ret; + u8 *buf; + int offd, offb; + const int stride = CMD_PACKET_SIZE - 8; + + buf = (u8 *) __get_free_page(GFP_KERNEL); + if (!buf) + return -ENOMEM; + + offb = offd = 0; + do { + int l = min_t(int, stride, size - offd); + buf[offb++] = fw; + buf[offb++] = l; + buf[offb++] = code1; + buf[offb++] = code2; + put_unaligned(cpu_to_le32(addr), (__le32 *)(buf + offb)); + offb += 4; + addr += l; + if (l) + memcpy(buf + offb, data + offd, l); + if (l < stride) + memset(buf + offb + l, 0, stride - l); + offb += stride; + offd += stride; + if ((offb >= PAGE_SIZE) || (offd >= size)) { + ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD), + buf, offb, NULL, CMD_TIMEOUT); + if (ret < 0) { + dev_dbg(&usb_dev->dev, "sending fw %#x failed\n", fw); + goto cleanup; + } + offb = 0; + } + } while (offd < size); + dev_dbg(&usb_dev->dev, "sent fw %#x\n", fw); + + ret = 0; + +cleanup: + free_page((unsigned long) buf); + return ret; +} + +static void cxacru_upload_firmware(struct cxacru_data *instance, + const struct firmware *fw, + const struct firmware *bp) +{ + int ret; + struct usbatm_data *usbatm = instance->usbatm; + struct usb_device *usb_dev = usbatm->usb_dev; + __le16 signature[] = { usb_dev->descriptor.idVendor, + usb_dev->descriptor.idProduct }; + __le32 val; + + usb_dbg(usbatm, "%s\n", __func__); + + /* FirmwarePllFClkValue */ + val = cpu_to_le32(instance->modem_type->pll_f_clk); + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, PLLFCLK_ADDR, (u8 *) &val, 4); + if (ret) { + usb_err(usbatm, "FirmwarePllFClkValue failed: %d\n", ret); + return; + } + + /* FirmwarePllBClkValue */ + val = cpu_to_le32(instance->modem_type->pll_b_clk); + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, PLLBCLK_ADDR, (u8 *) &val, 4); + if (ret) { + usb_err(usbatm, "FirmwarePllBClkValue failed: %d\n", ret); + return; + } + + /* Enable SDRAM */ + val = cpu_to_le32(SDRAM_ENA); + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, SDRAMEN_ADDR, (u8 *) &val, 4); + if (ret) { + usb_err(usbatm, "Enable SDRAM failed: %d\n", ret); + return; + } + + /* Firmware */ + usb_info(usbatm, "loading firmware\n"); + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, FW_ADDR, fw->data, fw->size); + if (ret) { + usb_err(usbatm, "Firmware upload failed: %d\n", ret); + return; + } + + /* Boot ROM patch */ + if (instance->modem_type->boot_rom_patch) { + usb_info(usbatm, "loading boot ROM patch\n"); + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, BR_ADDR, bp->data, bp->size); + if (ret) { + usb_err(usbatm, "Boot ROM patching failed: %d\n", ret); + return; + } + } + + /* Signature */ + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, SIG_ADDR, (u8 *) signature, 4); + if (ret) { + usb_err(usbatm, "Signature storing failed: %d\n", ret); + return; + } + + usb_info(usbatm, "starting device\n"); + if (instance->modem_type->boot_rom_patch) { + val = cpu_to_le32(BR_ADDR); + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, BR_STACK_ADDR, (u8 *) &val, 4); + } else { + ret = cxacru_fw(usb_dev, FW_GOTO_MEM, 0x0, 0x0, FW_ADDR, NULL, 0); + } + if (ret) { + usb_err(usbatm, "Passing control to firmware failed: %d\n", ret); + return; + } + + /* Delay to allow firmware to start up. */ + msleep_interruptible(1000); + + usb_clear_halt(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD)); + usb_clear_halt(usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_CMD)); + usb_clear_halt(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_DATA)); + usb_clear_halt(usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_DATA)); + + ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0); + if (ret < 0) { + usb_err(usbatm, "modem failed to initialize: %d\n", ret); + return; + } +} + +static int cxacru_find_firmware(struct cxacru_data *instance, + char *phase, const struct firmware **fw_p) +{ + struct usbatm_data *usbatm = instance->usbatm; + struct device *dev = &usbatm->usb_intf->dev; + char buf[16]; + + sprintf(buf, "/*(DEBLOBBED)*/", phase); + usb_dbg(usbatm, "cxacru_find_firmware: looking for %s\n", buf); + + if (reject_firmware(fw_p, buf, dev)) { + usb_dbg(usbatm, "no stage %s firmware found\n", phase); + return -ENOENT; + } + + usb_info(usbatm, "found firmware %s\n", buf); + + return 0; +} + +static int cxacru_heavy_init(struct usbatm_data *usbatm_instance, + struct usb_interface *usb_intf) +{ + const struct firmware *fw, *bp; + struct cxacru_data *instance = usbatm_instance->driver_data; + + int ret = cxacru_find_firmware(instance, "fw", &fw); + if (ret) { + usb_warn(usbatm_instance, "firmware (/*(DEBLOBBED)*/) unavailable (system misconfigured?)\n"); + return ret; + } + + if (instance->modem_type->boot_rom_patch) { + ret = cxacru_find_firmware(instance, "bp", &bp); + if (ret) { + usb_warn(usbatm_instance, "boot ROM patch (/*(DEBLOBBED)*/) unavailable (system misconfigured?)\n"); + release_firmware(fw); + return ret; + } + } + + cxacru_upload_firmware(instance, fw, bp); + + if (instance->modem_type->boot_rom_patch) + release_firmware(bp); + release_firmware(fw); + + ret = cxacru_card_status(instance); + if (ret) + usb_dbg(usbatm_instance, "modem initialisation failed\n"); + else + usb_dbg(usbatm_instance, "done setting up the modem\n"); + + return ret; +} + +static int cxacru_bind(struct usbatm_data *usbatm_instance, + struct usb_interface *intf, const struct usb_device_id *id) +{ + struct cxacru_data *instance; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct usb_host_endpoint *cmd_ep = usb_dev->ep_in[CXACRU_EP_CMD]; + int ret; + + /* instance init */ + instance = kzalloc(sizeof(*instance), GFP_KERNEL); + if (!instance) { + usb_dbg(usbatm_instance, "cxacru_bind: no memory for instance data\n"); + return -ENOMEM; + } + + instance->usbatm = usbatm_instance; + instance->modem_type = (struct cxacru_modem_type *) id->driver_info; + + mutex_init(&instance->poll_state_serialize); + instance->poll_state = CXPOLL_STOPPED; + instance->line_status = -1; + instance->adsl_status = -1; + + mutex_init(&instance->adsl_state_serialize); + + instance->rcv_buf = (u8 *) __get_free_page(GFP_KERNEL); + if (!instance->rcv_buf) { + usb_dbg(usbatm_instance, "cxacru_bind: no memory for rcv_buf\n"); + ret = -ENOMEM; + goto fail; + } + instance->snd_buf = (u8 *) __get_free_page(GFP_KERNEL); + if (!instance->snd_buf) { + usb_dbg(usbatm_instance, "cxacru_bind: no memory for snd_buf\n"); + ret = -ENOMEM; + goto fail; + } + instance->rcv_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!instance->rcv_urb) { + usb_dbg(usbatm_instance, "cxacru_bind: no memory for rcv_urb\n"); + ret = -ENOMEM; + goto fail; + } + instance->snd_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!instance->snd_urb) { + usb_dbg(usbatm_instance, "cxacru_bind: no memory for snd_urb\n"); + ret = -ENOMEM; + goto fail; + } + + if (!cmd_ep) { + usb_dbg(usbatm_instance, "cxacru_bind: no command endpoint\n"); + ret = -ENODEV; + goto fail; + } + + if ((cmd_ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + == USB_ENDPOINT_XFER_INT) { + usb_fill_int_urb(instance->rcv_urb, + usb_dev, usb_rcvintpipe(usb_dev, CXACRU_EP_CMD), + instance->rcv_buf, PAGE_SIZE, + cxacru_blocking_completion, &instance->rcv_done, 1); + + usb_fill_int_urb(instance->snd_urb, + usb_dev, usb_sndintpipe(usb_dev, CXACRU_EP_CMD), + instance->snd_buf, PAGE_SIZE, + cxacru_blocking_completion, &instance->snd_done, 4); + } else { + usb_fill_bulk_urb(instance->rcv_urb, + usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_CMD), + instance->rcv_buf, PAGE_SIZE, + cxacru_blocking_completion, &instance->rcv_done); + + usb_fill_bulk_urb(instance->snd_urb, + usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD), + instance->snd_buf, PAGE_SIZE, + cxacru_blocking_completion, &instance->snd_done); + } + + mutex_init(&instance->cm_serialize); + + INIT_DELAYED_WORK(&instance->poll_work, cxacru_poll_status); + + usbatm_instance->driver_data = instance; + + usbatm_instance->flags = (cxacru_card_status(instance) ? 0 : UDSL_SKIP_HEAVY_INIT); + + return 0; + + fail: + free_page((unsigned long) instance->snd_buf); + free_page((unsigned long) instance->rcv_buf); + usb_free_urb(instance->snd_urb); + usb_free_urb(instance->rcv_urb); + kfree(instance); + + return ret; +} + +static void cxacru_unbind(struct usbatm_data *usbatm_instance, + struct usb_interface *intf) +{ + struct cxacru_data *instance = usbatm_instance->driver_data; + int is_polling = 1; + + usb_dbg(usbatm_instance, "cxacru_unbind entered\n"); + + if (!instance) { + usb_dbg(usbatm_instance, "cxacru_unbind: NULL instance!\n"); + return; + } + + mutex_lock(&instance->poll_state_serialize); + BUG_ON(instance->poll_state == CXPOLL_SHUTDOWN); + + /* ensure that status polling continues unless + * it has already stopped */ + if (instance->poll_state == CXPOLL_STOPPED) + is_polling = 0; + + /* stop polling from being stopped or started */ + instance->poll_state = CXPOLL_SHUTDOWN; + mutex_unlock(&instance->poll_state_serialize); + + if (is_polling) + cancel_delayed_work_sync(&instance->poll_work); + + usb_kill_urb(instance->snd_urb); + usb_kill_urb(instance->rcv_urb); + usb_free_urb(instance->snd_urb); + usb_free_urb(instance->rcv_urb); + + free_page((unsigned long) instance->snd_buf); + free_page((unsigned long) instance->rcv_buf); + + kfree(instance); + + usbatm_instance->driver_data = NULL; +} + +static const struct cxacru_modem_type cxacru_cafe = { + .pll_f_clk = 0x02d874df, + .pll_b_clk = 0x0196a51a, + .boot_rom_patch = 1, +}; + +static const struct cxacru_modem_type cxacru_cb00 = { + .pll_f_clk = 0x5, + .pll_b_clk = 0x3, + .boot_rom_patch = 0, +}; + +static const struct usb_device_id cxacru_usb_ids[] = { + { /* V = Conexant P = ADSL modem (Euphrates project) */ + USB_DEVICE(0x0572, 0xcafe), .driver_info = (unsigned long) &cxacru_cafe + }, + { /* V = Conexant P = ADSL modem (Hasbani project) */ + USB_DEVICE(0x0572, 0xcb00), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Conexant P = ADSL modem */ + USB_DEVICE(0x0572, 0xcb01), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Conexant P = ADSL modem (Well PTI-800) */ + USB_DEVICE(0x0572, 0xcb02), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Conexant P = ADSL modem */ + USB_DEVICE(0x0572, 0xcb06), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Conexant P = ADSL modem (ZTE ZXDSL 852) */ + USB_DEVICE(0x0572, 0xcb07), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Olitec P = ADSL modem version 2 */ + USB_DEVICE(0x08e3, 0x0100), .driver_info = (unsigned long) &cxacru_cafe + }, + { /* V = Olitec P = ADSL modem version 3 */ + USB_DEVICE(0x08e3, 0x0102), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Trust/Amigo Technology Co. P = AMX-CA86U */ + USB_DEVICE(0x0eb0, 0x3457), .driver_info = (unsigned long) &cxacru_cafe + }, + { /* V = Zoom P = 5510 */ + USB_DEVICE(0x1803, 0x5510), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Draytek P = Vigor 318 */ + USB_DEVICE(0x0675, 0x0200), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Zyxel P = 630-C1 aka OMNI ADSL USB (Annex A) */ + USB_DEVICE(0x0586, 0x330a), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Zyxel P = 630-C3 aka OMNI ADSL USB (Annex B) */ + USB_DEVICE(0x0586, 0x330b), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Aethra P = Starmodem UM1020 */ + USB_DEVICE(0x0659, 0x0020), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Aztech Systems P = ? AKA Pirelli AUA-010 */ + USB_DEVICE(0x0509, 0x0812), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Netopia P = Cayman 3341(Annex A)/3351(Annex B) */ + USB_DEVICE(0x100d, 0xcb01), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Netopia P = Cayman 3342(Annex A)/3352(Annex B) */ + USB_DEVICE(0x100d, 0x3342), .driver_info = (unsigned long) &cxacru_cb00 + }, + {} +}; + +MODULE_DEVICE_TABLE(usb, cxacru_usb_ids); + +static struct usbatm_driver cxacru_driver = { + .driver_name = cxacru_driver_name, + .bind = cxacru_bind, + .heavy_init = cxacru_heavy_init, + .unbind = cxacru_unbind, + .atm_start = cxacru_atm_start, + .atm_stop = cxacru_remove_device_files, + .bulk_in = CXACRU_EP_DATA, + .bulk_out = CXACRU_EP_DATA, + .rx_padding = 3, + .tx_padding = 11, +}; + +static int cxacru_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + char buf[15]; + + /* Avoid ADSL routers (cx82310_eth). + * Abort if bDeviceClass is 0xff and iProduct is "USB NET CARD". + */ + if (usb_dev->descriptor.bDeviceClass == USB_CLASS_VENDOR_SPEC + && usb_string(usb_dev, usb_dev->descriptor.iProduct, + buf, sizeof(buf)) > 0) { + if (!strcmp(buf, "USB NET CARD")) { + dev_info(&intf->dev, "ignoring cx82310_eth device\n"); + return -ENODEV; + } + } + + return usbatm_usb_probe(intf, id, &cxacru_driver); +} + +static struct usb_driver cxacru_usb_driver = { + .name = cxacru_driver_name, + .probe = cxacru_usb_probe, + .disconnect = usbatm_usb_disconnect, + .id_table = cxacru_usb_ids +}; + +module_usb_driver(cxacru_usb_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); diff --git a/drivers/usb/atm/speedtch.c b/drivers/usb/atm/speedtch.c new file mode 100644 index 000000000..bcc9ca79e --- /dev/null +++ b/drivers/usb/atm/speedtch.c @@ -0,0 +1,960 @@ +/****************************************************************************** + * speedtch.c - Alcatel SpeedTouch USB xDSL modem driver + * + * Copyright (C) 2001, Alcatel + * Copyright (C) 2003, Duncan Sands + * Copyright (C) 2004, David Woodhouse + * + * Based on "modem_run.c", copyright (C) 2001, Benoit Papillault + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +#include <asm/page.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/firmware.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/timer.h> +#include <linux/types.h> +#include <linux/usb/ch9.h> +#include <linux/workqueue.h> + +#include "usbatm.h" + +#define DRIVER_AUTHOR "Johan Verrept, Duncan Sands <duncan.sands@free.fr>" +#define DRIVER_VERSION "1.10" +#define DRIVER_DESC "Alcatel SpeedTouch USB driver version " DRIVER_VERSION + +static const char speedtch_driver_name[] = "speedtch"; + +#define CTRL_TIMEOUT 2000 /* milliseconds */ +#define DATA_TIMEOUT 2000 /* milliseconds */ + +#define OFFSET_7 0 /* size 1 */ +#define OFFSET_b 1 /* size 8 */ +#define OFFSET_d 9 /* size 4 */ +#define OFFSET_e 13 /* size 1 */ +#define OFFSET_f 14 /* size 1 */ + +#define SIZE_7 1 +#define SIZE_b 8 +#define SIZE_d 4 +#define SIZE_e 1 +#define SIZE_f 1 + +#define MIN_POLL_DELAY 5000 /* milliseconds */ +#define MAX_POLL_DELAY 60000 /* milliseconds */ + +#define RESUBMIT_DELAY 1000 /* milliseconds */ + +#define DEFAULT_BULK_ALTSETTING 1 +#define DEFAULT_ISOC_ALTSETTING 3 +#define DEFAULT_DL_512_FIRST 0 +#define DEFAULT_ENABLE_ISOC 0 +#define DEFAULT_SW_BUFFERING 0 + +static unsigned int altsetting = 0; /* zero means: use the default */ +static bool dl_512_first = DEFAULT_DL_512_FIRST; +static bool enable_isoc = DEFAULT_ENABLE_ISOC; +static bool sw_buffering = DEFAULT_SW_BUFFERING; + +#define DEFAULT_B_MAX_DSL 8128 +#define DEFAULT_MODEM_MODE 11 +#define MODEM_OPTION_LENGTH 16 +static const unsigned char DEFAULT_MODEM_OPTION[MODEM_OPTION_LENGTH] = { + 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static unsigned int BMaxDSL = DEFAULT_B_MAX_DSL; +static unsigned char ModemMode = DEFAULT_MODEM_MODE; +static unsigned char ModemOption[MODEM_OPTION_LENGTH]; +static unsigned int num_ModemOption; + +module_param(altsetting, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(altsetting, + "Alternative setting for data interface (bulk_default: " + __MODULE_STRING(DEFAULT_BULK_ALTSETTING) "; isoc_default: " + __MODULE_STRING(DEFAULT_ISOC_ALTSETTING) ")"); + +module_param(dl_512_first, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(dl_512_first, + "Read 512 bytes before sending firmware (default: " + __MODULE_STRING(DEFAULT_DL_512_FIRST) ")"); + +module_param(enable_isoc, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(enable_isoc, + "Use isochronous transfers if available (default: " + __MODULE_STRING(DEFAULT_ENABLE_ISOC) ")"); + +module_param(sw_buffering, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(sw_buffering, + "Enable software buffering (default: " + __MODULE_STRING(DEFAULT_SW_BUFFERING) ")"); + +module_param(BMaxDSL, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(BMaxDSL, + "default: " __MODULE_STRING(DEFAULT_B_MAX_DSL)); + +module_param(ModemMode, byte, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(ModemMode, + "default: " __MODULE_STRING(DEFAULT_MODEM_MODE)); + +module_param_array(ModemOption, byte, &num_ModemOption, S_IRUGO); +MODULE_PARM_DESC(ModemOption, "default: 0x10,0x00,0x00,0x00,0x20"); + +#define INTERFACE_DATA 1 +#define ENDPOINT_INT 0x81 +#define ENDPOINT_BULK_DATA 0x07 +#define ENDPOINT_ISOC_DATA 0x07 +#define ENDPOINT_FIRMWARE 0x05 + +struct speedtch_params { + unsigned int altsetting; + unsigned int BMaxDSL; + unsigned char ModemMode; + unsigned char ModemOption[MODEM_OPTION_LENGTH]; +}; + +struct speedtch_instance_data { + struct usbatm_data *usbatm; + + struct speedtch_params params; /* set in probe, constant afterwards */ + + struct timer_list status_check_timer; + struct work_struct status_check_work; + + unsigned char last_status; + + int poll_delay; /* milliseconds */ + + struct timer_list resubmit_timer; + struct urb *int_urb; + unsigned char int_data[16]; + + unsigned char scratch_buffer[16]; +}; + +/*************** +** firmware ** +***************/ + +static void speedtch_set_swbuff(struct speedtch_instance_data *instance, int state) +{ + struct usbatm_data *usbatm = instance->usbatm; + struct usb_device *usb_dev = usbatm->usb_dev; + int ret; + + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x32, 0x40, state ? 0x01 : 0x00, 0x00, NULL, 0, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, + "%sabling SW buffering: usb_control_msg returned %d\n", + state ? "En" : "Dis", ret); + else + usb_dbg(usbatm, "speedtch_set_swbuff: %sbled SW buffering\n", state ? "En" : "Dis"); +} + +static void speedtch_test_sequence(struct speedtch_instance_data *instance) +{ + struct usbatm_data *usbatm = instance->usbatm; + struct usb_device *usb_dev = usbatm->usb_dev; + unsigned char *buf = instance->scratch_buffer; + int ret; + + /* URB 147 */ + buf[0] = 0x1c; + buf[1] = 0x50; + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x0b, 0x00, buf, 2, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URB147: %d\n", __func__, ret); + + /* URB 148 */ + buf[0] = 0x32; + buf[1] = 0x00; + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x02, 0x00, buf, 2, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URB148: %d\n", __func__, ret); + + /* URB 149 */ + buf[0] = 0x01; + buf[1] = 0x00; + buf[2] = 0x01; + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x03, 0x00, buf, 3, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URB149: %d\n", __func__, ret); + + /* URB 150 */ + buf[0] = 0x01; + buf[1] = 0x00; + buf[2] = 0x01; + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x04, 0x00, buf, 3, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URB150: %d\n", __func__, ret); + + /* Extra initialisation in recent drivers - gives higher speeds */ + + /* URBext1 */ + buf[0] = instance->params.ModemMode; + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x11, 0x00, buf, 1, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URBext1: %d\n", __func__, ret); + + /* URBext2 */ + /* This seems to be the one which actually triggers the higher sync + rate -- it does require the new firmware too, although it works OK + with older firmware */ + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x14, 0x00, + instance->params.ModemOption, + MODEM_OPTION_LENGTH, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URBext2: %d\n", __func__, ret); + + /* URBext3 */ + buf[0] = instance->params.BMaxDSL & 0xff; + buf[1] = instance->params.BMaxDSL >> 8; + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x12, 0x00, buf, 2, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URBext3: %d\n", __func__, ret); +} + +static int speedtch_upload_firmware(struct speedtch_instance_data *instance, + const struct firmware *fw1, + const struct firmware *fw2) +{ + unsigned char *buffer; + struct usbatm_data *usbatm = instance->usbatm; + struct usb_device *usb_dev = usbatm->usb_dev; + int actual_length; + int ret = 0; + int offset; + + usb_dbg(usbatm, "%s entered\n", __func__); + + if (!(buffer = (unsigned char *)__get_free_page(GFP_KERNEL))) { + ret = -ENOMEM; + usb_dbg(usbatm, "%s: no memory for buffer!\n", __func__); + goto out; + } + + if (!usb_ifnum_to_if(usb_dev, 2)) { + ret = -ENODEV; + usb_dbg(usbatm, "%s: interface not found!\n", __func__); + goto out_free; + } + + /* URB 7 */ + if (dl_512_first) { /* some modems need a read before writing the firmware */ + ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, ENDPOINT_FIRMWARE), + buffer, 0x200, &actual_length, 2000); + + if (ret < 0 && ret != -ETIMEDOUT) + usb_warn(usbatm, "%s: read BLOCK0 from modem failed (%d)!\n", __func__, ret); + else + usb_dbg(usbatm, "%s: BLOCK0 downloaded (%d bytes)\n", __func__, ret); + } + + /* URB 8 : both leds are static green */ + for (offset = 0; offset < fw1->size; offset += PAGE_SIZE) { + int thislen = min_t(int, PAGE_SIZE, fw1->size - offset); + memcpy(buffer, fw1->data + offset, thislen); + + ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, ENDPOINT_FIRMWARE), + buffer, thislen, &actual_length, DATA_TIMEOUT); + + if (ret < 0) { + usb_err(usbatm, "%s: write BLOCK1 to modem failed (%d)!\n", __func__, ret); + goto out_free; + } + usb_dbg(usbatm, "%s: BLOCK1 uploaded (%zu bytes)\n", __func__, fw1->size); + } + + /* USB led blinking green, ADSL led off */ + + /* URB 11 */ + ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, ENDPOINT_FIRMWARE), + buffer, 0x200, &actual_length, DATA_TIMEOUT); + + if (ret < 0) { + usb_err(usbatm, "%s: read BLOCK2 from modem failed (%d)!\n", __func__, ret); + goto out_free; + } + usb_dbg(usbatm, "%s: BLOCK2 downloaded (%d bytes)\n", __func__, actual_length); + + /* URBs 12 to 139 - USB led blinking green, ADSL led off */ + for (offset = 0; offset < fw2->size; offset += PAGE_SIZE) { + int thislen = min_t(int, PAGE_SIZE, fw2->size - offset); + memcpy(buffer, fw2->data + offset, thislen); + + ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, ENDPOINT_FIRMWARE), + buffer, thislen, &actual_length, DATA_TIMEOUT); + + if (ret < 0) { + usb_err(usbatm, "%s: write BLOCK3 to modem failed (%d)!\n", __func__, ret); + goto out_free; + } + } + usb_dbg(usbatm, "%s: BLOCK3 uploaded (%zu bytes)\n", __func__, fw2->size); + + /* USB led static green, ADSL led static red */ + + /* URB 142 */ + ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, ENDPOINT_FIRMWARE), + buffer, 0x200, &actual_length, DATA_TIMEOUT); + + if (ret < 0) { + usb_err(usbatm, "%s: read BLOCK4 from modem failed (%d)!\n", __func__, ret); + goto out_free; + } + + /* success */ + usb_dbg(usbatm, "%s: BLOCK4 downloaded (%d bytes)\n", __func__, actual_length); + + /* Delay to allow firmware to start up. We can do this here + because we're in our own kernel thread anyway. */ + msleep_interruptible(1000); + + if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, instance->params.altsetting)) < 0) { + usb_err(usbatm, "%s: setting interface to %d failed (%d)!\n", __func__, instance->params.altsetting, ret); + goto out_free; + } + + /* Enable software buffering, if requested */ + if (sw_buffering) + speedtch_set_swbuff(instance, 1); + + /* Magic spell; don't ask us what this does */ + speedtch_test_sequence(instance); + + ret = 0; + +out_free: + free_page((unsigned long)buffer); +out: + return ret; +} + +static int speedtch_find_firmware(struct usbatm_data *usbatm, struct usb_interface *intf, + int phase, const struct firmware **fw_p) +{ + struct device *dev = &intf->dev; + const u16 bcdDevice = le16_to_cpu(interface_to_usbdev(intf)->descriptor.bcdDevice); + const u8 major_revision = bcdDevice >> 8; + const u8 minor_revision = bcdDevice & 0xff; + char buf[24]; + + sprintf(buf, "/*(DEBLOBBED)*/", phase, major_revision, minor_revision); + usb_dbg(usbatm, "%s: looking for %s\n", __func__, buf); + + if (reject_firmware(fw_p, buf, dev)) { + sprintf(buf, "/*(DEBLOBBED)*/", phase, major_revision); + usb_dbg(usbatm, "%s: looking for %s\n", __func__, buf); + + if (reject_firmware(fw_p, buf, dev)) { + sprintf(buf, "/*(DEBLOBBED)*/", phase); + usb_dbg(usbatm, "%s: looking for %s\n", __func__, buf); + + if (reject_firmware(fw_p, buf, dev)) { + usb_err(usbatm, "%s: no stage %d firmware found!\n", __func__, phase); + return -ENOENT; + } + } + } + + usb_info(usbatm, "found stage %d firmware %s\n", phase, buf); + + return 0; +} + +static int speedtch_heavy_init(struct usbatm_data *usbatm, struct usb_interface *intf) +{ + const struct firmware *fw1, *fw2; + struct speedtch_instance_data *instance = usbatm->driver_data; + int ret; + + if ((ret = speedtch_find_firmware(usbatm, intf, 1, &fw1)) < 0) + return ret; + + if ((ret = speedtch_find_firmware(usbatm, intf, 2, &fw2)) < 0) { + release_firmware(fw1); + return ret; + } + + if ((ret = speedtch_upload_firmware(instance, fw1, fw2)) < 0) + usb_err(usbatm, "%s: firmware upload failed (%d)!\n", __func__, ret); + + release_firmware(fw2); + release_firmware(fw1); + + return ret; +} + + +/********** +** ATM ** +**********/ + +static int speedtch_read_status(struct speedtch_instance_data *instance) +{ + struct usbatm_data *usbatm = instance->usbatm; + struct usb_device *usb_dev = usbatm->usb_dev; + unsigned char *buf = instance->scratch_buffer; + int ret; + + memset(buf, 0, 16); + + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x12, 0xc0, 0x07, 0x00, buf + OFFSET_7, SIZE_7, + CTRL_TIMEOUT); + if (ret < 0) { + atm_dbg(usbatm, "%s: MSG 7 failed\n", __func__); + return ret; + } + + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x12, 0xc0, 0x0b, 0x00, buf + OFFSET_b, SIZE_b, + CTRL_TIMEOUT); + if (ret < 0) { + atm_dbg(usbatm, "%s: MSG B failed\n", __func__); + return ret; + } + + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x12, 0xc0, 0x0d, 0x00, buf + OFFSET_d, SIZE_d, + CTRL_TIMEOUT); + if (ret < 0) { + atm_dbg(usbatm, "%s: MSG D failed\n", __func__); + return ret; + } + + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x01, 0xc0, 0x0e, 0x00, buf + OFFSET_e, SIZE_e, + CTRL_TIMEOUT); + if (ret < 0) { + atm_dbg(usbatm, "%s: MSG E failed\n", __func__); + return ret; + } + + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x01, 0xc0, 0x0f, 0x00, buf + OFFSET_f, SIZE_f, + CTRL_TIMEOUT); + if (ret < 0) { + atm_dbg(usbatm, "%s: MSG F failed\n", __func__); + return ret; + } + + return 0; +} + +static int speedtch_start_synchro(struct speedtch_instance_data *instance) +{ + struct usbatm_data *usbatm = instance->usbatm; + struct usb_device *usb_dev = usbatm->usb_dev; + unsigned char *buf = instance->scratch_buffer; + int ret; + + atm_dbg(usbatm, "%s entered\n", __func__); + + memset(buf, 0, 2); + + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x12, 0xc0, 0x04, 0x00, + buf, 2, CTRL_TIMEOUT); + + if (ret < 0) + atm_warn(usbatm, "failed to start ADSL synchronisation: %d\n", ret); + else + atm_dbg(usbatm, "%s: modem prodded. %d bytes returned: %02x %02x\n", + __func__, ret, buf[0], buf[1]); + + return ret; +} + +static void speedtch_check_status(struct work_struct *work) +{ + struct speedtch_instance_data *instance = + container_of(work, struct speedtch_instance_data, + status_check_work); + struct usbatm_data *usbatm = instance->usbatm; + struct atm_dev *atm_dev = usbatm->atm_dev; + unsigned char *buf = instance->scratch_buffer; + int down_speed, up_speed, ret; + unsigned char status; + +#ifdef VERBOSE_DEBUG + atm_dbg(usbatm, "%s entered\n", __func__); +#endif + + ret = speedtch_read_status(instance); + if (ret < 0) { + atm_warn(usbatm, "error %d fetching device status\n", ret); + instance->poll_delay = min(2 * instance->poll_delay, MAX_POLL_DELAY); + return; + } + + instance->poll_delay = max(instance->poll_delay / 2, MIN_POLL_DELAY); + + status = buf[OFFSET_7]; + + if ((status != instance->last_status) || !status) { + atm_dbg(usbatm, "%s: line state 0x%02x\n", __func__, status); + + switch (status) { + case 0: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + if (instance->last_status) + atm_info(usbatm, "ADSL line is down\n"); + /* It may never resync again unless we ask it to... */ + ret = speedtch_start_synchro(instance); + break; + + case 0x08: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_UNKNOWN); + atm_info(usbatm, "ADSL line is blocked?\n"); + break; + + case 0x10: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line is synchronising\n"); + break; + + case 0x20: + down_speed = buf[OFFSET_b] | (buf[OFFSET_b + 1] << 8) + | (buf[OFFSET_b + 2] << 16) | (buf[OFFSET_b + 3] << 24); + up_speed = buf[OFFSET_b + 4] | (buf[OFFSET_b + 5] << 8) + | (buf[OFFSET_b + 6] << 16) | (buf[OFFSET_b + 7] << 24); + + if (!(down_speed & 0x0000ffff) && !(up_speed & 0x0000ffff)) { + down_speed >>= 16; + up_speed >>= 16; + } + + atm_dev->link_rate = down_speed * 1000 / 424; + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_FOUND); + + atm_info(usbatm, + "ADSL line is up (%d kb/s down | %d kb/s up)\n", + down_speed, up_speed); + break; + + default: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_UNKNOWN); + atm_info(usbatm, "unknown line state %02x\n", status); + break; + } + + instance->last_status = status; + } +} + +static void speedtch_status_poll(unsigned long data) +{ + struct speedtch_instance_data *instance = (void *)data; + + schedule_work(&instance->status_check_work); + + /* The following check is racy, but the race is harmless */ + if (instance->poll_delay < MAX_POLL_DELAY) + mod_timer(&instance->status_check_timer, jiffies + msecs_to_jiffies(instance->poll_delay)); + else + atm_warn(instance->usbatm, "Too many failures - disabling line status polling\n"); +} + +static void speedtch_resubmit_int(unsigned long data) +{ + struct speedtch_instance_data *instance = (void *)data; + struct urb *int_urb = instance->int_urb; + int ret; + + atm_dbg(instance->usbatm, "%s entered\n", __func__); + + if (int_urb) { + ret = usb_submit_urb(int_urb, GFP_ATOMIC); + if (!ret) + schedule_work(&instance->status_check_work); + else { + atm_dbg(instance->usbatm, "%s: usb_submit_urb failed with result %d\n", __func__, ret); + mod_timer(&instance->resubmit_timer, jiffies + msecs_to_jiffies(RESUBMIT_DELAY)); + } + } +} + +static void speedtch_handle_int(struct urb *int_urb) +{ + struct speedtch_instance_data *instance = int_urb->context; + struct usbatm_data *usbatm = instance->usbatm; + unsigned int count = int_urb->actual_length; + int status = int_urb->status; + int ret; + + /* The magic interrupt for "up state" */ + static const unsigned char up_int[6] = { 0xa1, 0x00, 0x01, 0x00, 0x00, 0x00 }; + /* The magic interrupt for "down state" */ + static const unsigned char down_int[6] = { 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + atm_dbg(usbatm, "%s entered\n", __func__); + + if (status < 0) { + atm_dbg(usbatm, "%s: nonzero urb status %d!\n", __func__, status); + goto fail; + } + + if ((count == 6) && !memcmp(up_int, instance->int_data, 6)) { + del_timer(&instance->status_check_timer); + atm_info(usbatm, "DSL line goes up\n"); + } else if ((count == 6) && !memcmp(down_int, instance->int_data, 6)) { + atm_info(usbatm, "DSL line goes down\n"); + } else { + int i; + + atm_dbg(usbatm, "%s: unknown interrupt packet of length %d:", __func__, count); + for (i = 0; i < count; i++) + printk(" %02x", instance->int_data[i]); + printk("\n"); + goto fail; + } + + if ((int_urb = instance->int_urb)) { + ret = usb_submit_urb(int_urb, GFP_ATOMIC); + schedule_work(&instance->status_check_work); + if (ret < 0) { + atm_dbg(usbatm, "%s: usb_submit_urb failed with result %d\n", __func__, ret); + goto fail; + } + } + + return; + +fail: + if ((int_urb = instance->int_urb)) + mod_timer(&instance->resubmit_timer, jiffies + msecs_to_jiffies(RESUBMIT_DELAY)); +} + +static int speedtch_atm_start(struct usbatm_data *usbatm, struct atm_dev *atm_dev) +{ + struct usb_device *usb_dev = usbatm->usb_dev; + struct speedtch_instance_data *instance = usbatm->driver_data; + int i, ret; + unsigned char mac_str[13]; + + atm_dbg(usbatm, "%s entered\n", __func__); + + /* Set MAC address, it is stored in the serial number */ + memset(atm_dev->esi, 0, sizeof(atm_dev->esi)); + if (usb_string(usb_dev, usb_dev->descriptor.iSerialNumber, mac_str, sizeof(mac_str)) == 12) { + for (i = 0; i < 6; i++) + atm_dev->esi[i] = (hex_to_bin(mac_str[i * 2]) << 4) + + hex_to_bin(mac_str[i * 2 + 1]); + } + + /* Start modem synchronisation */ + ret = speedtch_start_synchro(instance); + + /* Set up interrupt endpoint */ + if (instance->int_urb) { + ret = usb_submit_urb(instance->int_urb, GFP_KERNEL); + if (ret < 0) { + /* Doesn't matter; we'll poll anyway */ + atm_dbg(usbatm, "%s: submission of interrupt URB failed (%d)!\n", __func__, ret); + usb_free_urb(instance->int_urb); + instance->int_urb = NULL; + } + } + + /* Start status polling */ + mod_timer(&instance->status_check_timer, jiffies + msecs_to_jiffies(1000)); + + return 0; +} + +static void speedtch_atm_stop(struct usbatm_data *usbatm, struct atm_dev *atm_dev) +{ + struct speedtch_instance_data *instance = usbatm->driver_data; + struct urb *int_urb = instance->int_urb; + + atm_dbg(usbatm, "%s entered\n", __func__); + + del_timer_sync(&instance->status_check_timer); + + /* + * Since resubmit_timer and int_urb can schedule themselves and + * each other, shutting them down correctly takes some care + */ + instance->int_urb = NULL; /* signal shutdown */ + mb(); + usb_kill_urb(int_urb); + del_timer_sync(&instance->resubmit_timer); + /* + * At this point, speedtch_handle_int and speedtch_resubmit_int + * can run or be running, but instance->int_urb == NULL means that + * they will not reschedule + */ + usb_kill_urb(int_urb); + del_timer_sync(&instance->resubmit_timer); + usb_free_urb(int_urb); + + flush_work(&instance->status_check_work); +} + +static int speedtch_pre_reset(struct usb_interface *intf) +{ + return 0; +} + +static int speedtch_post_reset(struct usb_interface *intf) +{ + return 0; +} + + +/********** +** USB ** +**********/ + +static struct usb_device_id speedtch_usb_ids[] = { + {USB_DEVICE(0x06b9, 0x4061)}, + {} +}; + +MODULE_DEVICE_TABLE(usb, speedtch_usb_ids); + +static int speedtch_usb_probe(struct usb_interface *, const struct usb_device_id *); + +static struct usb_driver speedtch_usb_driver = { + .name = speedtch_driver_name, + .probe = speedtch_usb_probe, + .disconnect = usbatm_usb_disconnect, + .pre_reset = speedtch_pre_reset, + .post_reset = speedtch_post_reset, + .id_table = speedtch_usb_ids +}; + +static void speedtch_release_interfaces(struct usb_device *usb_dev, + int num_interfaces) +{ + struct usb_interface *cur_intf; + int i; + + for (i = 0; i < num_interfaces; i++) + if ((cur_intf = usb_ifnum_to_if(usb_dev, i))) { + usb_set_intfdata(cur_intf, NULL); + usb_driver_release_interface(&speedtch_usb_driver, cur_intf); + } +} + +static int speedtch_bind(struct usbatm_data *usbatm, + struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct usb_interface *cur_intf, *data_intf; + struct speedtch_instance_data *instance; + int ifnum = intf->altsetting->desc.bInterfaceNumber; + int num_interfaces = usb_dev->actconfig->desc.bNumInterfaces; + int i, ret; + int use_isoc; + + usb_dbg(usbatm, "%s entered\n", __func__); + + /* sanity checks */ + + if (usb_dev->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) { + usb_err(usbatm, "%s: wrong device class %d\n", __func__, usb_dev->descriptor.bDeviceClass); + return -ENODEV; + } + + if (!(data_intf = usb_ifnum_to_if(usb_dev, INTERFACE_DATA))) { + usb_err(usbatm, "%s: data interface not found!\n", __func__); + return -ENODEV; + } + + /* claim all interfaces */ + + for (i = 0; i < num_interfaces; i++) { + cur_intf = usb_ifnum_to_if(usb_dev, i); + + if ((i != ifnum) && cur_intf) { + ret = usb_driver_claim_interface(&speedtch_usb_driver, cur_intf, usbatm); + + if (ret < 0) { + usb_err(usbatm, "%s: failed to claim interface %2d (%d)!\n", __func__, i, ret); + speedtch_release_interfaces(usb_dev, i); + return ret; + } + } + } + + instance = kzalloc(sizeof(*instance), GFP_KERNEL); + + if (!instance) { + usb_err(usbatm, "%s: no memory for instance data!\n", __func__); + ret = -ENOMEM; + goto fail_release; + } + + instance->usbatm = usbatm; + + /* module parameters may change at any moment, so take a snapshot */ + instance->params.altsetting = altsetting; + instance->params.BMaxDSL = BMaxDSL; + instance->params.ModemMode = ModemMode; + memcpy(instance->params.ModemOption, DEFAULT_MODEM_OPTION, MODEM_OPTION_LENGTH); + memcpy(instance->params.ModemOption, ModemOption, num_ModemOption); + use_isoc = enable_isoc; + + if (instance->params.altsetting) + if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, instance->params.altsetting)) < 0) { + usb_err(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, instance->params.altsetting, ret); + instance->params.altsetting = 0; /* fall back to default */ + } + + if (!instance->params.altsetting && use_isoc) + if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, DEFAULT_ISOC_ALTSETTING)) < 0) { + usb_dbg(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, DEFAULT_ISOC_ALTSETTING, ret); + use_isoc = 0; /* fall back to bulk */ + } + + if (use_isoc) { + const struct usb_host_interface *desc = data_intf->cur_altsetting; + const __u8 target_address = USB_DIR_IN | usbatm->driver->isoc_in; + + use_isoc = 0; /* fall back to bulk if endpoint not found */ + + for (i = 0; i < desc->desc.bNumEndpoints; i++) { + const struct usb_endpoint_descriptor *endpoint_desc = &desc->endpoint[i].desc; + + if ((endpoint_desc->bEndpointAddress == target_address)) { + use_isoc = + usb_endpoint_xfer_isoc(endpoint_desc); + break; + } + } + + if (!use_isoc) + usb_info(usbatm, "isochronous transfer not supported - using bulk\n"); + } + + if (!use_isoc && !instance->params.altsetting) + if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, DEFAULT_BULK_ALTSETTING)) < 0) { + usb_err(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, DEFAULT_BULK_ALTSETTING, ret); + goto fail_free; + } + + if (!instance->params.altsetting) + instance->params.altsetting = use_isoc ? DEFAULT_ISOC_ALTSETTING : DEFAULT_BULK_ALTSETTING; + + usbatm->flags |= (use_isoc ? UDSL_USE_ISOC : 0); + + INIT_WORK(&instance->status_check_work, speedtch_check_status); + init_timer(&instance->status_check_timer); + + instance->status_check_timer.function = speedtch_status_poll; + instance->status_check_timer.data = (unsigned long)instance; + instance->last_status = 0xff; + instance->poll_delay = MIN_POLL_DELAY; + + init_timer(&instance->resubmit_timer); + instance->resubmit_timer.function = speedtch_resubmit_int; + instance->resubmit_timer.data = (unsigned long)instance; + + instance->int_urb = usb_alloc_urb(0, GFP_KERNEL); + + if (instance->int_urb) + usb_fill_int_urb(instance->int_urb, usb_dev, + usb_rcvintpipe(usb_dev, ENDPOINT_INT), + instance->int_data, sizeof(instance->int_data), + speedtch_handle_int, instance, 16); + else + usb_dbg(usbatm, "%s: no memory for interrupt urb!\n", __func__); + + /* check whether the modem already seems to be alive */ + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x12, 0xc0, 0x07, 0x00, + instance->scratch_buffer + OFFSET_7, SIZE_7, 500); + + usbatm->flags |= (ret == SIZE_7 ? UDSL_SKIP_HEAVY_INIT : 0); + + usb_dbg(usbatm, "%s: firmware %s loaded\n", __func__, usbatm->flags & UDSL_SKIP_HEAVY_INIT ? "already" : "not"); + + if (!(usbatm->flags & UDSL_SKIP_HEAVY_INIT)) + if ((ret = usb_reset_device(usb_dev)) < 0) { + usb_err(usbatm, "%s: device reset failed (%d)!\n", __func__, ret); + goto fail_free; + } + + usbatm->driver_data = instance; + + return 0; + +fail_free: + usb_free_urb(instance->int_urb); + kfree(instance); +fail_release: + speedtch_release_interfaces(usb_dev, num_interfaces); + return ret; +} + +static void speedtch_unbind(struct usbatm_data *usbatm, struct usb_interface *intf) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct speedtch_instance_data *instance = usbatm->driver_data; + + usb_dbg(usbatm, "%s entered\n", __func__); + + speedtch_release_interfaces(usb_dev, usb_dev->actconfig->desc.bNumInterfaces); + usb_free_urb(instance->int_urb); + kfree(instance); +} + + +/*********** +** init ** +***********/ + +static struct usbatm_driver speedtch_usbatm_driver = { + .driver_name = speedtch_driver_name, + .bind = speedtch_bind, + .heavy_init = speedtch_heavy_init, + .unbind = speedtch_unbind, + .atm_start = speedtch_atm_start, + .atm_stop = speedtch_atm_stop, + .bulk_in = ENDPOINT_BULK_DATA, + .bulk_out = ENDPOINT_BULK_DATA, + .isoc_in = ENDPOINT_ISOC_DATA +}; + +static int speedtch_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + return usbatm_usb_probe(intf, id, &speedtch_usbatm_driver); +} + +module_usb_driver(speedtch_usb_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c new file mode 100644 index 000000000..750407329 --- /dev/null +++ b/drivers/usb/atm/ueagle-atm.c @@ -0,0 +1,2783 @@ +/*- + * Copyright (c) 2003, 2004 + * Damien Bergamini <damien.bergamini@free.fr>. All rights reserved. + * + * Copyright (c) 2005-2007 Matthieu Castet <castet.matthieu@free.fr> + * Copyright (c) 2005-2007 Stanislaw Gruszka <stf_xl@wp.pl> + * + * This software is available to you under a choice of one of two + * licenses. You may choose to be licensed under the terms of the GNU + * General Public License (GPL) Version 2, available from the file + * COPYING in the main directory of this source tree, or the + * BSD license below: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * GPL license : + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * HISTORY : some part of the code was base on ueagle 1.3 BSD driver, + * Damien Bergamini agree to put his code under a DUAL GPL/BSD license. + * + * The rest of the code was was rewritten from scratch. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/crc32.h> +#include <linux/usb.h> +#include <linux/firmware.h> +#include <linux/ctype.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/mutex.h> +#include <linux/freezer.h> +#include <linux/slab.h> +#include <linux/kernel.h> + +#include <asm/unaligned.h> + +#include "usbatm.h" + +#define EAGLEUSBVERSION "ueagle 1.4" + + +/* + * Debug macros + */ +#define uea_dbg(usb_dev, format, args...) \ + do { \ + if (debug >= 1) \ + dev_dbg(&(usb_dev)->dev, \ + "[ueagle-atm dbg] %s: " format, \ + __func__, ##args); \ + } while (0) + +#define uea_vdbg(usb_dev, format, args...) \ + do { \ + if (debug >= 2) \ + dev_dbg(&(usb_dev)->dev, \ + "[ueagle-atm vdbg] " format, ##args); \ + } while (0) + +#define uea_enters(usb_dev) \ + uea_vdbg(usb_dev, "entering %s\n" , __func__) + +#define uea_leaves(usb_dev) \ + uea_vdbg(usb_dev, "leaving %s\n" , __func__) + +#define uea_err(usb_dev, format, args...) \ + dev_err(&(usb_dev)->dev , "[UEAGLE-ATM] " format , ##args) + +#define uea_warn(usb_dev, format, args...) \ + dev_warn(&(usb_dev)->dev , "[Ueagle-atm] " format, ##args) + +#define uea_info(usb_dev, format, args...) \ + dev_info(&(usb_dev)->dev , "[ueagle-atm] " format, ##args) + +struct intr_pkt; + +/* cmv's from firmware */ +struct uea_cmvs_v1 { + u32 address; + u16 offset; + u32 data; +} __packed; + +struct uea_cmvs_v2 { + u32 group; + u32 address; + u32 offset; + u32 data; +} __packed; + +/* information about currently processed cmv */ +struct cmv_dsc_e1 { + u8 function; + u16 idx; + u32 address; + u16 offset; +}; + +struct cmv_dsc_e4 { + u16 function; + u16 offset; + u16 address; + u16 group; +}; + +union cmv_dsc { + struct cmv_dsc_e1 e1; + struct cmv_dsc_e4 e4; +}; + +struct uea_softc { + struct usb_device *usb_dev; + struct usbatm_data *usbatm; + + int modem_index; + unsigned int driver_info; + int annex; +#define ANNEXA 0 +#define ANNEXB 1 + + int booting; + int reset; + + wait_queue_head_t sync_q; + + struct task_struct *kthread; + u32 data; + u32 data1; + + int cmv_ack; + union cmv_dsc cmv_dsc; + + struct work_struct task; + u16 pageno; + u16 ovl; + + const struct firmware *dsp_firm; + struct urb *urb_int; + + void (*dispatch_cmv) (struct uea_softc *, struct intr_pkt *); + void (*schedule_load_page) (struct uea_softc *, struct intr_pkt *); + int (*stat) (struct uea_softc *); + int (*send_cmvs) (struct uea_softc *); + + /* keep in sync with eaglectl */ + struct uea_stats { + struct { + u32 state; + u32 flags; + u32 mflags; + u32 vidcpe; + u32 vidco; + u32 dsrate; + u32 usrate; + u32 dsunc; + u32 usunc; + u32 dscorr; + u32 uscorr; + u32 txflow; + u32 rxflow; + u32 usattenuation; + u32 dsattenuation; + u32 dsmargin; + u32 usmargin; + u32 firmid; + } phy; + } stats; +}; + +/* + * Elsa IDs + */ +#define ELSA_VID 0x05CC +#define ELSA_PID_PSTFIRM 0x3350 +#define ELSA_PID_PREFIRM 0x3351 + +#define ELSA_PID_A_PREFIRM 0x3352 +#define ELSA_PID_A_PSTFIRM 0x3353 +#define ELSA_PID_B_PREFIRM 0x3362 +#define ELSA_PID_B_PSTFIRM 0x3363 + +/* + * Devolo IDs : pots if (pid & 0x10) + */ +#define DEVOLO_VID 0x1039 +#define DEVOLO_EAGLE_I_A_PID_PSTFIRM 0x2110 +#define DEVOLO_EAGLE_I_A_PID_PREFIRM 0x2111 + +#define DEVOLO_EAGLE_I_B_PID_PSTFIRM 0x2100 +#define DEVOLO_EAGLE_I_B_PID_PREFIRM 0x2101 + +#define DEVOLO_EAGLE_II_A_PID_PSTFIRM 0x2130 +#define DEVOLO_EAGLE_II_A_PID_PREFIRM 0x2131 + +#define DEVOLO_EAGLE_II_B_PID_PSTFIRM 0x2120 +#define DEVOLO_EAGLE_II_B_PID_PREFIRM 0x2121 + +/* + * Reference design USB IDs + */ +#define ANALOG_VID 0x1110 +#define ADI930_PID_PREFIRM 0x9001 +#define ADI930_PID_PSTFIRM 0x9000 + +#define EAGLE_I_PID_PREFIRM 0x9010 /* Eagle I */ +#define EAGLE_I_PID_PSTFIRM 0x900F /* Eagle I */ + +#define EAGLE_IIC_PID_PREFIRM 0x9024 /* Eagle IIC */ +#define EAGLE_IIC_PID_PSTFIRM 0x9023 /* Eagle IIC */ + +#define EAGLE_II_PID_PREFIRM 0x9022 /* Eagle II */ +#define EAGLE_II_PID_PSTFIRM 0x9021 /* Eagle II */ + +#define EAGLE_III_PID_PREFIRM 0x9032 /* Eagle III */ +#define EAGLE_III_PID_PSTFIRM 0x9031 /* Eagle III */ + +#define EAGLE_IV_PID_PREFIRM 0x9042 /* Eagle IV */ +#define EAGLE_IV_PID_PSTFIRM 0x9041 /* Eagle IV */ + +/* + * USR USB IDs + */ +#define USR_VID 0x0BAF +#define MILLER_A_PID_PREFIRM 0x00F2 +#define MILLER_A_PID_PSTFIRM 0x00F1 +#define MILLER_B_PID_PREFIRM 0x00FA +#define MILLER_B_PID_PSTFIRM 0x00F9 +#define HEINEKEN_A_PID_PREFIRM 0x00F6 +#define HEINEKEN_A_PID_PSTFIRM 0x00F5 +#define HEINEKEN_B_PID_PREFIRM 0x00F8 +#define HEINEKEN_B_PID_PSTFIRM 0x00F7 + +#define PREFIRM 0 +#define PSTFIRM (1<<7) +#define AUTO_ANNEX_A (1<<8) +#define AUTO_ANNEX_B (1<<9) + +enum { + ADI930 = 0, + EAGLE_I, + EAGLE_II, + EAGLE_III, + EAGLE_IV +}; + +/* macros for both struct usb_device_id and struct uea_softc */ +#define UEA_IS_PREFIRM(x) \ + (!((x)->driver_info & PSTFIRM)) +#define UEA_CHIP_VERSION(x) \ + ((x)->driver_info & 0xf) + +#define IS_ISDN(x) \ + ((x)->annex & ANNEXB) + +#define INS_TO_USBDEV(ins) (ins->usb_dev) + +#define GET_STATUS(data) \ + ((data >> 8) & 0xf) + +#define IS_OPERATIONAL(sc) \ + ((UEA_CHIP_VERSION(sc) != EAGLE_IV) ? \ + (GET_STATUS(sc->stats.phy.state) == 2) : \ + (sc->stats.phy.state == 7)) + +/* + * Set of macros to handle unaligned data in the firmware blob. + * The FW_GET_BYTE() macro is provided only for consistency. + */ + +#define FW_GET_BYTE(p) (*((__u8 *) (p))) + +#define FW_DIR "/*(DEBLOBBED)*/" +#define EAGLE_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define ADI930_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define EAGLE_I_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define EAGLE_II_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define EAGLE_III_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define EAGLE_IV_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" + +#define DSP4I_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define DSP4P_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define DSP9I_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define DSP9P_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define DSPEI_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define DSPEP_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define FPGA930_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" + +#define CMV4P_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define CMV4PV2_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define CMV4I_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define CMV4IV2_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define CMV9P_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define CMV9PV2_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define CMV9I_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define CMV9IV2_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define CMVEP_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define CMVEPV2_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define CMVEI_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" +#define CMVEIV2_FIRMWARE FW_DIR "/*(DEBLOBBED)*/" + +#define UEA_FW_NAME_MAX 30 +#define NB_MODEM 4 + +#define BULK_TIMEOUT 300 +#define CTRL_TIMEOUT 1000 + +#define ACK_TIMEOUT msecs_to_jiffies(3000) + +#define UEA_INTR_IFACE_NO 0 +#define UEA_US_IFACE_NO 1 +#define UEA_DS_IFACE_NO 2 + +#define FASTEST_ISO_INTF 8 + +#define UEA_BULK_DATA_PIPE 0x02 +#define UEA_IDMA_PIPE 0x04 +#define UEA_INTR_PIPE 0x04 +#define UEA_ISO_DATA_PIPE 0x08 + +#define UEA_E1_SET_BLOCK 0x0001 +#define UEA_E4_SET_BLOCK 0x002c +#define UEA_SET_MODE 0x0003 +#define UEA_SET_2183_DATA 0x0004 +#define UEA_SET_TIMEOUT 0x0011 + +#define UEA_LOOPBACK_OFF 0x0002 +#define UEA_LOOPBACK_ON 0x0003 +#define UEA_BOOT_IDMA 0x0006 +#define UEA_START_RESET 0x0007 +#define UEA_END_RESET 0x0008 + +#define UEA_SWAP_MAILBOX (0x3fcd | 0x4000) +#define UEA_MPTX_START (0x3fce | 0x4000) +#define UEA_MPTX_MAILBOX (0x3fd6 | 0x4000) +#define UEA_MPRX_MAILBOX (0x3fdf | 0x4000) + +/* block information in eagle4 dsp firmware */ +struct block_index { + __le32 PageOffset; + __le32 NotLastBlock; + __le32 dummy; + __le32 PageSize; + __le32 PageAddress; + __le16 dummy1; + __le16 PageNumber; +} __packed; + +#define E4_IS_BOOT_PAGE(PageSize) ((le32_to_cpu(PageSize)) & 0x80000000) +#define E4_PAGE_BYTES(PageSize) ((le32_to_cpu(PageSize) & 0x7fffffff) * 4) + +#define E4_L1_STRING_HEADER 0x10 +#define E4_MAX_PAGE_NUMBER 0x58 +#define E4_NO_SWAPPAGE_HEADERS 0x31 + +/* l1_code is eagle4 dsp firmware format */ +struct l1_code { + u8 string_header[E4_L1_STRING_HEADER]; + u8 page_number_to_block_index[E4_MAX_PAGE_NUMBER]; + struct block_index page_header[E4_NO_SWAPPAGE_HEADERS]; + u8 code[0]; +} __packed; + +/* structures describing a block within a DSP page */ +struct block_info_e1 { + __le16 wHdr; + __le16 wAddress; + __le16 wSize; + __le16 wOvlOffset; + __le16 wOvl; /* overlay */ + __le16 wLast; +} __packed; +#define E1_BLOCK_INFO_SIZE 12 + +struct block_info_e4 { + __be16 wHdr; + __u8 bBootPage; + __u8 bPageNumber; + __be32 dwSize; + __be32 dwAddress; + __be16 wReserved; +} __packed; +#define E4_BLOCK_INFO_SIZE 14 + +#define UEA_BIHDR 0xabcd +#define UEA_RESERVED 0xffff + +/* constants describing cmv type */ +#define E1_PREAMBLE 0x535c +#define E1_MODEMTOHOST 0x01 +#define E1_HOSTTOMODEM 0x10 + +#define E1_MEMACCESS 0x1 +#define E1_ADSLDIRECTIVE 0x7 +#define E1_FUNCTION_TYPE(f) ((f) >> 4) +#define E1_FUNCTION_SUBTYPE(f) ((f) & 0x0f) + +#define E4_MEMACCESS 0 +#define E4_ADSLDIRECTIVE 0xf +#define E4_FUNCTION_TYPE(f) ((f) >> 8) +#define E4_FUNCTION_SIZE(f) ((f) & 0x0f) +#define E4_FUNCTION_SUBTYPE(f) (((f) >> 4) & 0x0f) + +/* for MEMACCESS */ +#define E1_REQUESTREAD 0x0 +#define E1_REQUESTWRITE 0x1 +#define E1_REPLYREAD 0x2 +#define E1_REPLYWRITE 0x3 + +#define E4_REQUESTREAD 0x0 +#define E4_REQUESTWRITE 0x4 +#define E4_REPLYREAD (E4_REQUESTREAD | 1) +#define E4_REPLYWRITE (E4_REQUESTWRITE | 1) + +/* for ADSLDIRECTIVE */ +#define E1_KERNELREADY 0x0 +#define E1_MODEMREADY 0x1 + +#define E4_KERNELREADY 0x0 +#define E4_MODEMREADY 0x1 + +#define E1_MAKEFUNCTION(t, s) (((t) & 0xf) << 4 | ((s) & 0xf)) +#define E4_MAKEFUNCTION(t, st, s) (((t) & 0xf) << 8 | \ + ((st) & 0xf) << 4 | ((s) & 0xf)) + +#define E1_MAKESA(a, b, c, d) \ + (((c) & 0xff) << 24 | \ + ((d) & 0xff) << 16 | \ + ((a) & 0xff) << 8 | \ + ((b) & 0xff)) + +#define E1_GETSA1(a) ((a >> 8) & 0xff) +#define E1_GETSA2(a) (a & 0xff) +#define E1_GETSA3(a) ((a >> 24) & 0xff) +#define E1_GETSA4(a) ((a >> 16) & 0xff) + +#define E1_SA_CNTL E1_MAKESA('C', 'N', 'T', 'L') +#define E1_SA_DIAG E1_MAKESA('D', 'I', 'A', 'G') +#define E1_SA_INFO E1_MAKESA('I', 'N', 'F', 'O') +#define E1_SA_OPTN E1_MAKESA('O', 'P', 'T', 'N') +#define E1_SA_RATE E1_MAKESA('R', 'A', 'T', 'E') +#define E1_SA_STAT E1_MAKESA('S', 'T', 'A', 'T') + +#define E4_SA_CNTL 1 +#define E4_SA_STAT 2 +#define E4_SA_INFO 3 +#define E4_SA_TEST 4 +#define E4_SA_OPTN 5 +#define E4_SA_RATE 6 +#define E4_SA_DIAG 7 +#define E4_SA_CNFG 8 + +/* structures representing a CMV (Configuration and Management Variable) */ +struct cmv_e1 { + __le16 wPreamble; + __u8 bDirection; + __u8 bFunction; + __le16 wIndex; + __le32 dwSymbolicAddress; + __le16 wOffsetAddress; + __le32 dwData; +} __packed; + +struct cmv_e4 { + __be16 wGroup; + __be16 wFunction; + __be16 wOffset; + __be16 wAddress; + __be32 dwData[6]; +} __packed; + +/* structures representing swap information */ +struct swap_info_e1 { + __u8 bSwapPageNo; + __u8 bOvl; /* overlay */ +} __packed; + +struct swap_info_e4 { + __u8 bSwapPageNo; +} __packed; + +/* structures representing interrupt data */ +#define e1_bSwapPageNo u.e1.s1.swapinfo.bSwapPageNo +#define e1_bOvl u.e1.s1.swapinfo.bOvl +#define e4_bSwapPageNo u.e4.s1.swapinfo.bSwapPageNo + +#define INT_LOADSWAPPAGE 0x0001 +#define INT_INCOMINGCMV 0x0002 + +union intr_data_e1 { + struct { + struct swap_info_e1 swapinfo; + __le16 wDataSize; + } __packed s1; + struct { + struct cmv_e1 cmv; + __le16 wDataSize; + } __packed s2; +} __packed; + +union intr_data_e4 { + struct { + struct swap_info_e4 swapinfo; + __le16 wDataSize; + } __packed s1; + struct { + struct cmv_e4 cmv; + __le16 wDataSize; + } __packed s2; +} __packed; + +struct intr_pkt { + __u8 bType; + __u8 bNotification; + __le16 wValue; + __le16 wIndex; + __le16 wLength; + __le16 wInterrupt; + union { + union intr_data_e1 e1; + union intr_data_e4 e4; + } u; +} __packed; + +#define E1_INTR_PKT_SIZE 28 +#define E4_INTR_PKT_SIZE 64 + +static struct usb_driver uea_driver; +static DEFINE_MUTEX(uea_mutex); +static const char * const chip_name[] = { + "ADI930", "Eagle I", "Eagle II", "Eagle III", "Eagle IV"}; + +static int modem_index; +static unsigned int debug; +static unsigned int altsetting[NB_MODEM] = { + [0 ... (NB_MODEM - 1)] = FASTEST_ISO_INTF}; +static bool sync_wait[NB_MODEM]; +static char *cmv_file[NB_MODEM]; +static int annex[NB_MODEM]; + +module_param(debug, uint, 0644); +MODULE_PARM_DESC(debug, "module debug level (0=off,1=on,2=verbose)"); +module_param_array(altsetting, uint, NULL, 0644); +MODULE_PARM_DESC(altsetting, "alternate setting for incoming traffic: 0=bulk, " + "1=isoc slowest, ... , 8=isoc fastest (default)"); +module_param_array(sync_wait, bool, NULL, 0644); +MODULE_PARM_DESC(sync_wait, "wait the synchronisation before starting ATM"); +module_param_array(cmv_file, charp, NULL, 0644); +MODULE_PARM_DESC(cmv_file, + "file name with configuration and management variables"); +module_param_array(annex, uint, NULL, 0644); +MODULE_PARM_DESC(annex, + "manually set annex a/b (0=auto, 1=annex a, 2=annex b)"); + +#define uea_wait(sc, cond, timeo) \ +({ \ + int _r = wait_event_interruptible_timeout(sc->sync_q, \ + (cond) || kthread_should_stop(), timeo); \ + if (kthread_should_stop()) \ + _r = -ENODEV; \ + _r; \ +}) + +#define UPDATE_ATM_STAT(type, val) \ + do { \ + if (sc->usbatm->atm_dev) \ + sc->usbatm->atm_dev->type = val; \ + } while (0) + +#define UPDATE_ATM_SIGNAL(val) \ + do { \ + if (sc->usbatm->atm_dev) \ + atm_dev_signal_change(sc->usbatm->atm_dev, val); \ + } while (0) + + +/* Firmware loading */ +#define LOAD_INTERNAL 0xA0 +#define F8051_USBCS 0x7f92 + +/** + * uea_send_modem_cmd - Send a command for pre-firmware devices. + */ +static int uea_send_modem_cmd(struct usb_device *usb, + u16 addr, u16 size, const u8 *buff) +{ + int ret = -ENOMEM; + u8 *xfer_buff; + + xfer_buff = kmemdup(buff, size, GFP_KERNEL); + if (xfer_buff) { + ret = usb_control_msg(usb, + usb_sndctrlpipe(usb, 0), + LOAD_INTERNAL, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, addr, 0, xfer_buff, + size, CTRL_TIMEOUT); + kfree(xfer_buff); + } + + if (ret < 0) + return ret; + + return (ret == size) ? 0 : -EIO; +} + +static void uea_upload_pre_firmware(const struct firmware *fw_entry, + void *context) +{ + struct usb_device *usb = context; + const u8 *pfw; + u8 value; + u32 crc = 0; + int ret, size; + + uea_enters(usb); + if (!fw_entry) { + uea_err(usb, "firmware is not available\n"); + goto err; + } + + pfw = fw_entry->data; + size = fw_entry->size; + if (size < 4) + goto err_fw_corrupted; + + crc = get_unaligned_le32(pfw); + pfw += 4; + size -= 4; + if (crc32_be(0, pfw, size) != crc) + goto err_fw_corrupted; + + /* + * Start to upload firmware : send reset + */ + value = 1; + ret = uea_send_modem_cmd(usb, F8051_USBCS, sizeof(value), &value); + + if (ret < 0) { + uea_err(usb, "modem reset failed with error %d\n", ret); + goto err; + } + + while (size > 3) { + u8 len = FW_GET_BYTE(pfw); + u16 add = get_unaligned_le16(pfw + 1); + + size -= len + 3; + if (size < 0) + goto err_fw_corrupted; + + ret = uea_send_modem_cmd(usb, add, len, pfw + 3); + if (ret < 0) { + uea_err(usb, "uploading firmware data failed " + "with error %d\n", ret); + goto err; + } + pfw += len + 3; + } + + if (size != 0) + goto err_fw_corrupted; + + /* + * Tell the modem we finish : de-assert reset + */ + value = 0; + ret = uea_send_modem_cmd(usb, F8051_USBCS, 1, &value); + if (ret < 0) + uea_err(usb, "modem de-assert failed with error %d\n", ret); + else + uea_info(usb, "firmware uploaded\n"); + + goto err; + +err_fw_corrupted: + uea_err(usb, "firmware is corrupted\n"); +err: + release_firmware(fw_entry); + uea_leaves(usb); +} + +/** + * uea_load_firmware - Load usb firmware for pre-firmware devices. + */ +static int uea_load_firmware(struct usb_device *usb, unsigned int ver) +{ + int ret; + char *fw_name = EAGLE_FIRMWARE; + + uea_enters(usb); + uea_info(usb, "pre-firmware device, uploading firmware\n"); + + switch (ver) { + case ADI930: + fw_name = ADI930_FIRMWARE; + break; + case EAGLE_I: + fw_name = EAGLE_I_FIRMWARE; + break; + case EAGLE_II: + fw_name = EAGLE_II_FIRMWARE; + break; + case EAGLE_III: + fw_name = EAGLE_III_FIRMWARE; + break; + case EAGLE_IV: + fw_name = EAGLE_IV_FIRMWARE; + break; + } + + ret = reject_firmware_nowait(THIS_MODULE, 1, fw_name, &usb->dev, + GFP_KERNEL, usb, + uea_upload_pre_firmware); + if (ret) + uea_err(usb, "firmware %s is not available\n", fw_name); + else + uea_info(usb, "loading firmware %s\n", fw_name); + + uea_leaves(usb); + return ret; +} + +/* modem management : dsp firmware, send/read CMV, monitoring statistic + */ + +/* + * Make sure that the DSP code provided is safe to use. + */ +static int check_dsp_e1(const u8 *dsp, unsigned int len) +{ + u8 pagecount, blockcount; + u16 blocksize; + u32 pageoffset; + unsigned int i, j, p, pp; + + pagecount = FW_GET_BYTE(dsp); + p = 1; + + /* enough space for page offsets? */ + if (p + 4 * pagecount > len) + return 1; + + for (i = 0; i < pagecount; i++) { + + pageoffset = get_unaligned_le32(dsp + p); + p += 4; + + if (pageoffset == 0) + continue; + + /* enough space for blockcount? */ + if (pageoffset >= len) + return 1; + + pp = pageoffset; + blockcount = FW_GET_BYTE(dsp + pp); + pp += 1; + + for (j = 0; j < blockcount; j++) { + + /* enough space for block header? */ + if (pp + 4 > len) + return 1; + + pp += 2; /* skip blockaddr */ + blocksize = get_unaligned_le16(dsp + pp); + pp += 2; + + /* enough space for block data? */ + if (pp + blocksize > len) + return 1; + + pp += blocksize; + } + } + + return 0; +} + +static int check_dsp_e4(const u8 *dsp, int len) +{ + int i; + struct l1_code *p = (struct l1_code *) dsp; + unsigned int sum = p->code - dsp; + + if (len < sum) + return 1; + + if (strcmp("STRATIPHY ANEXA", p->string_header) != 0 && + strcmp("STRATIPHY ANEXB", p->string_header) != 0) + return 1; + + for (i = 0; i < E4_MAX_PAGE_NUMBER; i++) { + struct block_index *blockidx; + u8 blockno = p->page_number_to_block_index[i]; + if (blockno >= E4_NO_SWAPPAGE_HEADERS) + continue; + + do { + u64 l; + + if (blockno >= E4_NO_SWAPPAGE_HEADERS) + return 1; + + blockidx = &p->page_header[blockno++]; + if ((u8 *)(blockidx + 1) - dsp >= len) + return 1; + + if (le16_to_cpu(blockidx->PageNumber) != i) + return 1; + + l = E4_PAGE_BYTES(blockidx->PageSize); + sum += l; + l += le32_to_cpu(blockidx->PageOffset); + if (l > len) + return 1; + + /* zero is zero regardless endianes */ + } while (blockidx->NotLastBlock); + } + + return (sum == len) ? 0 : 1; +} + +/* + * send data to the idma pipe + * */ +static int uea_idma_write(struct uea_softc *sc, const void *data, u32 size) +{ + int ret = -ENOMEM; + u8 *xfer_buff; + int bytes_read; + + xfer_buff = kmemdup(data, size, GFP_KERNEL); + if (!xfer_buff) { + uea_err(INS_TO_USBDEV(sc), "can't allocate xfer_buff\n"); + return ret; + } + + ret = usb_bulk_msg(sc->usb_dev, + usb_sndbulkpipe(sc->usb_dev, UEA_IDMA_PIPE), + xfer_buff, size, &bytes_read, BULK_TIMEOUT); + + kfree(xfer_buff); + if (ret < 0) + return ret; + if (size != bytes_read) { + uea_err(INS_TO_USBDEV(sc), "size != bytes_read %d %d\n", size, + bytes_read); + return -EIO; + } + + return 0; +} + +static int request_dsp(struct uea_softc *sc) +{ + int ret; + char *dsp_name; + + if (UEA_CHIP_VERSION(sc) == EAGLE_IV) { + if (IS_ISDN(sc)) + dsp_name = DSP4I_FIRMWARE; + else + dsp_name = DSP4P_FIRMWARE; + } else if (UEA_CHIP_VERSION(sc) == ADI930) { + if (IS_ISDN(sc)) + dsp_name = DSP9I_FIRMWARE; + else + dsp_name = DSP9P_FIRMWARE; + } else { + if (IS_ISDN(sc)) + dsp_name = DSPEI_FIRMWARE; + else + dsp_name = DSPEP_FIRMWARE; + } + + ret = reject_firmware(&sc->dsp_firm, dsp_name, &sc->usb_dev->dev); + if (ret < 0) { + uea_err(INS_TO_USBDEV(sc), + "requesting firmware %s failed with error %d\n", + dsp_name, ret); + return ret; + } + + if (UEA_CHIP_VERSION(sc) == EAGLE_IV) + ret = check_dsp_e4(sc->dsp_firm->data, sc->dsp_firm->size); + else + ret = check_dsp_e1(sc->dsp_firm->data, sc->dsp_firm->size); + + if (ret) { + uea_err(INS_TO_USBDEV(sc), "firmware %s is corrupted\n", + dsp_name); + release_firmware(sc->dsp_firm); + sc->dsp_firm = NULL; + return -EILSEQ; + } + + return 0; +} + +/* + * The uea_load_page() function must be called within a process context + */ +static void uea_load_page_e1(struct work_struct *work) +{ + struct uea_softc *sc = container_of(work, struct uea_softc, task); + u16 pageno = sc->pageno; + u16 ovl = sc->ovl; + struct block_info_e1 bi; + + const u8 *p; + u8 pagecount, blockcount; + u16 blockaddr, blocksize; + u32 pageoffset; + int i; + + /* reload firmware when reboot start and it's loaded already */ + if (ovl == 0 && pageno == 0) { + release_firmware(sc->dsp_firm); + sc->dsp_firm = NULL; + } + + if (sc->dsp_firm == NULL && request_dsp(sc) < 0) + return; + + p = sc->dsp_firm->data; + pagecount = FW_GET_BYTE(p); + p += 1; + + if (pageno >= pagecount) + goto bad1; + + p += 4 * pageno; + pageoffset = get_unaligned_le32(p); + + if (pageoffset == 0) + goto bad1; + + p = sc->dsp_firm->data + pageoffset; + blockcount = FW_GET_BYTE(p); + p += 1; + + uea_dbg(INS_TO_USBDEV(sc), + "sending %u blocks for DSP page %u\n", blockcount, pageno); + + bi.wHdr = cpu_to_le16(UEA_BIHDR); + bi.wOvl = cpu_to_le16(ovl); + bi.wOvlOffset = cpu_to_le16(ovl | 0x8000); + + for (i = 0; i < blockcount; i++) { + blockaddr = get_unaligned_le16(p); + p += 2; + + blocksize = get_unaligned_le16(p); + p += 2; + + bi.wSize = cpu_to_le16(blocksize); + bi.wAddress = cpu_to_le16(blockaddr); + bi.wLast = cpu_to_le16((i == blockcount - 1) ? 1 : 0); + + /* send block info through the IDMA pipe */ + if (uea_idma_write(sc, &bi, E1_BLOCK_INFO_SIZE)) + goto bad2; + + /* send block data through the IDMA pipe */ + if (uea_idma_write(sc, p, blocksize)) + goto bad2; + + p += blocksize; + } + + return; + +bad2: + uea_err(INS_TO_USBDEV(sc), "sending DSP block %u failed\n", i); + return; +bad1: + uea_err(INS_TO_USBDEV(sc), "invalid DSP page %u requested\n", pageno); +} + +static void __uea_load_page_e4(struct uea_softc *sc, u8 pageno, int boot) +{ + struct block_info_e4 bi; + struct block_index *blockidx; + struct l1_code *p = (struct l1_code *) sc->dsp_firm->data; + u8 blockno = p->page_number_to_block_index[pageno]; + + bi.wHdr = cpu_to_be16(UEA_BIHDR); + bi.bBootPage = boot; + bi.bPageNumber = pageno; + bi.wReserved = cpu_to_be16(UEA_RESERVED); + + do { + const u8 *blockoffset; + unsigned int blocksize; + + blockidx = &p->page_header[blockno]; + blocksize = E4_PAGE_BYTES(blockidx->PageSize); + blockoffset = sc->dsp_firm->data + le32_to_cpu( + blockidx->PageOffset); + + bi.dwSize = cpu_to_be32(blocksize); + bi.dwAddress = cpu_to_be32(le32_to_cpu(blockidx->PageAddress)); + + uea_dbg(INS_TO_USBDEV(sc), + "sending block %u for DSP page " + "%u size %u address %x\n", + blockno, pageno, blocksize, + le32_to_cpu(blockidx->PageAddress)); + + /* send block info through the IDMA pipe */ + if (uea_idma_write(sc, &bi, E4_BLOCK_INFO_SIZE)) + goto bad; + + /* send block data through the IDMA pipe */ + if (uea_idma_write(sc, blockoffset, blocksize)) + goto bad; + + blockno++; + } while (blockidx->NotLastBlock); + + return; + +bad: + uea_err(INS_TO_USBDEV(sc), "sending DSP block %u failed\n", blockno); + return; +} + +static void uea_load_page_e4(struct work_struct *work) +{ + struct uea_softc *sc = container_of(work, struct uea_softc, task); + u8 pageno = sc->pageno; + int i; + struct block_info_e4 bi; + struct l1_code *p; + + uea_dbg(INS_TO_USBDEV(sc), "sending DSP page %u\n", pageno); + + /* reload firmware when reboot start and it's loaded already */ + if (pageno == 0) { + release_firmware(sc->dsp_firm); + sc->dsp_firm = NULL; + } + + if (sc->dsp_firm == NULL && request_dsp(sc) < 0) + return; + + p = (struct l1_code *) sc->dsp_firm->data; + if (pageno >= le16_to_cpu(p->page_header[0].PageNumber)) { + uea_err(INS_TO_USBDEV(sc), "invalid DSP " + "page %u requested\n", pageno); + return; + } + + if (pageno != 0) { + __uea_load_page_e4(sc, pageno, 0); + return; + } + + uea_dbg(INS_TO_USBDEV(sc), + "sending Main DSP page %u\n", p->page_header[0].PageNumber); + + for (i = 0; i < le16_to_cpu(p->page_header[0].PageNumber); i++) { + if (E4_IS_BOOT_PAGE(p->page_header[i].PageSize)) + __uea_load_page_e4(sc, i, 1); + } + + uea_dbg(INS_TO_USBDEV(sc) , "sending start bi\n"); + + bi.wHdr = cpu_to_be16(UEA_BIHDR); + bi.bBootPage = 0; + bi.bPageNumber = 0xff; + bi.wReserved = cpu_to_be16(UEA_RESERVED); + bi.dwSize = cpu_to_be32(E4_PAGE_BYTES(p->page_header[0].PageSize)); + bi.dwAddress = cpu_to_be32(le32_to_cpu(p->page_header[0].PageAddress)); + + /* send block info through the IDMA pipe */ + if (uea_idma_write(sc, &bi, E4_BLOCK_INFO_SIZE)) + uea_err(INS_TO_USBDEV(sc), "sending DSP start bi failed\n"); +} + +static inline void wake_up_cmv_ack(struct uea_softc *sc) +{ + BUG_ON(sc->cmv_ack); + sc->cmv_ack = 1; + wake_up(&sc->sync_q); +} + +static inline int wait_cmv_ack(struct uea_softc *sc) +{ + int ret = uea_wait(sc, sc->cmv_ack , ACK_TIMEOUT); + + sc->cmv_ack = 0; + + uea_dbg(INS_TO_USBDEV(sc), "wait_event_timeout : %d ms\n", + jiffies_to_msecs(ret)); + + if (ret < 0) + return ret; + + return (ret == 0) ? -ETIMEDOUT : 0; +} + +#define UCDC_SEND_ENCAPSULATED_COMMAND 0x00 + +static int uea_request(struct uea_softc *sc, + u16 value, u16 index, u16 size, const void *data) +{ + u8 *xfer_buff; + int ret = -ENOMEM; + + xfer_buff = kmemdup(data, size, GFP_KERNEL); + if (!xfer_buff) { + uea_err(INS_TO_USBDEV(sc), "can't allocate xfer_buff\n"); + return ret; + } + + ret = usb_control_msg(sc->usb_dev, usb_sndctrlpipe(sc->usb_dev, 0), + UCDC_SEND_ENCAPSULATED_COMMAND, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, xfer_buff, size, CTRL_TIMEOUT); + + kfree(xfer_buff); + if (ret < 0) { + uea_err(INS_TO_USBDEV(sc), "usb_control_msg error %d\n", ret); + return ret; + } + + if (ret != size) { + uea_err(INS_TO_USBDEV(sc), + "usb_control_msg send only %d bytes (instead of %d)\n", + ret, size); + return -EIO; + } + + return 0; +} + +static int uea_cmv_e1(struct uea_softc *sc, + u8 function, u32 address, u16 offset, u32 data) +{ + struct cmv_e1 cmv; + int ret; + + uea_enters(INS_TO_USBDEV(sc)); + uea_vdbg(INS_TO_USBDEV(sc), "Function : %d-%d, Address : %c%c%c%c, " + "offset : 0x%04x, data : 0x%08x\n", + E1_FUNCTION_TYPE(function), + E1_FUNCTION_SUBTYPE(function), + E1_GETSA1(address), E1_GETSA2(address), + E1_GETSA3(address), + E1_GETSA4(address), offset, data); + + /* we send a request, but we expect a reply */ + sc->cmv_dsc.e1.function = function | 0x2; + sc->cmv_dsc.e1.idx++; + sc->cmv_dsc.e1.address = address; + sc->cmv_dsc.e1.offset = offset; + + cmv.wPreamble = cpu_to_le16(E1_PREAMBLE); + cmv.bDirection = E1_HOSTTOMODEM; + cmv.bFunction = function; + cmv.wIndex = cpu_to_le16(sc->cmv_dsc.e1.idx); + put_unaligned_le32(address, &cmv.dwSymbolicAddress); + cmv.wOffsetAddress = cpu_to_le16(offset); + put_unaligned_le32(data >> 16 | data << 16, &cmv.dwData); + + ret = uea_request(sc, UEA_E1_SET_BLOCK, UEA_MPTX_START, + sizeof(cmv), &cmv); + if (ret < 0) + return ret; + ret = wait_cmv_ack(sc); + uea_leaves(INS_TO_USBDEV(sc)); + return ret; +} + +static int uea_cmv_e4(struct uea_softc *sc, + u16 function, u16 group, u16 address, u16 offset, u32 data) +{ + struct cmv_e4 cmv; + int ret; + + uea_enters(INS_TO_USBDEV(sc)); + memset(&cmv, 0, sizeof(cmv)); + + uea_vdbg(INS_TO_USBDEV(sc), "Function : %d-%d, Group : 0x%04x, " + "Address : 0x%04x, offset : 0x%04x, data : 0x%08x\n", + E4_FUNCTION_TYPE(function), E4_FUNCTION_SUBTYPE(function), + group, address, offset, data); + + /* we send a request, but we expect a reply */ + sc->cmv_dsc.e4.function = function | (0x1 << 4); + sc->cmv_dsc.e4.offset = offset; + sc->cmv_dsc.e4.address = address; + sc->cmv_dsc.e4.group = group; + + cmv.wFunction = cpu_to_be16(function); + cmv.wGroup = cpu_to_be16(group); + cmv.wAddress = cpu_to_be16(address); + cmv.wOffset = cpu_to_be16(offset); + cmv.dwData[0] = cpu_to_be32(data); + + ret = uea_request(sc, UEA_E4_SET_BLOCK, UEA_MPTX_START, + sizeof(cmv), &cmv); + if (ret < 0) + return ret; + ret = wait_cmv_ack(sc); + uea_leaves(INS_TO_USBDEV(sc)); + return ret; +} + +static inline int uea_read_cmv_e1(struct uea_softc *sc, + u32 address, u16 offset, u32 *data) +{ + int ret = uea_cmv_e1(sc, E1_MAKEFUNCTION(E1_MEMACCESS, E1_REQUESTREAD), + address, offset, 0); + if (ret < 0) + uea_err(INS_TO_USBDEV(sc), + "reading cmv failed with error %d\n", ret); + else + *data = sc->data; + + return ret; +} + +static inline int uea_read_cmv_e4(struct uea_softc *sc, + u8 size, u16 group, u16 address, u16 offset, u32 *data) +{ + int ret = uea_cmv_e4(sc, E4_MAKEFUNCTION(E4_MEMACCESS, + E4_REQUESTREAD, size), + group, address, offset, 0); + if (ret < 0) + uea_err(INS_TO_USBDEV(sc), + "reading cmv failed with error %d\n", ret); + else { + *data = sc->data; + /* size is in 16-bit word quantities */ + if (size > 2) + *(data + 1) = sc->data1; + } + return ret; +} + +static inline int uea_write_cmv_e1(struct uea_softc *sc, + u32 address, u16 offset, u32 data) +{ + int ret = uea_cmv_e1(sc, E1_MAKEFUNCTION(E1_MEMACCESS, E1_REQUESTWRITE), + address, offset, data); + if (ret < 0) + uea_err(INS_TO_USBDEV(sc), + "writing cmv failed with error %d\n", ret); + + return ret; +} + +static inline int uea_write_cmv_e4(struct uea_softc *sc, + u8 size, u16 group, u16 address, u16 offset, u32 data) +{ + int ret = uea_cmv_e4(sc, E4_MAKEFUNCTION(E4_MEMACCESS, + E4_REQUESTWRITE, size), + group, address, offset, data); + if (ret < 0) + uea_err(INS_TO_USBDEV(sc), + "writing cmv failed with error %d\n", ret); + + return ret; +} + +static void uea_set_bulk_timeout(struct uea_softc *sc, u32 dsrate) +{ + int ret; + u16 timeout; + + /* in bulk mode the modem have problem with high rate + * changing internal timing could improve things, but the + * value is mysterious. + * ADI930 don't support it (-EPIPE error). + */ + + if (UEA_CHIP_VERSION(sc) == ADI930 || + altsetting[sc->modem_index] > 0 || + sc->stats.phy.dsrate == dsrate) + return; + + /* Original timming (1Mbit/s) from ADI (used in windows driver) */ + timeout = (dsrate <= 1024*1024) ? 0 : 1; + ret = uea_request(sc, UEA_SET_TIMEOUT, timeout, 0, NULL); + uea_info(INS_TO_USBDEV(sc), "setting new timeout %d%s\n", + timeout, ret < 0 ? " failed" : ""); + +} + +/* + * Monitor the modem and update the stat + * return 0 if everything is ok + * return < 0 if an error occurs (-EAGAIN reboot needed) + */ +static int uea_stat_e1(struct uea_softc *sc) +{ + u32 data; + int ret; + + uea_enters(INS_TO_USBDEV(sc)); + data = sc->stats.phy.state; + + ret = uea_read_cmv_e1(sc, E1_SA_STAT, 0, &sc->stats.phy.state); + if (ret < 0) + return ret; + + switch (GET_STATUS(sc->stats.phy.state)) { + case 0: /* not yet synchronized */ + uea_dbg(INS_TO_USBDEV(sc), + "modem not yet synchronized\n"); + return 0; + + case 1: /* initialization */ + uea_dbg(INS_TO_USBDEV(sc), "modem initializing\n"); + return 0; + + case 2: /* operational */ + uea_vdbg(INS_TO_USBDEV(sc), "modem operational\n"); + break; + + case 3: /* fail ... */ + uea_info(INS_TO_USBDEV(sc), "modem synchronization failed" + " (may be try other cmv/dsp)\n"); + return -EAGAIN; + + case 4 ... 6: /* test state */ + uea_warn(INS_TO_USBDEV(sc), + "modem in test mode - not supported\n"); + return -EAGAIN; + + case 7: /* fast-retain ... */ + uea_info(INS_TO_USBDEV(sc), "modem in fast-retain mode\n"); + return 0; + default: + uea_err(INS_TO_USBDEV(sc), "modem invalid SW mode %d\n", + GET_STATUS(sc->stats.phy.state)); + return -EAGAIN; + } + + if (GET_STATUS(data) != 2) { + uea_request(sc, UEA_SET_MODE, UEA_LOOPBACK_OFF, 0, NULL); + uea_info(INS_TO_USBDEV(sc), "modem operational\n"); + + /* release the dsp firmware as it is not needed until + * the next failure + */ + release_firmware(sc->dsp_firm); + sc->dsp_firm = NULL; + } + + /* always update it as atm layer could not be init when we switch to + * operational state + */ + UPDATE_ATM_SIGNAL(ATM_PHY_SIG_FOUND); + + /* wake up processes waiting for synchronization */ + wake_up(&sc->sync_q); + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 2, &sc->stats.phy.flags); + if (ret < 0) + return ret; + sc->stats.phy.mflags |= sc->stats.phy.flags; + + /* in case of a flags ( for example delineation LOSS (& 0x10)), + * we check the status again in order to detect the failure earlier + */ + if (sc->stats.phy.flags) { + uea_dbg(INS_TO_USBDEV(sc), "Stat flag = 0x%x\n", + sc->stats.phy.flags); + return 0; + } + + ret = uea_read_cmv_e1(sc, E1_SA_RATE, 0, &data); + if (ret < 0) + return ret; + + uea_set_bulk_timeout(sc, (data >> 16) * 32); + sc->stats.phy.dsrate = (data >> 16) * 32; + sc->stats.phy.usrate = (data & 0xffff) * 32; + UPDATE_ATM_STAT(link_rate, sc->stats.phy.dsrate * 1000 / 424); + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 23, &data); + if (ret < 0) + return ret; + sc->stats.phy.dsattenuation = (data & 0xff) / 2; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 47, &data); + if (ret < 0) + return ret; + sc->stats.phy.usattenuation = (data & 0xff) / 2; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 25, &sc->stats.phy.dsmargin); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 49, &sc->stats.phy.usmargin); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 51, &sc->stats.phy.rxflow); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 52, &sc->stats.phy.txflow); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 54, &sc->stats.phy.dsunc); + if (ret < 0) + return ret; + + /* only for atu-c */ + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 58, &sc->stats.phy.usunc); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 53, &sc->stats.phy.dscorr); + if (ret < 0) + return ret; + + /* only for atu-c */ + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 57, &sc->stats.phy.uscorr); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_INFO, 8, &sc->stats.phy.vidco); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_INFO, 13, &sc->stats.phy.vidcpe); + if (ret < 0) + return ret; + + return 0; +} + +static int uea_stat_e4(struct uea_softc *sc) +{ + u32 data; + u32 tmp_arr[2]; + int ret; + + uea_enters(INS_TO_USBDEV(sc)); + data = sc->stats.phy.state; + + /* XXX only need to be done before operationnal... */ + ret = uea_read_cmv_e4(sc, 1, E4_SA_STAT, 0, 0, &sc->stats.phy.state); + if (ret < 0) + return ret; + + switch (sc->stats.phy.state) { + case 0x0: /* not yet synchronized */ + case 0x1: + case 0x3: + case 0x4: + uea_dbg(INS_TO_USBDEV(sc), "modem not yet " + "synchronized\n"); + return 0; + case 0x5: /* initialization */ + case 0x6: + case 0x9: + case 0xa: + uea_dbg(INS_TO_USBDEV(sc), "modem initializing\n"); + return 0; + case 0x2: /* fail ... */ + uea_info(INS_TO_USBDEV(sc), "modem synchronization " + "failed (may be try other cmv/dsp)\n"); + return -EAGAIN; + case 0x7: /* operational */ + break; + default: + uea_warn(INS_TO_USBDEV(sc), "unknown state: %x\n", + sc->stats.phy.state); + return 0; + } + + if (data != 7) { + uea_request(sc, UEA_SET_MODE, UEA_LOOPBACK_OFF, 0, NULL); + uea_info(INS_TO_USBDEV(sc), "modem operational\n"); + + /* release the dsp firmware as it is not needed until + * the next failure + */ + release_firmware(sc->dsp_firm); + sc->dsp_firm = NULL; + } + + /* always update it as atm layer could not be init when we switch to + * operational state + */ + UPDATE_ATM_SIGNAL(ATM_PHY_SIG_FOUND); + + /* wake up processes waiting for synchronization */ + wake_up(&sc->sync_q); + + /* TODO improve this state machine : + * we need some CMV info : what they do and their unit + * we should find the equivalent of eagle3- CMV + */ + /* check flags */ + ret = uea_read_cmv_e4(sc, 1, E4_SA_DIAG, 0, 0, &sc->stats.phy.flags); + if (ret < 0) + return ret; + sc->stats.phy.mflags |= sc->stats.phy.flags; + + /* in case of a flags ( for example delineation LOSS (& 0x10)), + * we check the status again in order to detect the failure earlier + */ + if (sc->stats.phy.flags) { + uea_dbg(INS_TO_USBDEV(sc), "Stat flag = 0x%x\n", + sc->stats.phy.flags); + if (sc->stats.phy.flags & 1) /* delineation LOSS */ + return -EAGAIN; + if (sc->stats.phy.flags & 0x4000) /* Reset Flag */ + return -EAGAIN; + return 0; + } + + /* rate data may be in upper or lower half of 64 bit word, strange */ + ret = uea_read_cmv_e4(sc, 4, E4_SA_RATE, 0, 0, tmp_arr); + if (ret < 0) + return ret; + data = (tmp_arr[0]) ? tmp_arr[0] : tmp_arr[1]; + sc->stats.phy.usrate = data / 1000; + + ret = uea_read_cmv_e4(sc, 4, E4_SA_RATE, 1, 0, tmp_arr); + if (ret < 0) + return ret; + data = (tmp_arr[0]) ? tmp_arr[0] : tmp_arr[1]; + uea_set_bulk_timeout(sc, data / 1000); + sc->stats.phy.dsrate = data / 1000; + UPDATE_ATM_STAT(link_rate, sc->stats.phy.dsrate * 1000 / 424); + + ret = uea_read_cmv_e4(sc, 1, E4_SA_INFO, 68, 1, &data); + if (ret < 0) + return ret; + sc->stats.phy.dsattenuation = data / 10; + + ret = uea_read_cmv_e4(sc, 1, E4_SA_INFO, 69, 1, &data); + if (ret < 0) + return ret; + sc->stats.phy.usattenuation = data / 10; + + ret = uea_read_cmv_e4(sc, 1, E4_SA_INFO, 68, 3, &data); + if (ret < 0) + return ret; + sc->stats.phy.dsmargin = data / 2; + + ret = uea_read_cmv_e4(sc, 1, E4_SA_INFO, 69, 3, &data); + if (ret < 0) + return ret; + sc->stats.phy.usmargin = data / 10; + + return 0; +} + +static void cmvs_file_name(struct uea_softc *sc, char *const cmv_name, int ver) +{ + char file_arr[] = "/*(DEBLOBBED)*/"; + char *file; + + kparam_block_sysfs_write(cmv_file); + /* set proper name corresponding modem version and line type */ + if (cmv_file[sc->modem_index] == NULL) { + if (UEA_CHIP_VERSION(sc) == ADI930) + file_arr[3] = '9'; + else if (UEA_CHIP_VERSION(sc) == EAGLE_IV) + file_arr[3] = '4'; + else + file_arr[3] = 'e'; + + file_arr[4] = IS_ISDN(sc) ? 'i' : 'p'; + file = file_arr; + } else + file = cmv_file[sc->modem_index]; + + strcpy(cmv_name, FW_DIR); + strlcat(cmv_name, file, UEA_FW_NAME_MAX); + if (ver == 2) + strlcat(cmv_name, ".v2", UEA_FW_NAME_MAX); + kparam_unblock_sysfs_write(cmv_file); +} + +static int request_cmvs_old(struct uea_softc *sc, + void **cmvs, const struct firmware **fw) +{ + int ret, size; + u8 *data; + char cmv_name[UEA_FW_NAME_MAX]; /* 30 bytes stack variable */ + + cmvs_file_name(sc, cmv_name, 1); + ret = reject_firmware(fw, cmv_name, &sc->usb_dev->dev); + if (ret < 0) { + uea_err(INS_TO_USBDEV(sc), + "requesting firmware %s failed with error %d\n", + cmv_name, ret); + return ret; + } + + data = (u8 *) (*fw)->data; + size = (*fw)->size; + if (size < 1) + goto err_fw_corrupted; + + if (size != *data * sizeof(struct uea_cmvs_v1) + 1) + goto err_fw_corrupted; + + *cmvs = (void *)(data + 1); + return *data; + +err_fw_corrupted: + uea_err(INS_TO_USBDEV(sc), "firmware %s is corrupted\n", cmv_name); + release_firmware(*fw); + return -EILSEQ; +} + +static int request_cmvs(struct uea_softc *sc, + void **cmvs, const struct firmware **fw, int *ver) +{ + int ret, size; + u32 crc; + u8 *data; + char cmv_name[UEA_FW_NAME_MAX]; /* 30 bytes stack variable */ + + cmvs_file_name(sc, cmv_name, 2); + ret = reject_firmware(fw, cmv_name, &sc->usb_dev->dev); + if (ret < 0) { + /* if caller can handle old version, try to provide it */ + if (*ver == 1) { + uea_warn(INS_TO_USBDEV(sc), "requesting " + "firmware %s failed, " + "try to get older cmvs\n", cmv_name); + return request_cmvs_old(sc, cmvs, fw); + } + uea_err(INS_TO_USBDEV(sc), + "requesting firmware %s failed with error %d\n", + cmv_name, ret); + return ret; + } + + size = (*fw)->size; + data = (u8 *) (*fw)->data; + if (size < 4 || strncmp(data, "cmv2", 4) != 0) { + if (*ver == 1) { + uea_warn(INS_TO_USBDEV(sc), "firmware %s is corrupted," + " try to get older cmvs\n", cmv_name); + release_firmware(*fw); + return request_cmvs_old(sc, cmvs, fw); + } + goto err_fw_corrupted; + } + + *ver = 2; + + data += 4; + size -= 4; + if (size < 5) + goto err_fw_corrupted; + + crc = get_unaligned_le32(data); + data += 4; + size -= 4; + if (crc32_be(0, data, size) != crc) + goto err_fw_corrupted; + + if (size != *data * sizeof(struct uea_cmvs_v2) + 1) + goto err_fw_corrupted; + + *cmvs = (void *) (data + 1); + return *data; + +err_fw_corrupted: + uea_err(INS_TO_USBDEV(sc), "firmware %s is corrupted\n", cmv_name); + release_firmware(*fw); + return -EILSEQ; +} + +static int uea_send_cmvs_e1(struct uea_softc *sc) +{ + int i, ret, len; + void *cmvs_ptr; + const struct firmware *cmvs_fw; + int ver = 1; /* we can handle v1 cmv firmware version; */ + + /* Enter in R-IDLE (cmv) until instructed otherwise */ + ret = uea_write_cmv_e1(sc, E1_SA_CNTL, 0, 1); + if (ret < 0) + return ret; + + /* Dump firmware version */ + ret = uea_read_cmv_e1(sc, E1_SA_INFO, 10, &sc->stats.phy.firmid); + if (ret < 0) + return ret; + uea_info(INS_TO_USBDEV(sc), "ATU-R firmware version : %x\n", + sc->stats.phy.firmid); + + /* get options */ + ret = len = request_cmvs(sc, &cmvs_ptr, &cmvs_fw, &ver); + if (ret < 0) + return ret; + + /* send options */ + if (ver == 1) { + struct uea_cmvs_v1 *cmvs_v1 = cmvs_ptr; + + uea_warn(INS_TO_USBDEV(sc), "use deprecated cmvs version, " + "please update your firmware\n"); + + for (i = 0; i < len; i++) { + ret = uea_write_cmv_e1(sc, + get_unaligned_le32(&cmvs_v1[i].address), + get_unaligned_le16(&cmvs_v1[i].offset), + get_unaligned_le32(&cmvs_v1[i].data)); + if (ret < 0) + goto out; + } + } else if (ver == 2) { + struct uea_cmvs_v2 *cmvs_v2 = cmvs_ptr; + + for (i = 0; i < len; i++) { + ret = uea_write_cmv_e1(sc, + get_unaligned_le32(&cmvs_v2[i].address), + (u16) get_unaligned_le32(&cmvs_v2[i].offset), + get_unaligned_le32(&cmvs_v2[i].data)); + if (ret < 0) + goto out; + } + } else { + /* This really should not happen */ + uea_err(INS_TO_USBDEV(sc), "bad cmvs version %d\n", ver); + goto out; + } + + /* Enter in R-ACT-REQ */ + ret = uea_write_cmv_e1(sc, E1_SA_CNTL, 0, 2); + uea_vdbg(INS_TO_USBDEV(sc), "Entering in R-ACT-REQ state\n"); + uea_info(INS_TO_USBDEV(sc), "modem started, waiting " + "synchronization...\n"); +out: + release_firmware(cmvs_fw); + return ret; +} + +static int uea_send_cmvs_e4(struct uea_softc *sc) +{ + int i, ret, len; + void *cmvs_ptr; + const struct firmware *cmvs_fw; + int ver = 2; /* we can only handle v2 cmv firmware version; */ + + /* Enter in R-IDLE (cmv) until instructed otherwise */ + ret = uea_write_cmv_e4(sc, 1, E4_SA_CNTL, 0, 0, 1); + if (ret < 0) + return ret; + + /* Dump firmware version */ + /* XXX don't read the 3th byte as it is always 6 */ + ret = uea_read_cmv_e4(sc, 2, E4_SA_INFO, 55, 0, &sc->stats.phy.firmid); + if (ret < 0) + return ret; + uea_info(INS_TO_USBDEV(sc), "ATU-R firmware version : %x\n", + sc->stats.phy.firmid); + + + /* get options */ + ret = len = request_cmvs(sc, &cmvs_ptr, &cmvs_fw, &ver); + if (ret < 0) + return ret; + + /* send options */ + if (ver == 2) { + struct uea_cmvs_v2 *cmvs_v2 = cmvs_ptr; + + for (i = 0; i < len; i++) { + ret = uea_write_cmv_e4(sc, 1, + get_unaligned_le32(&cmvs_v2[i].group), + get_unaligned_le32(&cmvs_v2[i].address), + get_unaligned_le32(&cmvs_v2[i].offset), + get_unaligned_le32(&cmvs_v2[i].data)); + if (ret < 0) + goto out; + } + } else { + /* This really should not happen */ + uea_err(INS_TO_USBDEV(sc), "bad cmvs version %d\n", ver); + goto out; + } + + /* Enter in R-ACT-REQ */ + ret = uea_write_cmv_e4(sc, 1, E4_SA_CNTL, 0, 0, 2); + uea_vdbg(INS_TO_USBDEV(sc), "Entering in R-ACT-REQ state\n"); + uea_info(INS_TO_USBDEV(sc), "modem started, waiting " + "synchronization...\n"); +out: + release_firmware(cmvs_fw); + return ret; +} + +/* Start boot post firmware modem: + * - send reset commands through usb control pipe + * - start workqueue for DSP loading + * - send CMV options to modem + */ + +static int uea_start_reset(struct uea_softc *sc) +{ + u16 zero = 0; /* ;-) */ + int ret; + + uea_enters(INS_TO_USBDEV(sc)); + uea_info(INS_TO_USBDEV(sc), "(re)booting started\n"); + + /* mask interrupt */ + sc->booting = 1; + /* We need to set this here because, a ack timeout could have occurred, + * but before we start the reboot, the ack occurs and set this to 1. + * So we will failed to wait Ready CMV. + */ + sc->cmv_ack = 0; + UPDATE_ATM_SIGNAL(ATM_PHY_SIG_LOST); + + /* reset statistics */ + memset(&sc->stats, 0, sizeof(struct uea_stats)); + + /* tell the modem that we want to boot in IDMA mode */ + uea_request(sc, UEA_SET_MODE, UEA_LOOPBACK_ON, 0, NULL); + uea_request(sc, UEA_SET_MODE, UEA_BOOT_IDMA, 0, NULL); + + /* enter reset mode */ + uea_request(sc, UEA_SET_MODE, UEA_START_RESET, 0, NULL); + + /* original driver use 200ms, but windows driver use 100ms */ + ret = uea_wait(sc, 0, msecs_to_jiffies(100)); + if (ret < 0) + return ret; + + /* leave reset mode */ + uea_request(sc, UEA_SET_MODE, UEA_END_RESET, 0, NULL); + + if (UEA_CHIP_VERSION(sc) != EAGLE_IV) { + /* clear tx and rx mailboxes */ + uea_request(sc, UEA_SET_2183_DATA, UEA_MPTX_MAILBOX, 2, &zero); + uea_request(sc, UEA_SET_2183_DATA, UEA_MPRX_MAILBOX, 2, &zero); + uea_request(sc, UEA_SET_2183_DATA, UEA_SWAP_MAILBOX, 2, &zero); + } + + ret = uea_wait(sc, 0, msecs_to_jiffies(1000)); + if (ret < 0) + return ret; + + if (UEA_CHIP_VERSION(sc) == EAGLE_IV) + sc->cmv_dsc.e4.function = E4_MAKEFUNCTION(E4_ADSLDIRECTIVE, + E4_MODEMREADY, 1); + else + sc->cmv_dsc.e1.function = E1_MAKEFUNCTION(E1_ADSLDIRECTIVE, + E1_MODEMREADY); + + /* demask interrupt */ + sc->booting = 0; + + /* start loading DSP */ + sc->pageno = 0; + sc->ovl = 0; + schedule_work(&sc->task); + + /* wait for modem ready CMV */ + ret = wait_cmv_ack(sc); + if (ret < 0) + return ret; + + uea_vdbg(INS_TO_USBDEV(sc), "Ready CMV received\n"); + + ret = sc->send_cmvs(sc); + if (ret < 0) + return ret; + + sc->reset = 0; + uea_leaves(INS_TO_USBDEV(sc)); + return ret; +} + +/* + * In case of an error wait 1s before rebooting the modem + * if the modem don't request reboot (-EAGAIN). + * Monitor the modem every 1s. + */ + +static int uea_kthread(void *data) +{ + struct uea_softc *sc = data; + int ret = -EAGAIN; + + set_freezable(); + uea_enters(INS_TO_USBDEV(sc)); + while (!kthread_should_stop()) { + if (ret < 0 || sc->reset) + ret = uea_start_reset(sc); + if (!ret) + ret = sc->stat(sc); + if (ret != -EAGAIN) + uea_wait(sc, 0, msecs_to_jiffies(1000)); + try_to_freeze(); + } + uea_leaves(INS_TO_USBDEV(sc)); + return ret; +} + +/* Load second usb firmware for ADI930 chip */ +static int load_XILINX_firmware(struct uea_softc *sc) +{ + const struct firmware *fw_entry; + int ret, size, u, ln; + const u8 *pfw; + u8 value; + char *fw_name = FPGA930_FIRMWARE; + + uea_enters(INS_TO_USBDEV(sc)); + + ret = reject_firmware(&fw_entry, fw_name, &sc->usb_dev->dev); + if (ret) { + uea_err(INS_TO_USBDEV(sc), "firmware %s is not available\n", + fw_name); + goto err0; + } + + pfw = fw_entry->data; + size = fw_entry->size; + if (size != 0x577B) { + uea_err(INS_TO_USBDEV(sc), "firmware %s is corrupted\n", + fw_name); + ret = -EILSEQ; + goto err1; + } + for (u = 0; u < size; u += ln) { + ln = min(size - u, 64); + ret = uea_request(sc, 0xe, 0, ln, pfw + u); + if (ret < 0) { + uea_err(INS_TO_USBDEV(sc), + "elsa download data failed (%d)\n", ret); + goto err1; + } + } + + /* finish to send the fpga */ + ret = uea_request(sc, 0xe, 1, 0, NULL); + if (ret < 0) { + uea_err(INS_TO_USBDEV(sc), + "elsa download data failed (%d)\n", ret); + goto err1; + } + + /* Tell the modem we finish : de-assert reset */ + value = 0; + ret = uea_send_modem_cmd(sc->usb_dev, 0xe, 1, &value); + if (ret < 0) + uea_err(sc->usb_dev, "elsa de-assert failed with error" + " %d\n", ret); + +err1: + release_firmware(fw_entry); +err0: + uea_leaves(INS_TO_USBDEV(sc)); + return ret; +} + +/* The modem send us an ack. First with check if it right */ +static void uea_dispatch_cmv_e1(struct uea_softc *sc, struct intr_pkt *intr) +{ + struct cmv_dsc_e1 *dsc = &sc->cmv_dsc.e1; + struct cmv_e1 *cmv = &intr->u.e1.s2.cmv; + + uea_enters(INS_TO_USBDEV(sc)); + if (le16_to_cpu(cmv->wPreamble) != E1_PREAMBLE) + goto bad1; + + if (cmv->bDirection != E1_MODEMTOHOST) + goto bad1; + + /* FIXME : ADI930 reply wrong preambule (func = 2, sub = 2) to + * the first MEMACCESS cmv. Ignore it... + */ + if (cmv->bFunction != dsc->function) { + if (UEA_CHIP_VERSION(sc) == ADI930 + && cmv->bFunction == E1_MAKEFUNCTION(2, 2)) { + cmv->wIndex = cpu_to_le16(dsc->idx); + put_unaligned_le32(dsc->address, + &cmv->dwSymbolicAddress); + cmv->wOffsetAddress = cpu_to_le16(dsc->offset); + } else + goto bad2; + } + + if (cmv->bFunction == E1_MAKEFUNCTION(E1_ADSLDIRECTIVE, + E1_MODEMREADY)) { + wake_up_cmv_ack(sc); + uea_leaves(INS_TO_USBDEV(sc)); + return; + } + + /* in case of MEMACCESS */ + if (le16_to_cpu(cmv->wIndex) != dsc->idx || + get_unaligned_le32(&cmv->dwSymbolicAddress) != dsc->address || + le16_to_cpu(cmv->wOffsetAddress) != dsc->offset) + goto bad2; + + sc->data = get_unaligned_le32(&cmv->dwData); + sc->data = sc->data << 16 | sc->data >> 16; + + wake_up_cmv_ack(sc); + uea_leaves(INS_TO_USBDEV(sc)); + return; + +bad2: + uea_err(INS_TO_USBDEV(sc), "unexpected cmv received, " + "Function : %d, Subfunction : %d\n", + E1_FUNCTION_TYPE(cmv->bFunction), + E1_FUNCTION_SUBTYPE(cmv->bFunction)); + uea_leaves(INS_TO_USBDEV(sc)); + return; + +bad1: + uea_err(INS_TO_USBDEV(sc), "invalid cmv received, " + "wPreamble %d, bDirection %d\n", + le16_to_cpu(cmv->wPreamble), cmv->bDirection); + uea_leaves(INS_TO_USBDEV(sc)); +} + +/* The modem send us an ack. First with check if it right */ +static void uea_dispatch_cmv_e4(struct uea_softc *sc, struct intr_pkt *intr) +{ + struct cmv_dsc_e4 *dsc = &sc->cmv_dsc.e4; + struct cmv_e4 *cmv = &intr->u.e4.s2.cmv; + + uea_enters(INS_TO_USBDEV(sc)); + uea_dbg(INS_TO_USBDEV(sc), "cmv %x %x %x %x %x %x\n", + be16_to_cpu(cmv->wGroup), be16_to_cpu(cmv->wFunction), + be16_to_cpu(cmv->wOffset), be16_to_cpu(cmv->wAddress), + be32_to_cpu(cmv->dwData[0]), be32_to_cpu(cmv->dwData[1])); + + if (be16_to_cpu(cmv->wFunction) != dsc->function) + goto bad2; + + if (be16_to_cpu(cmv->wFunction) == E4_MAKEFUNCTION(E4_ADSLDIRECTIVE, + E4_MODEMREADY, 1)) { + wake_up_cmv_ack(sc); + uea_leaves(INS_TO_USBDEV(sc)); + return; + } + + /* in case of MEMACCESS */ + if (be16_to_cpu(cmv->wOffset) != dsc->offset || + be16_to_cpu(cmv->wGroup) != dsc->group || + be16_to_cpu(cmv->wAddress) != dsc->address) + goto bad2; + + sc->data = be32_to_cpu(cmv->dwData[0]); + sc->data1 = be32_to_cpu(cmv->dwData[1]); + wake_up_cmv_ack(sc); + uea_leaves(INS_TO_USBDEV(sc)); + return; + +bad2: + uea_err(INS_TO_USBDEV(sc), "unexpected cmv received, " + "Function : %d, Subfunction : %d\n", + E4_FUNCTION_TYPE(cmv->wFunction), + E4_FUNCTION_SUBTYPE(cmv->wFunction)); + uea_leaves(INS_TO_USBDEV(sc)); + return; +} + +static void uea_schedule_load_page_e1(struct uea_softc *sc, + struct intr_pkt *intr) +{ + sc->pageno = intr->e1_bSwapPageNo; + sc->ovl = intr->e1_bOvl >> 4 | intr->e1_bOvl << 4; + schedule_work(&sc->task); +} + +static void uea_schedule_load_page_e4(struct uea_softc *sc, + struct intr_pkt *intr) +{ + sc->pageno = intr->e4_bSwapPageNo; + schedule_work(&sc->task); +} + +/* + * interrupt handler + */ +static void uea_intr(struct urb *urb) +{ + struct uea_softc *sc = urb->context; + struct intr_pkt *intr = urb->transfer_buffer; + int status = urb->status; + + uea_enters(INS_TO_USBDEV(sc)); + + if (unlikely(status < 0)) { + uea_err(INS_TO_USBDEV(sc), "uea_intr() failed with %d\n", + status); + return; + } + + /* device-to-host interrupt */ + if (intr->bType != 0x08 || sc->booting) { + uea_err(INS_TO_USBDEV(sc), "wrong interrupt\n"); + goto resubmit; + } + + switch (le16_to_cpu(intr->wInterrupt)) { + case INT_LOADSWAPPAGE: + sc->schedule_load_page(sc, intr); + break; + + case INT_INCOMINGCMV: + sc->dispatch_cmv(sc, intr); + break; + + default: + uea_err(INS_TO_USBDEV(sc), "unknown interrupt %u\n", + le16_to_cpu(intr->wInterrupt)); + } + +resubmit: + usb_submit_urb(sc->urb_int, GFP_ATOMIC); +} + +/* + * Start the modem : init the data and start kernel thread + */ +static int uea_boot(struct uea_softc *sc) +{ + int ret, size; + struct intr_pkt *intr; + + uea_enters(INS_TO_USBDEV(sc)); + + if (UEA_CHIP_VERSION(sc) == EAGLE_IV) { + size = E4_INTR_PKT_SIZE; + sc->dispatch_cmv = uea_dispatch_cmv_e4; + sc->schedule_load_page = uea_schedule_load_page_e4; + sc->stat = uea_stat_e4; + sc->send_cmvs = uea_send_cmvs_e4; + INIT_WORK(&sc->task, uea_load_page_e4); + } else { + size = E1_INTR_PKT_SIZE; + sc->dispatch_cmv = uea_dispatch_cmv_e1; + sc->schedule_load_page = uea_schedule_load_page_e1; + sc->stat = uea_stat_e1; + sc->send_cmvs = uea_send_cmvs_e1; + INIT_WORK(&sc->task, uea_load_page_e1); + } + + init_waitqueue_head(&sc->sync_q); + + if (UEA_CHIP_VERSION(sc) == ADI930) + load_XILINX_firmware(sc); + + intr = kmalloc(size, GFP_KERNEL); + if (!intr) { + uea_err(INS_TO_USBDEV(sc), + "cannot allocate interrupt package\n"); + goto err0; + } + + sc->urb_int = usb_alloc_urb(0, GFP_KERNEL); + if (!sc->urb_int) { + uea_err(INS_TO_USBDEV(sc), "cannot allocate interrupt URB\n"); + goto err1; + } + + usb_fill_int_urb(sc->urb_int, sc->usb_dev, + usb_rcvintpipe(sc->usb_dev, UEA_INTR_PIPE), + intr, size, uea_intr, sc, + sc->usb_dev->actconfig->interface[0]->altsetting[0]. + endpoint[0].desc.bInterval); + + ret = usb_submit_urb(sc->urb_int, GFP_KERNEL); + if (ret < 0) { + uea_err(INS_TO_USBDEV(sc), + "urb submition failed with error %d\n", ret); + goto err1; + } + + /* Create worker thread, but don't start it here. Start it after + * all usbatm generic initialization is done. + */ + sc->kthread = kthread_create(uea_kthread, sc, "ueagle-atm"); + if (IS_ERR(sc->kthread)) { + uea_err(INS_TO_USBDEV(sc), "failed to create thread\n"); + goto err2; + } + + uea_leaves(INS_TO_USBDEV(sc)); + return 0; + +err2: + usb_kill_urb(sc->urb_int); +err1: + usb_free_urb(sc->urb_int); + sc->urb_int = NULL; + kfree(intr); +err0: + uea_leaves(INS_TO_USBDEV(sc)); + return -ENOMEM; +} + +/* + * Stop the modem : kill kernel thread and free data + */ +static void uea_stop(struct uea_softc *sc) +{ + int ret; + uea_enters(INS_TO_USBDEV(sc)); + ret = kthread_stop(sc->kthread); + uea_dbg(INS_TO_USBDEV(sc), "kthread finish with status %d\n", ret); + + uea_request(sc, UEA_SET_MODE, UEA_LOOPBACK_ON, 0, NULL); + + usb_kill_urb(sc->urb_int); + kfree(sc->urb_int->transfer_buffer); + usb_free_urb(sc->urb_int); + + /* flush the work item, when no one can schedule it */ + flush_work(&sc->task); + + release_firmware(sc->dsp_firm); + uea_leaves(INS_TO_USBDEV(sc)); +} + +/* syfs interface */ +static struct uea_softc *dev_to_uea(struct device *dev) +{ + struct usb_interface *intf; + struct usbatm_data *usbatm; + + intf = to_usb_interface(dev); + if (!intf) + return NULL; + + usbatm = usb_get_intfdata(intf); + if (!usbatm) + return NULL; + + return usbatm->driver_data; +} + +static ssize_t read_status(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret = -ENODEV; + struct uea_softc *sc; + + mutex_lock(&uea_mutex); + sc = dev_to_uea(dev); + if (!sc) + goto out; + ret = snprintf(buf, 10, "%08x\n", sc->stats.phy.state); +out: + mutex_unlock(&uea_mutex); + return ret; +} + +static ssize_t reboot(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = -ENODEV; + struct uea_softc *sc; + + mutex_lock(&uea_mutex); + sc = dev_to_uea(dev); + if (!sc) + goto out; + sc->reset = 1; + ret = count; +out: + mutex_unlock(&uea_mutex); + return ret; +} + +static DEVICE_ATTR(stat_status, S_IWUSR | S_IRUGO, read_status, reboot); + +static ssize_t read_human_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret = -ENODEV; + int modem_state; + struct uea_softc *sc; + + mutex_lock(&uea_mutex); + sc = dev_to_uea(dev); + if (!sc) + goto out; + + if (UEA_CHIP_VERSION(sc) == EAGLE_IV) { + switch (sc->stats.phy.state) { + case 0x0: /* not yet synchronized */ + case 0x1: + case 0x3: + case 0x4: + modem_state = 0; + break; + case 0x5: /* initialization */ + case 0x6: + case 0x9: + case 0xa: + modem_state = 1; + break; + case 0x7: /* operational */ + modem_state = 2; + break; + case 0x2: /* fail ... */ + modem_state = 3; + break; + default: /* unknown */ + modem_state = 4; + break; + } + } else + modem_state = GET_STATUS(sc->stats.phy.state); + + switch (modem_state) { + case 0: + ret = sprintf(buf, "Modem is booting\n"); + break; + case 1: + ret = sprintf(buf, "Modem is initializing\n"); + break; + case 2: + ret = sprintf(buf, "Modem is operational\n"); + break; + case 3: + ret = sprintf(buf, "Modem synchronization failed\n"); + break; + default: + ret = sprintf(buf, "Modem state is unknown\n"); + break; + } +out: + mutex_unlock(&uea_mutex); + return ret; +} + +static DEVICE_ATTR(stat_human_status, S_IRUGO, read_human_status, NULL); + +static ssize_t read_delin(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret = -ENODEV; + struct uea_softc *sc; + char *delin = "GOOD"; + + mutex_lock(&uea_mutex); + sc = dev_to_uea(dev); + if (!sc) + goto out; + + if (UEA_CHIP_VERSION(sc) == EAGLE_IV) { + if (sc->stats.phy.flags & 0x4000) + delin = "RESET"; + else if (sc->stats.phy.flags & 0x0001) + delin = "LOSS"; + } else { + if (sc->stats.phy.flags & 0x0C00) + delin = "ERROR"; + else if (sc->stats.phy.flags & 0x0030) + delin = "LOSS"; + } + + ret = sprintf(buf, "%s\n", delin); +out: + mutex_unlock(&uea_mutex); + return ret; +} + +static DEVICE_ATTR(stat_delin, S_IRUGO, read_delin, NULL); + +#define UEA_ATTR(name, reset) \ + \ +static ssize_t read_##name(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int ret = -ENODEV; \ + struct uea_softc *sc; \ + \ + mutex_lock(&uea_mutex); \ + sc = dev_to_uea(dev); \ + if (!sc) \ + goto out; \ + ret = snprintf(buf, 10, "%08x\n", sc->stats.phy.name); \ + if (reset) \ + sc->stats.phy.name = 0; \ +out: \ + mutex_unlock(&uea_mutex); \ + return ret; \ +} \ + \ +static DEVICE_ATTR(stat_##name, S_IRUGO, read_##name, NULL) + +UEA_ATTR(mflags, 1); +UEA_ATTR(vidcpe, 0); +UEA_ATTR(usrate, 0); +UEA_ATTR(dsrate, 0); +UEA_ATTR(usattenuation, 0); +UEA_ATTR(dsattenuation, 0); +UEA_ATTR(usmargin, 0); +UEA_ATTR(dsmargin, 0); +UEA_ATTR(txflow, 0); +UEA_ATTR(rxflow, 0); +UEA_ATTR(uscorr, 0); +UEA_ATTR(dscorr, 0); +UEA_ATTR(usunc, 0); +UEA_ATTR(dsunc, 0); +UEA_ATTR(firmid, 0); + +/* Retrieve the device End System Identifier (MAC) */ + +static int uea_getesi(struct uea_softc *sc, u_char * esi) +{ + unsigned char mac_str[2 * ETH_ALEN + 1]; + int i; + if (usb_string + (sc->usb_dev, sc->usb_dev->descriptor.iSerialNumber, mac_str, + sizeof(mac_str)) != 2 * ETH_ALEN) + return 1; + + for (i = 0; i < ETH_ALEN; i++) + esi[i] = hex_to_bin(mac_str[2 * i]) * 16 + + hex_to_bin(mac_str[2 * i + 1]); + + return 0; +} + +/* ATM stuff */ +static int uea_atm_open(struct usbatm_data *usbatm, struct atm_dev *atm_dev) +{ + struct uea_softc *sc = usbatm->driver_data; + + return uea_getesi(sc, atm_dev->esi); +} + +static int uea_heavy(struct usbatm_data *usbatm, struct usb_interface *intf) +{ + struct uea_softc *sc = usbatm->driver_data; + + wait_event_interruptible(sc->sync_q, IS_OPERATIONAL(sc)); + + return 0; + +} + +static int claim_interface(struct usb_device *usb_dev, + struct usbatm_data *usbatm, int ifnum) +{ + int ret; + struct usb_interface *intf = usb_ifnum_to_if(usb_dev, ifnum); + + if (!intf) { + uea_err(usb_dev, "interface %d not found\n", ifnum); + return -ENODEV; + } + + ret = usb_driver_claim_interface(&uea_driver, intf, usbatm); + if (ret != 0) + uea_err(usb_dev, "can't claim interface %d, error %d\n", ifnum, + ret); + return ret; +} + +static struct attribute *attrs[] = { + &dev_attr_stat_status.attr, + &dev_attr_stat_mflags.attr, + &dev_attr_stat_human_status.attr, + &dev_attr_stat_delin.attr, + &dev_attr_stat_vidcpe.attr, + &dev_attr_stat_usrate.attr, + &dev_attr_stat_dsrate.attr, + &dev_attr_stat_usattenuation.attr, + &dev_attr_stat_dsattenuation.attr, + &dev_attr_stat_usmargin.attr, + &dev_attr_stat_dsmargin.attr, + &dev_attr_stat_txflow.attr, + &dev_attr_stat_rxflow.attr, + &dev_attr_stat_uscorr.attr, + &dev_attr_stat_dscorr.attr, + &dev_attr_stat_usunc.attr, + &dev_attr_stat_dsunc.attr, + &dev_attr_stat_firmid.attr, + NULL, +}; +static struct attribute_group attr_grp = { + .attrs = attrs, +}; + +static int uea_bind(struct usbatm_data *usbatm, struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *usb = interface_to_usbdev(intf); + struct uea_softc *sc; + int ret, ifnum = intf->altsetting->desc.bInterfaceNumber; + unsigned int alt; + + uea_enters(usb); + + /* interface 0 is for firmware/monitoring */ + if (ifnum != UEA_INTR_IFACE_NO) + return -ENODEV; + + usbatm->flags = (sync_wait[modem_index] ? 0 : UDSL_SKIP_HEAVY_INIT); + + /* interface 1 is for outbound traffic */ + ret = claim_interface(usb, usbatm, UEA_US_IFACE_NO); + if (ret < 0) + return ret; + + /* ADI930 has only 2 interfaces and inbound traffic is on interface 1 */ + if (UEA_CHIP_VERSION(id) != ADI930) { + /* interface 2 is for inbound traffic */ + ret = claim_interface(usb, usbatm, UEA_DS_IFACE_NO); + if (ret < 0) + return ret; + } + + sc = kzalloc(sizeof(struct uea_softc), GFP_KERNEL); + if (!sc) { + uea_err(usb, "uea_init: not enough memory !\n"); + return -ENOMEM; + } + + sc->usb_dev = usb; + usbatm->driver_data = sc; + sc->usbatm = usbatm; + sc->modem_index = (modem_index < NB_MODEM) ? modem_index++ : 0; + sc->driver_info = id->driver_info; + + /* first try to use module parameter */ + if (annex[sc->modem_index] == 1) + sc->annex = ANNEXA; + else if (annex[sc->modem_index] == 2) + sc->annex = ANNEXB; + /* try to autodetect annex */ + else if (sc->driver_info & AUTO_ANNEX_A) + sc->annex = ANNEXA; + else if (sc->driver_info & AUTO_ANNEX_B) + sc->annex = ANNEXB; + else + sc->annex = (le16_to_cpu + (sc->usb_dev->descriptor.bcdDevice) & 0x80) ? ANNEXB : ANNEXA; + + alt = altsetting[sc->modem_index]; + /* ADI930 don't support iso */ + if (UEA_CHIP_VERSION(id) != ADI930 && alt > 0) { + if (alt <= 8 && + usb_set_interface(usb, UEA_DS_IFACE_NO, alt) == 0) { + uea_dbg(usb, "set alternate %u for 2 interface\n", alt); + uea_info(usb, "using iso mode\n"); + usbatm->flags |= UDSL_USE_ISOC | UDSL_IGNORE_EILSEQ; + } else { + uea_err(usb, "setting alternate %u failed for " + "2 interface, using bulk mode\n", alt); + } + } + + ret = sysfs_create_group(&intf->dev.kobj, &attr_grp); + if (ret < 0) + goto error; + + ret = uea_boot(sc); + if (ret < 0) + goto error_rm_grp; + + return 0; + +error_rm_grp: + sysfs_remove_group(&intf->dev.kobj, &attr_grp); +error: + kfree(sc); + return ret; +} + +static void uea_unbind(struct usbatm_data *usbatm, struct usb_interface *intf) +{ + struct uea_softc *sc = usbatm->driver_data; + + sysfs_remove_group(&intf->dev.kobj, &attr_grp); + uea_stop(sc); + kfree(sc); +} + +static struct usbatm_driver uea_usbatm_driver = { + .driver_name = "ueagle-atm", + .bind = uea_bind, + .atm_start = uea_atm_open, + .unbind = uea_unbind, + .heavy_init = uea_heavy, + .bulk_in = UEA_BULK_DATA_PIPE, + .bulk_out = UEA_BULK_DATA_PIPE, + .isoc_in = UEA_ISO_DATA_PIPE, +}; + +static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *usb = interface_to_usbdev(intf); + int ret; + + uea_enters(usb); + uea_info(usb, "ADSL device founded vid (%#X) pid (%#X) Rev (%#X): %s\n", + le16_to_cpu(usb->descriptor.idVendor), + le16_to_cpu(usb->descriptor.idProduct), + le16_to_cpu(usb->descriptor.bcdDevice), + chip_name[UEA_CHIP_VERSION(id)]); + + usb_reset_device(usb); + + if (UEA_IS_PREFIRM(id)) + return uea_load_firmware(usb, UEA_CHIP_VERSION(id)); + + ret = usbatm_usb_probe(intf, id, &uea_usbatm_driver); + if (ret == 0) { + struct usbatm_data *usbatm = usb_get_intfdata(intf); + struct uea_softc *sc = usbatm->driver_data; + + /* Ensure carrier is initialized to off as early as possible */ + UPDATE_ATM_SIGNAL(ATM_PHY_SIG_LOST); + + /* Only start the worker thread when all init is done */ + wake_up_process(sc->kthread); + } + + return ret; +} + +static void uea_disconnect(struct usb_interface *intf) +{ + struct usb_device *usb = interface_to_usbdev(intf); + int ifnum = intf->altsetting->desc.bInterfaceNumber; + uea_enters(usb); + + /* ADI930 has 2 interfaces and eagle 3 interfaces. + * Pre-firmware device has one interface + */ + if (usb->config->desc.bNumInterfaces != 1 && ifnum == 0) { + mutex_lock(&uea_mutex); + usbatm_usb_disconnect(intf); + mutex_unlock(&uea_mutex); + uea_info(usb, "ADSL device removed\n"); + } + + uea_leaves(usb); +} + +/* + * List of supported VID/PID + */ +static const struct usb_device_id uea_ids[] = { + {USB_DEVICE(ANALOG_VID, ADI930_PID_PREFIRM), + .driver_info = ADI930 | PREFIRM}, + {USB_DEVICE(ANALOG_VID, ADI930_PID_PSTFIRM), + .driver_info = ADI930 | PSTFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_I_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_I_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_II_PID_PREFIRM), + .driver_info = EAGLE_II | PREFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_II_PID_PSTFIRM), + .driver_info = EAGLE_II | PSTFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_IIC_PID_PREFIRM), + .driver_info = EAGLE_II | PREFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_IIC_PID_PSTFIRM), + .driver_info = EAGLE_II | PSTFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_III_PID_PREFIRM), + .driver_info = EAGLE_III | PREFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_III_PID_PSTFIRM), + .driver_info = EAGLE_III | PSTFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_IV_PID_PREFIRM), + .driver_info = EAGLE_IV | PREFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_IV_PID_PSTFIRM), + .driver_info = EAGLE_IV | PSTFIRM}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_I_A_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_I_A_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_A}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_I_B_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_I_B_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_B}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_II_A_PID_PREFIRM), + .driver_info = EAGLE_II | PREFIRM}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_II_A_PID_PSTFIRM), + .driver_info = EAGLE_II | PSTFIRM | AUTO_ANNEX_A}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_II_B_PID_PREFIRM), + .driver_info = EAGLE_II | PREFIRM}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_II_B_PID_PSTFIRM), + .driver_info = EAGLE_II | PSTFIRM | AUTO_ANNEX_B}, + {USB_DEVICE(ELSA_VID, ELSA_PID_PREFIRM), + .driver_info = ADI930 | PREFIRM}, + {USB_DEVICE(ELSA_VID, ELSA_PID_PSTFIRM), + .driver_info = ADI930 | PSTFIRM}, + {USB_DEVICE(ELSA_VID, ELSA_PID_A_PREFIRM), + .driver_info = ADI930 | PREFIRM}, + {USB_DEVICE(ELSA_VID, ELSA_PID_A_PSTFIRM), + .driver_info = ADI930 | PSTFIRM | AUTO_ANNEX_A}, + {USB_DEVICE(ELSA_VID, ELSA_PID_B_PREFIRM), + .driver_info = ADI930 | PREFIRM}, + {USB_DEVICE(ELSA_VID, ELSA_PID_B_PSTFIRM), + .driver_info = ADI930 | PSTFIRM | AUTO_ANNEX_B}, + {USB_DEVICE(USR_VID, MILLER_A_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(USR_VID, MILLER_A_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_A}, + {USB_DEVICE(USR_VID, MILLER_B_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(USR_VID, MILLER_B_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_B}, + {USB_DEVICE(USR_VID, HEINEKEN_A_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(USR_VID, HEINEKEN_A_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_A}, + {USB_DEVICE(USR_VID, HEINEKEN_B_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(USR_VID, HEINEKEN_B_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_B}, + {} +}; + +/* + * USB driver descriptor + */ +static struct usb_driver uea_driver = { + .name = "ueagle-atm", + .id_table = uea_ids, + .probe = uea_probe, + .disconnect = uea_disconnect, +}; + +MODULE_DEVICE_TABLE(usb, uea_ids); + +module_usb_driver(uea_driver); + +MODULE_AUTHOR("Damien Bergamini/Matthieu Castet/Stanislaw W. Gruszka"); +MODULE_DESCRIPTION("ADI 930/Eagle USB ADSL Modem driver"); +MODULE_LICENSE("Dual BSD/GPL"); +/*(DEBLOBBED)*/ diff --git a/drivers/usb/atm/usbatm.c b/drivers/usb/atm/usbatm.c new file mode 100644 index 000000000..dada0146c --- /dev/null +++ b/drivers/usb/atm/usbatm.c @@ -0,0 +1,1344 @@ +/****************************************************************************** + * usbatm.c - Generic USB xDSL driver core + * + * Copyright (C) 2001, Alcatel + * Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas + * Copyright (C) 2004, David Woodhouse, Roman Kagan + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +/* + * Written by Johan Verrept, Duncan Sands (duncan.sands@free.fr) and David Woodhouse + * + * 1.7+: - See the check-in logs + * + * 1.6: - No longer opens a connection if the firmware is not loaded + * - Added support for the speedtouch 330 + * - Removed the limit on the number of devices + * - Module now autoloads on device plugin + * - Merged relevant parts of sarlib + * - Replaced the kernel thread with a tasklet + * - New packet transmission code + * - Changed proc file contents + * - Fixed all known SMP races + * - Many fixes and cleanups + * - Various fixes by Oliver Neukum (oliver@neukum.name) + * + * 1.5A: - Version for inclusion in 2.5 series kernel + * - Modifications by Richard Purdie (rpurdie@rpsys.net) + * - made compatible with kernel 2.5.6 onwards by changing + * usbatm_usb_send_data_context->urb to a pointer and adding code + * to alloc and free it + * - remove_wait_queue() added to usbatm_atm_processqueue_thread() + * + * 1.5: - fixed memory leak when atmsar_decode_aal5 returned NULL. + * (reported by stephen.robinson@zen.co.uk) + * + * 1.4: - changed the spin_lock() under interrupt to spin_lock_irqsave() + * - unlink all active send urbs of a vcc that is being closed. + * + * 1.3.1: - added the version number + * + * 1.3: - Added multiple send urb support + * - fixed memory leak and vcc->tx_inuse starvation bug + * when not enough memory left in vcc. + * + * 1.2: - Fixed race condition in usbatm_usb_send_data() + * 1.1: - Turned off packet debugging + * + */ + +#include "usbatm.h" + +#include <asm/uaccess.h> +#include <linux/crc32.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/netdevice.h> +#include <linux/proc_fs.h> +#include <linux/sched.h> +#include <linux/signal.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/kthread.h> +#include <linux/ratelimit.h> + +#ifdef VERBOSE_DEBUG +static int usbatm_print_packet(struct usbatm_data *instance, const unsigned char *data, int len); +#define PACKETDEBUG(arg...) usbatm_print_packet(arg) +#define vdbg(arg...) dev_dbg(arg) +#else +#define PACKETDEBUG(arg...) +#define vdbg(arg...) +#endif + +#define DRIVER_AUTHOR "Johan Verrept, Duncan Sands <duncan.sands@free.fr>" +#define DRIVER_VERSION "1.10" +#define DRIVER_DESC "Generic USB ATM/DSL I/O, version " DRIVER_VERSION + +static const char usbatm_driver_name[] = "usbatm"; + +#define UDSL_MAX_RCV_URBS 16 +#define UDSL_MAX_SND_URBS 16 +#define UDSL_MAX_BUF_SIZE 65536 +#define UDSL_DEFAULT_RCV_URBS 4 +#define UDSL_DEFAULT_SND_URBS 4 +#define UDSL_DEFAULT_RCV_BUF_SIZE 3392 /* 64 * ATM_CELL_SIZE */ +#define UDSL_DEFAULT_SND_BUF_SIZE 3392 /* 64 * ATM_CELL_SIZE */ + +#define ATM_CELL_HEADER (ATM_CELL_SIZE - ATM_CELL_PAYLOAD) + +#define THROTTLE_MSECS 100 /* delay to recover processing after urb submission fails */ + +static unsigned int num_rcv_urbs = UDSL_DEFAULT_RCV_URBS; +static unsigned int num_snd_urbs = UDSL_DEFAULT_SND_URBS; +static unsigned int rcv_buf_bytes = UDSL_DEFAULT_RCV_BUF_SIZE; +static unsigned int snd_buf_bytes = UDSL_DEFAULT_SND_BUF_SIZE; + +module_param(num_rcv_urbs, uint, S_IRUGO); +MODULE_PARM_DESC(num_rcv_urbs, + "Number of urbs used for reception (range: 0-" + __MODULE_STRING(UDSL_MAX_RCV_URBS) ", default: " + __MODULE_STRING(UDSL_DEFAULT_RCV_URBS) ")"); + +module_param(num_snd_urbs, uint, S_IRUGO); +MODULE_PARM_DESC(num_snd_urbs, + "Number of urbs used for transmission (range: 0-" + __MODULE_STRING(UDSL_MAX_SND_URBS) ", default: " + __MODULE_STRING(UDSL_DEFAULT_SND_URBS) ")"); + +module_param(rcv_buf_bytes, uint, S_IRUGO); +MODULE_PARM_DESC(rcv_buf_bytes, + "Size of the buffers used for reception, in bytes (range: 1-" + __MODULE_STRING(UDSL_MAX_BUF_SIZE) ", default: " + __MODULE_STRING(UDSL_DEFAULT_RCV_BUF_SIZE) ")"); + +module_param(snd_buf_bytes, uint, S_IRUGO); +MODULE_PARM_DESC(snd_buf_bytes, + "Size of the buffers used for transmission, in bytes (range: 1-" + __MODULE_STRING(UDSL_MAX_BUF_SIZE) ", default: " + __MODULE_STRING(UDSL_DEFAULT_SND_BUF_SIZE) ")"); + + +/* receive */ + +struct usbatm_vcc_data { + /* vpi/vci lookup */ + struct list_head list; + short vpi; + int vci; + struct atm_vcc *vcc; + + /* raw cell reassembly */ + struct sk_buff *sarb; +}; + + +/* send */ + +struct usbatm_control { + struct atm_skb_data atm; + u32 len; + u32 crc; +}; + +#define UDSL_SKB(x) ((struct usbatm_control *)(x)->cb) + + +/* ATM */ + +static void usbatm_atm_dev_close(struct atm_dev *atm_dev); +static int usbatm_atm_open(struct atm_vcc *vcc); +static void usbatm_atm_close(struct atm_vcc *vcc); +static int usbatm_atm_ioctl(struct atm_dev *atm_dev, unsigned int cmd, void __user *arg); +static int usbatm_atm_send(struct atm_vcc *vcc, struct sk_buff *skb); +static int usbatm_atm_proc_read(struct atm_dev *atm_dev, loff_t *pos, char *page); + +static struct atmdev_ops usbatm_atm_devops = { + .dev_close = usbatm_atm_dev_close, + .open = usbatm_atm_open, + .close = usbatm_atm_close, + .ioctl = usbatm_atm_ioctl, + .send = usbatm_atm_send, + .proc_read = usbatm_atm_proc_read, + .owner = THIS_MODULE, +}; + + +/*********** +** misc ** +***********/ + +static inline unsigned int usbatm_pdu_length(unsigned int length) +{ + length += ATM_CELL_PAYLOAD - 1 + ATM_AAL5_TRAILER; + return length - length % ATM_CELL_PAYLOAD; +} + +static inline void usbatm_pop(struct atm_vcc *vcc, struct sk_buff *skb) +{ + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb_any(skb); +} + + +/*********** +** urbs ** +************/ + +static struct urb *usbatm_pop_urb(struct usbatm_channel *channel) +{ + struct urb *urb; + + spin_lock_irq(&channel->lock); + if (list_empty(&channel->list)) { + spin_unlock_irq(&channel->lock); + return NULL; + } + + urb = list_entry(channel->list.next, struct urb, urb_list); + list_del(&urb->urb_list); + spin_unlock_irq(&channel->lock); + + return urb; +} + +static int usbatm_submit_urb(struct urb *urb) +{ + struct usbatm_channel *channel = urb->context; + int ret; + + /* vdbg("%s: submitting urb 0x%p, size %u", + __func__, urb, urb->transfer_buffer_length); */ + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) { + if (printk_ratelimit()) + atm_warn(channel->usbatm, "%s: urb 0x%p submission failed (%d)!\n", + __func__, urb, ret); + + /* consider all errors transient and return the buffer back to the queue */ + urb->status = -EAGAIN; + spin_lock_irq(&channel->lock); + + /* must add to the front when sending; doesn't matter when receiving */ + list_add(&urb->urb_list, &channel->list); + + spin_unlock_irq(&channel->lock); + + /* make sure the channel doesn't stall */ + mod_timer(&channel->delay, jiffies + msecs_to_jiffies(THROTTLE_MSECS)); + } + + return ret; +} + +static void usbatm_complete(struct urb *urb) +{ + struct usbatm_channel *channel = urb->context; + unsigned long flags; + int status = urb->status; + + /* vdbg("%s: urb 0x%p, status %d, actual_length %d", + __func__, urb, status, urb->actual_length); */ + + /* usually in_interrupt(), but not always */ + spin_lock_irqsave(&channel->lock, flags); + + /* must add to the back when receiving; doesn't matter when sending */ + list_add_tail(&urb->urb_list, &channel->list); + + spin_unlock_irqrestore(&channel->lock, flags); + + if (unlikely(status) && + (!(channel->usbatm->flags & UDSL_IGNORE_EILSEQ) || + status != -EILSEQ)) { + if (status == -ESHUTDOWN) + return; + + if (printk_ratelimit()) + atm_warn(channel->usbatm, "%s: urb 0x%p failed (%d)!\n", + __func__, urb, status); + /* throttle processing in case of an error */ + mod_timer(&channel->delay, jiffies + msecs_to_jiffies(THROTTLE_MSECS)); + } else + tasklet_schedule(&channel->tasklet); +} + + +/************* +** decode ** +*************/ + +static inline struct usbatm_vcc_data *usbatm_find_vcc(struct usbatm_data *instance, + short vpi, int vci) +{ + struct usbatm_vcc_data *vcc_data; + + list_for_each_entry(vcc_data, &instance->vcc_list, list) + if ((vcc_data->vci == vci) && (vcc_data->vpi == vpi)) + return vcc_data; + return NULL; +} + +static void usbatm_extract_one_cell(struct usbatm_data *instance, unsigned char *source) +{ + struct atm_vcc *vcc; + struct sk_buff *sarb; + short vpi = ((source[0] & 0x0f) << 4) | (source[1] >> 4); + int vci = ((source[1] & 0x0f) << 12) | (source[2] << 4) | (source[3] >> 4); + u8 pti = ((source[3] & 0xe) >> 1); + + if ((vci != instance->cached_vci) || (vpi != instance->cached_vpi)) { + instance->cached_vpi = vpi; + instance->cached_vci = vci; + + instance->cached_vcc = usbatm_find_vcc(instance, vpi, vci); + + if (!instance->cached_vcc) + atm_rldbg(instance, "%s: unknown vpi/vci (%hd/%d)!\n", __func__, vpi, vci); + } + + if (!instance->cached_vcc) + return; + + vcc = instance->cached_vcc->vcc; + + /* OAM F5 end-to-end */ + if (pti == ATM_PTI_E2EF5) { + if (printk_ratelimit()) + atm_warn(instance, "%s: OAM not supported (vpi %d, vci %d)!\n", + __func__, vpi, vci); + atomic_inc(&vcc->stats->rx_err); + return; + } + + sarb = instance->cached_vcc->sarb; + + if (sarb->tail + ATM_CELL_PAYLOAD > sarb->end) { + atm_rldbg(instance, "%s: buffer overrun (sarb->len %u, vcc: 0x%p)!\n", + __func__, sarb->len, vcc); + /* discard cells already received */ + skb_trim(sarb, 0); + } + + memcpy(skb_tail_pointer(sarb), source + ATM_CELL_HEADER, ATM_CELL_PAYLOAD); + __skb_put(sarb, ATM_CELL_PAYLOAD); + + if (pti & 1) { + struct sk_buff *skb; + unsigned int length; + unsigned int pdu_length; + + length = (source[ATM_CELL_SIZE - 6] << 8) + source[ATM_CELL_SIZE - 5]; + + /* guard against overflow */ + if (length > ATM_MAX_AAL5_PDU) { + atm_rldbg(instance, "%s: bogus length %u (vcc: 0x%p)!\n", + __func__, length, vcc); + atomic_inc(&vcc->stats->rx_err); + goto out; + } + + pdu_length = usbatm_pdu_length(length); + + if (sarb->len < pdu_length) { + atm_rldbg(instance, "%s: bogus pdu_length %u (sarb->len: %u, vcc: 0x%p)!\n", + __func__, pdu_length, sarb->len, vcc); + atomic_inc(&vcc->stats->rx_err); + goto out; + } + + if (crc32_be(~0, skb_tail_pointer(sarb) - pdu_length, pdu_length) != 0xc704dd7b) { + atm_rldbg(instance, "%s: packet failed crc check (vcc: 0x%p)!\n", + __func__, vcc); + atomic_inc(&vcc->stats->rx_err); + goto out; + } + + vdbg(&instance->usb_intf->dev, + "%s: got packet (length: %u, pdu_length: %u, vcc: 0x%p)", + __func__, length, pdu_length, vcc); + + if (!(skb = dev_alloc_skb(length))) { + if (printk_ratelimit()) + atm_err(instance, "%s: no memory for skb (length: %u)!\n", + __func__, length); + atomic_inc(&vcc->stats->rx_drop); + goto out; + } + + vdbg(&instance->usb_intf->dev, + "%s: allocated new sk_buff (skb: 0x%p, skb->truesize: %u)", + __func__, skb, skb->truesize); + + if (!atm_charge(vcc, skb->truesize)) { + atm_rldbg(instance, "%s: failed atm_charge (skb->truesize: %u)!\n", + __func__, skb->truesize); + dev_kfree_skb_any(skb); + goto out; /* atm_charge increments rx_drop */ + } + + skb_copy_to_linear_data(skb, + skb_tail_pointer(sarb) - pdu_length, + length); + __skb_put(skb, length); + + vdbg(&instance->usb_intf->dev, + "%s: sending skb 0x%p, skb->len %u, skb->truesize %u", + __func__, skb, skb->len, skb->truesize); + + PACKETDEBUG(instance, skb->data, skb->len); + + vcc->push(vcc, skb); + + atomic_inc(&vcc->stats->rx); + out: + skb_trim(sarb, 0); + } +} + +static void usbatm_extract_cells(struct usbatm_data *instance, + unsigned char *source, unsigned int avail_data) +{ + unsigned int stride = instance->rx_channel.stride; + unsigned int buf_usage = instance->buf_usage; + + /* extract cells from incoming data, taking into account that + * the length of avail data may not be a multiple of stride */ + + if (buf_usage > 0) { + /* we have a partially received atm cell */ + unsigned char *cell_buf = instance->cell_buf; + unsigned int space_left = stride - buf_usage; + + if (avail_data >= space_left) { + /* add new data and process cell */ + memcpy(cell_buf + buf_usage, source, space_left); + source += space_left; + avail_data -= space_left; + usbatm_extract_one_cell(instance, cell_buf); + instance->buf_usage = 0; + } else { + /* not enough data to fill the cell */ + memcpy(cell_buf + buf_usage, source, avail_data); + instance->buf_usage = buf_usage + avail_data; + return; + } + } + + for (; avail_data >= stride; avail_data -= stride, source += stride) + usbatm_extract_one_cell(instance, source); + + if (avail_data > 0) { + /* length was not a multiple of stride - + * save remaining data for next call */ + memcpy(instance->cell_buf, source, avail_data); + instance->buf_usage = avail_data; + } +} + + +/************* +** encode ** +*************/ + +static unsigned int usbatm_write_cells(struct usbatm_data *instance, + struct sk_buff *skb, + u8 *target, unsigned int avail_space) +{ + struct usbatm_control *ctrl = UDSL_SKB(skb); + struct atm_vcc *vcc = ctrl->atm.vcc; + unsigned int bytes_written; + unsigned int stride = instance->tx_channel.stride; + + for (bytes_written = 0; bytes_written < avail_space && ctrl->len; + bytes_written += stride, target += stride) { + unsigned int data_len = min_t(unsigned int, skb->len, ATM_CELL_PAYLOAD); + unsigned int left = ATM_CELL_PAYLOAD - data_len; + u8 *ptr = target; + + ptr[0] = vcc->vpi >> 4; + ptr[1] = (vcc->vpi << 4) | (vcc->vci >> 12); + ptr[2] = vcc->vci >> 4; + ptr[3] = vcc->vci << 4; + ptr[4] = 0xec; + ptr += ATM_CELL_HEADER; + + skb_copy_from_linear_data(skb, ptr, data_len); + ptr += data_len; + __skb_pull(skb, data_len); + + if (!left) + continue; + + memset(ptr, 0, left); + + if (left >= ATM_AAL5_TRAILER) { /* trailer will go in this cell */ + u8 *trailer = target + ATM_CELL_SIZE - ATM_AAL5_TRAILER; + /* trailer[0] = 0; UU = 0 */ + /* trailer[1] = 0; CPI = 0 */ + trailer[2] = ctrl->len >> 8; + trailer[3] = ctrl->len; + + ctrl->crc = ~crc32_be(ctrl->crc, ptr, left - 4); + + trailer[4] = ctrl->crc >> 24; + trailer[5] = ctrl->crc >> 16; + trailer[6] = ctrl->crc >> 8; + trailer[7] = ctrl->crc; + + target[3] |= 0x2; /* adjust PTI */ + + ctrl->len = 0; /* tag this skb finished */ + } else + ctrl->crc = crc32_be(ctrl->crc, ptr, left); + } + + return bytes_written; +} + + +/************** +** receive ** +**************/ + +static void usbatm_rx_process(unsigned long data) +{ + struct usbatm_data *instance = (struct usbatm_data *)data; + struct urb *urb; + + while ((urb = usbatm_pop_urb(&instance->rx_channel))) { + vdbg(&instance->usb_intf->dev, + "%s: processing urb 0x%p", __func__, urb); + + if (usb_pipeisoc(urb->pipe)) { + unsigned char *merge_start = NULL; + unsigned int merge_length = 0; + const unsigned int packet_size = instance->rx_channel.packet_size; + int i; + + for (i = 0; i < urb->number_of_packets; i++) { + if (!urb->iso_frame_desc[i].status) { + unsigned int actual_length = urb->iso_frame_desc[i].actual_length; + + if (!merge_length) + merge_start = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset; + merge_length += actual_length; + if (merge_length && (actual_length < packet_size)) { + usbatm_extract_cells(instance, merge_start, merge_length); + merge_length = 0; + } + } else { + atm_rldbg(instance, "%s: status %d in frame %d!\n", __func__, urb->status, i); + if (merge_length) + usbatm_extract_cells(instance, merge_start, merge_length); + merge_length = 0; + instance->buf_usage = 0; + } + } + + if (merge_length) + usbatm_extract_cells(instance, merge_start, merge_length); + } else + if (!urb->status) + usbatm_extract_cells(instance, urb->transfer_buffer, urb->actual_length); + else + instance->buf_usage = 0; + + if (usbatm_submit_urb(urb)) + return; + } +} + + +/*********** +** send ** +***********/ + +static void usbatm_tx_process(unsigned long data) +{ + struct usbatm_data *instance = (struct usbatm_data *)data; + struct sk_buff *skb = instance->current_skb; + struct urb *urb = NULL; + const unsigned int buf_size = instance->tx_channel.buf_size; + unsigned int bytes_written = 0; + u8 *buffer = NULL; + + if (!skb) + skb = skb_dequeue(&instance->sndqueue); + + while (skb) { + if (!urb) { + urb = usbatm_pop_urb(&instance->tx_channel); + if (!urb) + break; /* no more senders */ + buffer = urb->transfer_buffer; + bytes_written = (urb->status == -EAGAIN) ? + urb->transfer_buffer_length : 0; + } + + bytes_written += usbatm_write_cells(instance, skb, + buffer + bytes_written, + buf_size - bytes_written); + + vdbg(&instance->usb_intf->dev, + "%s: wrote %u bytes from skb 0x%p to urb 0x%p", + __func__, bytes_written, skb, urb); + + if (!UDSL_SKB(skb)->len) { + struct atm_vcc *vcc = UDSL_SKB(skb)->atm.vcc; + + usbatm_pop(vcc, skb); + atomic_inc(&vcc->stats->tx); + + skb = skb_dequeue(&instance->sndqueue); + } + + if (bytes_written == buf_size || (!skb && bytes_written)) { + urb->transfer_buffer_length = bytes_written; + + if (usbatm_submit_urb(urb)) + break; + urb = NULL; + } + } + + instance->current_skb = skb; +} + +static void usbatm_cancel_send(struct usbatm_data *instance, + struct atm_vcc *vcc) +{ + struct sk_buff *skb, *n; + + spin_lock_irq(&instance->sndqueue.lock); + skb_queue_walk_safe(&instance->sndqueue, skb, n) { + if (UDSL_SKB(skb)->atm.vcc == vcc) { + atm_dbg(instance, "%s: popping skb 0x%p\n", __func__, skb); + __skb_unlink(skb, &instance->sndqueue); + usbatm_pop(vcc, skb); + } + } + spin_unlock_irq(&instance->sndqueue.lock); + + tasklet_disable(&instance->tx_channel.tasklet); + if ((skb = instance->current_skb) && (UDSL_SKB(skb)->atm.vcc == vcc)) { + atm_dbg(instance, "%s: popping current skb (0x%p)\n", __func__, skb); + instance->current_skb = NULL; + usbatm_pop(vcc, skb); + } + tasklet_enable(&instance->tx_channel.tasklet); +} + +static int usbatm_atm_send(struct atm_vcc *vcc, struct sk_buff *skb) +{ + struct usbatm_data *instance = vcc->dev->dev_data; + struct usbatm_control *ctrl = UDSL_SKB(skb); + int err; + + /* racy disconnection check - fine */ + if (!instance || instance->disconnected) { +#ifdef VERBOSE_DEBUG + printk_ratelimited(KERN_DEBUG "%s: %s!\n", __func__, instance ? "disconnected" : "NULL instance"); +#endif + err = -ENODEV; + goto fail; + } + + if (vcc->qos.aal != ATM_AAL5) { + atm_rldbg(instance, "%s: unsupported ATM type %d!\n", __func__, vcc->qos.aal); + err = -EINVAL; + goto fail; + } + + if (skb->len > ATM_MAX_AAL5_PDU) { + atm_rldbg(instance, "%s: packet too long (%d vs %d)!\n", + __func__, skb->len, ATM_MAX_AAL5_PDU); + err = -EINVAL; + goto fail; + } + + PACKETDEBUG(instance, skb->data, skb->len); + + /* initialize the control block */ + ctrl->atm.vcc = vcc; + ctrl->len = skb->len; + ctrl->crc = crc32_be(~0, skb->data, skb->len); + + skb_queue_tail(&instance->sndqueue, skb); + tasklet_schedule(&instance->tx_channel.tasklet); + + return 0; + + fail: + usbatm_pop(vcc, skb); + return err; +} + + +/******************** +** bean counting ** +********************/ + +static void usbatm_destroy_instance(struct kref *kref) +{ + struct usbatm_data *instance = container_of(kref, struct usbatm_data, refcount); + + tasklet_kill(&instance->rx_channel.tasklet); + tasklet_kill(&instance->tx_channel.tasklet); + usb_put_dev(instance->usb_dev); + kfree(instance); +} + +static void usbatm_get_instance(struct usbatm_data *instance) +{ + kref_get(&instance->refcount); +} + +static void usbatm_put_instance(struct usbatm_data *instance) +{ + kref_put(&instance->refcount, usbatm_destroy_instance); +} + + +/********** +** ATM ** +**********/ + +static void usbatm_atm_dev_close(struct atm_dev *atm_dev) +{ + struct usbatm_data *instance = atm_dev->dev_data; + + if (!instance) + return; + + atm_dev->dev_data = NULL; /* catch bugs */ + usbatm_put_instance(instance); /* taken in usbatm_atm_init */ +} + +static int usbatm_atm_proc_read(struct atm_dev *atm_dev, loff_t *pos, char *page) +{ + struct usbatm_data *instance = atm_dev->dev_data; + int left = *pos; + + if (!instance) + return -ENODEV; + + if (!left--) + return sprintf(page, "%s\n", instance->description); + + if (!left--) + return sprintf(page, "MAC: %pM\n", atm_dev->esi); + + if (!left--) + return sprintf(page, + "AAL5: tx %d ( %d err ), rx %d ( %d err, %d drop )\n", + atomic_read(&atm_dev->stats.aal5.tx), + atomic_read(&atm_dev->stats.aal5.tx_err), + atomic_read(&atm_dev->stats.aal5.rx), + atomic_read(&atm_dev->stats.aal5.rx_err), + atomic_read(&atm_dev->stats.aal5.rx_drop)); + + if (!left--) { + if (instance->disconnected) + return sprintf(page, "Disconnected\n"); + else + switch (atm_dev->signal) { + case ATM_PHY_SIG_FOUND: + return sprintf(page, "Line up\n"); + case ATM_PHY_SIG_LOST: + return sprintf(page, "Line down\n"); + default: + return sprintf(page, "Line state unknown\n"); + } + } + + return 0; +} + +static int usbatm_atm_open(struct atm_vcc *vcc) +{ + struct usbatm_data *instance = vcc->dev->dev_data; + struct usbatm_vcc_data *new = NULL; + int ret; + int vci = vcc->vci; + short vpi = vcc->vpi; + + if (!instance) + return -ENODEV; + + /* only support AAL5 */ + if ((vcc->qos.aal != ATM_AAL5)) { + atm_warn(instance, "%s: unsupported ATM type %d!\n", __func__, vcc->qos.aal); + return -EINVAL; + } + + /* sanity checks */ + if ((vcc->qos.rxtp.max_sdu < 0) || (vcc->qos.rxtp.max_sdu > ATM_MAX_AAL5_PDU)) { + atm_dbg(instance, "%s: max_sdu %d out of range!\n", __func__, vcc->qos.rxtp.max_sdu); + return -EINVAL; + } + + mutex_lock(&instance->serialize); /* vs self, usbatm_atm_close, usbatm_usb_disconnect */ + + if (instance->disconnected) { + atm_dbg(instance, "%s: disconnected!\n", __func__); + ret = -ENODEV; + goto fail; + } + + if (usbatm_find_vcc(instance, vpi, vci)) { + atm_dbg(instance, "%s: %hd/%d already in use!\n", __func__, vpi, vci); + ret = -EADDRINUSE; + goto fail; + } + + if (!(new = kzalloc(sizeof(struct usbatm_vcc_data), GFP_KERNEL))) { + atm_err(instance, "%s: no memory for vcc_data!\n", __func__); + ret = -ENOMEM; + goto fail; + } + + new->vcc = vcc; + new->vpi = vpi; + new->vci = vci; + + new->sarb = alloc_skb(usbatm_pdu_length(vcc->qos.rxtp.max_sdu), GFP_KERNEL); + if (!new->sarb) { + atm_err(instance, "%s: no memory for SAR buffer!\n", __func__); + ret = -ENOMEM; + goto fail; + } + + vcc->dev_data = new; + + tasklet_disable(&instance->rx_channel.tasklet); + instance->cached_vcc = new; + instance->cached_vpi = vpi; + instance->cached_vci = vci; + list_add(&new->list, &instance->vcc_list); + tasklet_enable(&instance->rx_channel.tasklet); + + set_bit(ATM_VF_ADDR, &vcc->flags); + set_bit(ATM_VF_PARTIAL, &vcc->flags); + set_bit(ATM_VF_READY, &vcc->flags); + + mutex_unlock(&instance->serialize); + + atm_dbg(instance, "%s: allocated vcc data 0x%p\n", __func__, new); + + return 0; + +fail: + kfree(new); + mutex_unlock(&instance->serialize); + return ret; +} + +static void usbatm_atm_close(struct atm_vcc *vcc) +{ + struct usbatm_data *instance = vcc->dev->dev_data; + struct usbatm_vcc_data *vcc_data = vcc->dev_data; + + if (!instance || !vcc_data) + return; + + usbatm_cancel_send(instance, vcc); + + mutex_lock(&instance->serialize); /* vs self, usbatm_atm_open, usbatm_usb_disconnect */ + + tasklet_disable(&instance->rx_channel.tasklet); + if (instance->cached_vcc == vcc_data) { + instance->cached_vcc = NULL; + instance->cached_vpi = ATM_VPI_UNSPEC; + instance->cached_vci = ATM_VCI_UNSPEC; + } + list_del(&vcc_data->list); + tasklet_enable(&instance->rx_channel.tasklet); + + kfree_skb(vcc_data->sarb); + vcc_data->sarb = NULL; + + kfree(vcc_data); + vcc->dev_data = NULL; + + vcc->vpi = ATM_VPI_UNSPEC; + vcc->vci = ATM_VCI_UNSPEC; + clear_bit(ATM_VF_READY, &vcc->flags); + clear_bit(ATM_VF_PARTIAL, &vcc->flags); + clear_bit(ATM_VF_ADDR, &vcc->flags); + + mutex_unlock(&instance->serialize); +} + +static int usbatm_atm_ioctl(struct atm_dev *atm_dev, unsigned int cmd, + void __user *arg) +{ + struct usbatm_data *instance = atm_dev->dev_data; + + if (!instance || instance->disconnected) + return -ENODEV; + + switch (cmd) { + case ATM_QUERYLOOP: + return put_user(ATM_LM_NONE, (int __user *)arg) ? -EFAULT : 0; + default: + return -ENOIOCTLCMD; + } +} + +static int usbatm_atm_init(struct usbatm_data *instance) +{ + struct atm_dev *atm_dev; + int ret, i; + + /* ATM init. The ATM initialization scheme suffers from an intrinsic race + * condition: callbacks we register can be executed at once, before we have + * initialized the struct atm_dev. To protect against this, all callbacks + * abort if atm_dev->dev_data is NULL. */ + atm_dev = atm_dev_register(instance->driver_name, + &instance->usb_intf->dev, &usbatm_atm_devops, + -1, NULL); + if (!atm_dev) { + usb_err(instance, "%s: failed to register ATM device!\n", __func__); + return -1; + } + + instance->atm_dev = atm_dev; + + atm_dev->ci_range.vpi_bits = ATM_CI_MAX; + atm_dev->ci_range.vci_bits = ATM_CI_MAX; + atm_dev->signal = ATM_PHY_SIG_UNKNOWN; + + /* temp init ATM device, set to 128kbit */ + atm_dev->link_rate = 128 * 1000 / 424; + + if (instance->driver->atm_start && ((ret = instance->driver->atm_start(instance, atm_dev)) < 0)) { + atm_err(instance, "%s: atm_start failed: %d!\n", __func__, ret); + goto fail; + } + + usbatm_get_instance(instance); /* dropped in usbatm_atm_dev_close */ + + /* ready for ATM callbacks */ + mb(); + atm_dev->dev_data = instance; + + /* submit all rx URBs */ + for (i = 0; i < num_rcv_urbs; i++) + usbatm_submit_urb(instance->urbs[i]); + + return 0; + + fail: + instance->atm_dev = NULL; + atm_dev_deregister(atm_dev); /* usbatm_atm_dev_close will eventually be called */ + return ret; +} + + +/********** +** USB ** +**********/ + +static int usbatm_do_heavy_init(void *arg) +{ + struct usbatm_data *instance = arg; + int ret; + + allow_signal(SIGTERM); + complete(&instance->thread_started); + + ret = instance->driver->heavy_init(instance, instance->usb_intf); + + if (!ret) + ret = usbatm_atm_init(instance); + + mutex_lock(&instance->serialize); + instance->thread = NULL; + mutex_unlock(&instance->serialize); + + complete_and_exit(&instance->thread_exited, ret); +} + +static int usbatm_heavy_init(struct usbatm_data *instance) +{ + struct task_struct *t; + + t = kthread_create(usbatm_do_heavy_init, instance, "%s", + instance->driver->driver_name); + if (IS_ERR(t)) { + usb_err(instance, "%s: failed to create kernel_thread (%ld)!\n", + __func__, PTR_ERR(t)); + return PTR_ERR(t); + } + + instance->thread = t; + wake_up_process(t); + wait_for_completion(&instance->thread_started); + + return 0; +} + +static void usbatm_tasklet_schedule(unsigned long data) +{ + tasklet_schedule((struct tasklet_struct *) data); +} + +static void usbatm_init_channel(struct usbatm_channel *channel) +{ + spin_lock_init(&channel->lock); + INIT_LIST_HEAD(&channel->list); + channel->delay.function = usbatm_tasklet_schedule; + channel->delay.data = (unsigned long) &channel->tasklet; + init_timer(&channel->delay); +} + +int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id, + struct usbatm_driver *driver) +{ + struct device *dev = &intf->dev; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct usbatm_data *instance; + char *buf; + int error = -ENOMEM; + int i, length; + unsigned int maxpacket, num_packets; + + /* instance init */ + instance = kzalloc(sizeof(*instance) + sizeof(struct urb *) * (num_rcv_urbs + num_snd_urbs), GFP_KERNEL); + if (!instance) { + dev_err(dev, "%s: no memory for instance data!\n", __func__); + return -ENOMEM; + } + + /* public fields */ + + instance->driver = driver; + strlcpy(instance->driver_name, driver->driver_name, + sizeof(instance->driver_name)); + + instance->usb_dev = usb_dev; + instance->usb_intf = intf; + + buf = instance->description; + length = sizeof(instance->description); + + if ((i = usb_string(usb_dev, usb_dev->descriptor.iProduct, buf, length)) < 0) + goto bind; + + buf += i; + length -= i; + + i = scnprintf(buf, length, " ("); + buf += i; + length -= i; + + if (length <= 0 || (i = usb_make_path(usb_dev, buf, length)) < 0) + goto bind; + + buf += i; + length -= i; + + snprintf(buf, length, ")"); + + bind: + if (driver->bind && (error = driver->bind(instance, intf, id)) < 0) { + dev_err(dev, "%s: bind failed: %d!\n", __func__, error); + goto fail_free; + } + + /* private fields */ + + kref_init(&instance->refcount); /* dropped in usbatm_usb_disconnect */ + mutex_init(&instance->serialize); + + instance->thread = NULL; + init_completion(&instance->thread_started); + init_completion(&instance->thread_exited); + + INIT_LIST_HEAD(&instance->vcc_list); + skb_queue_head_init(&instance->sndqueue); + + usbatm_init_channel(&instance->rx_channel); + usbatm_init_channel(&instance->tx_channel); + tasklet_init(&instance->rx_channel.tasklet, usbatm_rx_process, (unsigned long)instance); + tasklet_init(&instance->tx_channel.tasklet, usbatm_tx_process, (unsigned long)instance); + instance->rx_channel.stride = ATM_CELL_SIZE + driver->rx_padding; + instance->tx_channel.stride = ATM_CELL_SIZE + driver->tx_padding; + instance->rx_channel.usbatm = instance->tx_channel.usbatm = instance; + + if ((instance->flags & UDSL_USE_ISOC) && driver->isoc_in) + instance->rx_channel.endpoint = usb_rcvisocpipe(usb_dev, driver->isoc_in); + else + instance->rx_channel.endpoint = usb_rcvbulkpipe(usb_dev, driver->bulk_in); + + instance->tx_channel.endpoint = usb_sndbulkpipe(usb_dev, driver->bulk_out); + + /* tx buffer size must be a positive multiple of the stride */ + instance->tx_channel.buf_size = max(instance->tx_channel.stride, + snd_buf_bytes - (snd_buf_bytes % instance->tx_channel.stride)); + + /* rx buffer size must be a positive multiple of the endpoint maxpacket */ + maxpacket = usb_maxpacket(usb_dev, instance->rx_channel.endpoint, 0); + + if ((maxpacket < 1) || (maxpacket > UDSL_MAX_BUF_SIZE)) { + dev_err(dev, "%s: invalid endpoint %02x!\n", __func__, + usb_pipeendpoint(instance->rx_channel.endpoint)); + error = -EINVAL; + goto fail_unbind; + } + + num_packets = max(1U, (rcv_buf_bytes + maxpacket / 2) / maxpacket); /* round */ + + if (num_packets * maxpacket > UDSL_MAX_BUF_SIZE) + num_packets--; + + instance->rx_channel.buf_size = num_packets * maxpacket; + instance->rx_channel.packet_size = maxpacket; + + for (i = 0; i < 2; i++) { + struct usbatm_channel *channel = i ? + &instance->tx_channel : &instance->rx_channel; + + dev_dbg(dev, "%s: using %d byte buffer for %s channel 0x%p\n", + __func__, channel->buf_size, i ? "tx" : "rx", channel); + } + + /* initialize urbs */ + + for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) { + u8 *buffer; + struct usbatm_channel *channel = i < num_rcv_urbs ? + &instance->rx_channel : &instance->tx_channel; + struct urb *urb; + unsigned int iso_packets = usb_pipeisoc(channel->endpoint) ? channel->buf_size / channel->packet_size : 0; + + urb = usb_alloc_urb(iso_packets, GFP_KERNEL); + if (!urb) { + dev_err(dev, "%s: no memory for urb %d!\n", __func__, i); + error = -ENOMEM; + goto fail_unbind; + } + + instance->urbs[i] = urb; + + /* zero the tx padding to avoid leaking information */ + buffer = kzalloc(channel->buf_size, GFP_KERNEL); + if (!buffer) { + dev_err(dev, "%s: no memory for buffer %d!\n", __func__, i); + error = -ENOMEM; + goto fail_unbind; + } + + usb_fill_bulk_urb(urb, instance->usb_dev, channel->endpoint, + buffer, channel->buf_size, usbatm_complete, channel); + if (iso_packets) { + int j; + urb->interval = 1; + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = iso_packets; + for (j = 0; j < iso_packets; j++) { + urb->iso_frame_desc[j].offset = channel->packet_size * j; + urb->iso_frame_desc[j].length = channel->packet_size; + } + } + + /* put all tx URBs on the list of spares */ + if (i >= num_rcv_urbs) + list_add_tail(&urb->urb_list, &channel->list); + + vdbg(&intf->dev, "%s: alloced buffer 0x%p buf size %u urb 0x%p", + __func__, urb->transfer_buffer, urb->transfer_buffer_length, urb); + } + + instance->cached_vpi = ATM_VPI_UNSPEC; + instance->cached_vci = ATM_VCI_UNSPEC; + instance->cell_buf = kmalloc(instance->rx_channel.stride, GFP_KERNEL); + + if (!instance->cell_buf) { + dev_err(dev, "%s: no memory for cell buffer!\n", __func__); + error = -ENOMEM; + goto fail_unbind; + } + + if (!(instance->flags & UDSL_SKIP_HEAVY_INIT) && driver->heavy_init) { + error = usbatm_heavy_init(instance); + } else { + complete(&instance->thread_exited); /* pretend that heavy_init was run */ + error = usbatm_atm_init(instance); + } + + if (error < 0) + goto fail_unbind; + + usb_get_dev(usb_dev); + usb_set_intfdata(intf, instance); + + return 0; + + fail_unbind: + if (instance->driver->unbind) + instance->driver->unbind(instance, intf); + fail_free: + kfree(instance->cell_buf); + + for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) { + if (instance->urbs[i]) + kfree(instance->urbs[i]->transfer_buffer); + usb_free_urb(instance->urbs[i]); + } + + kfree(instance); + + return error; +} +EXPORT_SYMBOL_GPL(usbatm_usb_probe); + +void usbatm_usb_disconnect(struct usb_interface *intf) +{ + struct device *dev = &intf->dev; + struct usbatm_data *instance = usb_get_intfdata(intf); + struct usbatm_vcc_data *vcc_data; + int i; + + if (!instance) { + dev_dbg(dev, "%s: NULL instance!\n", __func__); + return; + } + + usb_set_intfdata(intf, NULL); + + mutex_lock(&instance->serialize); + instance->disconnected = 1; + if (instance->thread != NULL) + send_sig(SIGTERM, instance->thread, 1); + mutex_unlock(&instance->serialize); + + wait_for_completion(&instance->thread_exited); + + mutex_lock(&instance->serialize); + list_for_each_entry(vcc_data, &instance->vcc_list, list) + vcc_release_async(vcc_data->vcc, -EPIPE); + mutex_unlock(&instance->serialize); + + tasklet_disable(&instance->rx_channel.tasklet); + tasklet_disable(&instance->tx_channel.tasklet); + + for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) + usb_kill_urb(instance->urbs[i]); + + del_timer_sync(&instance->rx_channel.delay); + del_timer_sync(&instance->tx_channel.delay); + + /* turn usbatm_[rt]x_process into something close to a no-op */ + /* no need to take the spinlock */ + INIT_LIST_HEAD(&instance->rx_channel.list); + INIT_LIST_HEAD(&instance->tx_channel.list); + + tasklet_enable(&instance->rx_channel.tasklet); + tasklet_enable(&instance->tx_channel.tasklet); + + if (instance->atm_dev && instance->driver->atm_stop) + instance->driver->atm_stop(instance, instance->atm_dev); + + if (instance->driver->unbind) + instance->driver->unbind(instance, intf); + + instance->driver_data = NULL; + + for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) { + kfree(instance->urbs[i]->transfer_buffer); + usb_free_urb(instance->urbs[i]); + } + + kfree(instance->cell_buf); + + /* ATM finalize */ + if (instance->atm_dev) { + atm_dev_deregister(instance->atm_dev); + instance->atm_dev = NULL; + } + + usbatm_put_instance(instance); /* taken in usbatm_usb_probe */ +} +EXPORT_SYMBOL_GPL(usbatm_usb_disconnect); + + +/*********** +** init ** +***********/ + +static int __init usbatm_usb_init(void) +{ + if (sizeof(struct usbatm_control) > FIELD_SIZEOF(struct sk_buff, cb)) { + printk(KERN_ERR "%s unusable with this kernel!\n", usbatm_driver_name); + return -EIO; + } + + if ((num_rcv_urbs > UDSL_MAX_RCV_URBS) + || (num_snd_urbs > UDSL_MAX_SND_URBS) + || (rcv_buf_bytes < 1) + || (rcv_buf_bytes > UDSL_MAX_BUF_SIZE) + || (snd_buf_bytes < 1) + || (snd_buf_bytes > UDSL_MAX_BUF_SIZE)) + return -EINVAL; + + return 0; +} +module_init(usbatm_usb_init); + +static void __exit usbatm_usb_exit(void) +{ +} +module_exit(usbatm_usb_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); + +/************ +** debug ** +************/ + +#ifdef VERBOSE_DEBUG +static int usbatm_print_packet(struct usbatm_data *instance, + const unsigned char *data, int len) +{ + unsigned char buffer[256]; + int i = 0, j = 0; + + for (i = 0; i < len;) { + buffer[0] = '\0'; + sprintf(buffer, "%.3d :", i); + for (j = 0; (j < 16) && (i < len); j++, i++) + sprintf(buffer, "%s %2.2x", buffer, data[i]); + dev_dbg(&instance->usb_intf->dev, "%s", buffer); + } + return i; +} +#endif diff --git a/drivers/usb/atm/usbatm.h b/drivers/usb/atm/usbatm.h new file mode 100644 index 000000000..f3eecd967 --- /dev/null +++ b/drivers/usb/atm/usbatm.h @@ -0,0 +1,199 @@ +/****************************************************************************** + * usbatm.h - Generic USB xDSL driver core + * + * Copyright (C) 2001, Alcatel + * Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas + * Copyright (C) 2004, David Woodhouse + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +#ifndef _USBATM_H_ +#define _USBATM_H_ + +#include <linux/atm.h> +#include <linux/atmdev.h> +#include <linux/completion.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/kref.h> +#include <linux/list.h> +#include <linux/stringify.h> +#include <linux/usb.h> +#include <linux/mutex.h> +#include <linux/ratelimit.h> + +/* +#define VERBOSE_DEBUG +*/ + +#define usb_err(instance, format, arg...) \ + dev_err(&(instance)->usb_intf->dev , format , ## arg) +#define usb_info(instance, format, arg...) \ + dev_info(&(instance)->usb_intf->dev , format , ## arg) +#define usb_warn(instance, format, arg...) \ + dev_warn(&(instance)->usb_intf->dev , format , ## arg) +#define usb_dbg(instance, format, arg...) \ + dev_dbg(&(instance)->usb_intf->dev , format , ## arg) + +/* FIXME: move to dev_* once ATM is driver model aware */ +#define atm_printk(level, instance, format, arg...) \ + printk(level "ATM dev %d: " format , \ + (instance)->atm_dev->number , ## arg) + +#define atm_err(instance, format, arg...) \ + atm_printk(KERN_ERR, instance , format , ## arg) +#define atm_info(instance, format, arg...) \ + atm_printk(KERN_INFO, instance , format , ## arg) +#define atm_warn(instance, format, arg...) \ + atm_printk(KERN_WARNING, instance , format , ## arg) +#define atm_dbg(instance, format, ...) \ + pr_debug("ATM dev %d: " format, \ + (instance)->atm_dev->number, ##__VA_ARGS__) +#define atm_rldbg(instance, format, ...) \ + pr_debug_ratelimited("ATM dev %d: " format, \ + (instance)->atm_dev->number, ##__VA_ARGS__) + +/* flags, set by mini-driver in bind() */ + +#define UDSL_SKIP_HEAVY_INIT (1<<0) +#define UDSL_USE_ISOC (1<<1) +#define UDSL_IGNORE_EILSEQ (1<<2) + + +/* mini driver */ + +struct usbatm_data; + +/* +* Assuming all methods exist and succeed, they are called in this order: +* +* bind, heavy_init, atm_start, ..., atm_stop, unbind +*/ + +struct usbatm_driver { + const char *driver_name; + + /* init device ... can sleep, or cause probe() failure */ + int (*bind) (struct usbatm_data *, struct usb_interface *, + const struct usb_device_id *id); + + /* additional device initialization that is too slow to be done in probe() */ + int (*heavy_init) (struct usbatm_data *, struct usb_interface *); + + /* cleanup device ... can sleep, but can't fail */ + void (*unbind) (struct usbatm_data *, struct usb_interface *); + + /* init ATM device ... can sleep, or cause ATM initialization failure */ + int (*atm_start) (struct usbatm_data *, struct atm_dev *); + + /* cleanup ATM device ... can sleep, but can't fail */ + void (*atm_stop) (struct usbatm_data *, struct atm_dev *); + + int bulk_in; /* bulk rx endpoint */ + int isoc_in; /* isochronous rx endpoint */ + int bulk_out; /* bulk tx endpoint */ + + unsigned rx_padding; + unsigned tx_padding; +}; + +extern int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id, + struct usbatm_driver *driver); +extern void usbatm_usb_disconnect(struct usb_interface *intf); + + +struct usbatm_channel { + int endpoint; /* usb pipe */ + unsigned int stride; /* ATM cell size + padding */ + unsigned int buf_size; /* urb buffer size */ + unsigned int packet_size; /* endpoint maxpacket */ + spinlock_t lock; + struct list_head list; + struct tasklet_struct tasklet; + struct timer_list delay; + struct usbatm_data *usbatm; +}; + +/* main driver data */ + +struct usbatm_data { + /****************** + * public fields * + ******************/ + + /* mini driver */ + struct usbatm_driver *driver; + void *driver_data; + char driver_name[16]; + unsigned int flags; /* set by mini-driver in bind() */ + + /* USB device */ + struct usb_device *usb_dev; + struct usb_interface *usb_intf; + char description[64]; + + /* ATM device */ + struct atm_dev *atm_dev; + + /******************************** + * private fields - do not use * + ********************************/ + + struct kref refcount; + struct mutex serialize; + int disconnected; + + /* heavy init */ + struct task_struct *thread; + struct completion thread_started; + struct completion thread_exited; + + /* ATM device */ + struct list_head vcc_list; + + struct usbatm_channel rx_channel; + struct usbatm_channel tx_channel; + + struct sk_buff_head sndqueue; + struct sk_buff *current_skb; /* being emptied */ + + struct usbatm_vcc_data *cached_vcc; + int cached_vci; + short cached_vpi; + + unsigned char *cell_buf; /* holds partial rx cell */ + unsigned int buf_usage; + + struct urb *urbs[0]; +}; + +static inline void *to_usbatm_driver_data(struct usb_interface *intf) +{ + struct usbatm_data *usbatm_instance; + + if (intf == NULL) + return NULL; + + usbatm_instance = usb_get_intfdata(intf); + + if (usbatm_instance == NULL) /* set NULL before unbind() */ + return NULL; + + return usbatm_instance->driver_data; /* set NULL after unbind() */ +} + +#endif /* _USBATM_H_ */ diff --git a/drivers/usb/atm/xusbatm.c b/drivers/usb/atm/xusbatm.c new file mode 100644 index 000000000..b3b1bb78b --- /dev/null +++ b/drivers/usb/atm/xusbatm.c @@ -0,0 +1,229 @@ +/****************************************************************************** + * xusbatm.c - dumb usbatm-based driver for modems initialized in userspace + * + * Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +#include <linux/module.h> +#include <linux/etherdevice.h> /* for eth_random_addr() */ + +#include "usbatm.h" + + +#define XUSBATM_DRIVERS_MAX 8 + +#define XUSBATM_PARM(name, type, parmtype, desc) \ + static type name[XUSBATM_DRIVERS_MAX]; \ + static unsigned int num_##name; \ + module_param_array(name, parmtype, &num_##name, 0444); \ + MODULE_PARM_DESC(name, desc) + +XUSBATM_PARM(vendor, unsigned short, ushort, "USB device vendor"); +XUSBATM_PARM(product, unsigned short, ushort, "USB device product"); + +XUSBATM_PARM(rx_endpoint, unsigned char, byte, "rx endpoint number"); +XUSBATM_PARM(tx_endpoint, unsigned char, byte, "tx endpoint number"); +XUSBATM_PARM(rx_padding, unsigned char, byte, "rx padding (default 0)"); +XUSBATM_PARM(tx_padding, unsigned char, byte, "tx padding (default 0)"); +XUSBATM_PARM(rx_altsetting, unsigned char, byte, "rx altsetting (default 0)"); +XUSBATM_PARM(tx_altsetting, unsigned char, byte, "rx altsetting (default 0)"); + +static const char xusbatm_driver_name[] = "xusbatm"; + +static struct usbatm_driver xusbatm_drivers[XUSBATM_DRIVERS_MAX]; +static struct usb_device_id xusbatm_usb_ids[XUSBATM_DRIVERS_MAX + 1]; +static struct usb_driver xusbatm_usb_driver; + +static struct usb_interface *xusbatm_find_intf(struct usb_device *usb_dev, int altsetting, u8 ep) +{ + struct usb_host_interface *alt; + struct usb_interface *intf; + int i, j; + + for (i = 0; i < usb_dev->actconfig->desc.bNumInterfaces; i++) + if ((intf = usb_dev->actconfig->interface[i]) && (alt = usb_altnum_to_altsetting(intf, altsetting))) + for (j = 0; j < alt->desc.bNumEndpoints; j++) + if (alt->endpoint[j].desc.bEndpointAddress == ep) + return intf; + return NULL; +} + +static int xusbatm_capture_intf(struct usbatm_data *usbatm, struct usb_device *usb_dev, + struct usb_interface *intf, int altsetting, int claim) +{ + int ifnum = intf->altsetting->desc.bInterfaceNumber; + int ret; + + if (claim && (ret = usb_driver_claim_interface(&xusbatm_usb_driver, intf, usbatm))) { + usb_err(usbatm, "%s: failed to claim interface %2d (%d)!\n", __func__, ifnum, ret); + return ret; + } + if ((ret = usb_set_interface(usb_dev, ifnum, altsetting))) { + usb_err(usbatm, "%s: altsetting %2d for interface %2d failed (%d)!\n", __func__, altsetting, ifnum, ret); + return ret; + } + return 0; +} + +static void xusbatm_release_intf(struct usb_device *usb_dev, struct usb_interface *intf, int claimed) +{ + if (claimed) { + usb_set_intfdata(intf, NULL); + usb_driver_release_interface(&xusbatm_usb_driver, intf); + } +} + +static int xusbatm_bind(struct usbatm_data *usbatm, + struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + int drv_ix = id - xusbatm_usb_ids; + int rx_alt = rx_altsetting[drv_ix]; + int tx_alt = tx_altsetting[drv_ix]; + struct usb_interface *rx_intf = xusbatm_find_intf(usb_dev, rx_alt, rx_endpoint[drv_ix]); + struct usb_interface *tx_intf = xusbatm_find_intf(usb_dev, tx_alt, tx_endpoint[drv_ix]); + int ret; + + usb_dbg(usbatm, "%s: binding driver %d: vendor %04x product %04x" + " rx: ep %02x padd %d alt %2d tx: ep %02x padd %d alt %2d\n", + __func__, drv_ix, vendor[drv_ix], product[drv_ix], + rx_endpoint[drv_ix], rx_padding[drv_ix], rx_alt, + tx_endpoint[drv_ix], tx_padding[drv_ix], tx_alt); + + if (!rx_intf || !tx_intf) { + if (!rx_intf) + usb_dbg(usbatm, "%s: no interface contains endpoint %02x in altsetting %2d\n", + __func__, rx_endpoint[drv_ix], rx_alt); + if (!tx_intf) + usb_dbg(usbatm, "%s: no interface contains endpoint %02x in altsetting %2d\n", + __func__, tx_endpoint[drv_ix], tx_alt); + return -ENODEV; + } + + if ((rx_intf != intf) && (tx_intf != intf)) + return -ENODEV; + + if ((rx_intf == tx_intf) && (rx_alt != tx_alt)) { + usb_err(usbatm, "%s: altsettings clash on interface %2d (%2d vs %2d)!\n", __func__, + rx_intf->altsetting->desc.bInterfaceNumber, rx_alt, tx_alt); + return -EINVAL; + } + + usb_dbg(usbatm, "%s: rx If#=%2d; tx If#=%2d\n", __func__, + rx_intf->altsetting->desc.bInterfaceNumber, + tx_intf->altsetting->desc.bInterfaceNumber); + + if ((ret = xusbatm_capture_intf(usbatm, usb_dev, rx_intf, rx_alt, rx_intf != intf))) + return ret; + + if ((tx_intf != rx_intf) && (ret = xusbatm_capture_intf(usbatm, usb_dev, tx_intf, tx_alt, tx_intf != intf))) { + xusbatm_release_intf(usb_dev, rx_intf, rx_intf != intf); + return ret; + } + + return 0; +} + +static void xusbatm_unbind(struct usbatm_data *usbatm, + struct usb_interface *intf) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + int i; + + usb_dbg(usbatm, "%s entered\n", __func__); + + for (i = 0; i < usb_dev->actconfig->desc.bNumInterfaces; i++) { + struct usb_interface *cur_intf = usb_dev->actconfig->interface[i]; + + if (cur_intf && (usb_get_intfdata(cur_intf) == usbatm)) { + usb_set_intfdata(cur_intf, NULL); + usb_driver_release_interface(&xusbatm_usb_driver, cur_intf); + } + } +} + +static int xusbatm_atm_start(struct usbatm_data *usbatm, + struct atm_dev *atm_dev) +{ + atm_dbg(usbatm, "%s entered\n", __func__); + + /* use random MAC as we've no way to get it from the device */ + eth_random_addr(atm_dev->esi); + + return 0; +} + + +static int xusbatm_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + return usbatm_usb_probe(intf, id, + xusbatm_drivers + (id - xusbatm_usb_ids)); +} + +static struct usb_driver xusbatm_usb_driver = { + .name = xusbatm_driver_name, + .probe = xusbatm_usb_probe, + .disconnect = usbatm_usb_disconnect, + .id_table = xusbatm_usb_ids +}; + +static int __init xusbatm_init(void) +{ + int i; + + if (!num_vendor || + num_vendor != num_product || + num_vendor != num_rx_endpoint || + num_vendor != num_tx_endpoint) { + printk(KERN_WARNING "xusbatm: malformed module parameters\n"); + return -EINVAL; + } + + for (i = 0; i < num_vendor; i++) { + rx_endpoint[i] |= USB_DIR_IN; + tx_endpoint[i] &= USB_ENDPOINT_NUMBER_MASK; + + xusbatm_usb_ids[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE; + xusbatm_usb_ids[i].idVendor = vendor[i]; + xusbatm_usb_ids[i].idProduct = product[i]; + + xusbatm_drivers[i].driver_name = xusbatm_driver_name; + xusbatm_drivers[i].bind = xusbatm_bind; + xusbatm_drivers[i].unbind = xusbatm_unbind; + xusbatm_drivers[i].atm_start = xusbatm_atm_start; + xusbatm_drivers[i].bulk_in = rx_endpoint[i]; + xusbatm_drivers[i].bulk_out = tx_endpoint[i]; + xusbatm_drivers[i].rx_padding = rx_padding[i]; + xusbatm_drivers[i].tx_padding = tx_padding[i]; + } + + return usb_register(&xusbatm_usb_driver); +} +module_init(xusbatm_init); + +static void __exit xusbatm_exit(void) +{ + usb_deregister(&xusbatm_usb_driver); +} +module_exit(xusbatm_exit); + +MODULE_AUTHOR("Roman Kagan, Duncan Sands"); +MODULE_DESCRIPTION("Driver for USB ADSL modems initialized in userspace"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); |