Hi,
Attached is the driver for USB ADSL modems based on the Conexant
AccessRunner chipset using the recently introduced usb_atm
infrastructure. It was inspired by the current speedtch driver (and
actually started up by s/speedtch/cxacru/g on it, so may have inherited
its bugs :), and is based heavily on the cxacru bundle by Josep Comas
(http://accessrunner.sourceforge.net/), hence the name (provided Josep
doesn't object).
The driver uses the in-kernel firmware loader in the style speedtch
does, with a few points to note:
1) older chipset revisions need a boot ROM patch in addition to the
firmware. Instead of adding heuristics to decide whether it's needed, I
trust the user to supply it only when it is.
2) older chipset revisions need different values for the PLL clock
registers, which have to be written before the firmware is loaded.
Currently I believe that these are the same chipset revisions that need
the boot ROM patch, so the values are chosen upon the availability of
the latter.
3) after the firmware is loaded, the modem needs to be sent an array of
64 to 80 configuration parameters, whose values and number depend on the
modem and probably the service provider. To avoid complicating the
code, I also fetch this array as a le32 blob through the firmware
loader. The ways to prepare it in userspace are a separate issue; I've
done mine from the usbsnoop output in Windows.
Please comment and consider for inclusion. The driver has been (and is
being, while I'm writing this via the ADSL link) tested with ZyXEL
630-C1 modem (trademarked in Russia as ZyXEL OMNI ADSL USB).
Also please note that this is my first attempt in a non-trivial
contribution to the kernel, so it may have all sorts of issues (locking,
SMP corectness, etc.) and needs to be checked carefully by an
experienced kernel hacker.
Roman.
/******************************************************************************
* cxacru.c - driver for USB xDSL modems based on
* Conexant AccessRunner chipset
*
* 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/moduleparam.h>
#include <linux/gfp.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/errno.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/list.h>
#include <asm/processor.h>
#include <asm/uaccess.h>
#include <linux/smp_lock.h>
#include <linux/interrupt.h>
#include <linux/atm.h>
#include <linux/atmdev.h>
#include <linux/crc32.h>
#include <linux/init.h>
#include <linux/firmware.h>
/*
#define DEBUG
*/
#if !defined (DEBUG) && defined (CONFIG_USB_DEBUG)
# define DEBUG
#endif
#include <linux/usb.h>
#include "usb_atm.h"
#if defined(CONFIG_FW_LOADER) || defined(CONFIG_FW_LOADER_MODULE)
# define USE_FW_LOADER
#endif
#define DRIVER_AUTHOR "Roman Kagan, Josep Comas, David Woodhouse, Duncan
Sands"
#define DRIVER_VERSION "0.1"
#define DRIVER_DESC "Conexant AccessRunner USB driver version "
DRIVER_VERSION
static const char cxacru_driver_name[] = "cxacru";
#define UDSL_IOCTL_LINE_UP 1
#define UDSL_IOCTL_LINE_DOWN 2
#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)? */
/* 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 PLLFCLK_NEW 0x5
#define PLLBCLK_NEW 0x3
#define PLLFCLK_OLD 0x02d874df
#define PLLBCLK_OLD 0x0196a51a
#define SDRAM_ENA 0x1
/* Timeout in jiffies */
#define CMD_TIMEOUT (2 * HZ)
#define DATA_TIMEOUT (2 * HZ)
/* 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,
};
/* 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,
};
struct cxacru_instance_data {
struct udsl_instance_data u;
int line_status;
struct work_struct poll_work;
struct timer_list poll_timer;
/* contol handles */
struct semaphore cm_serialize;
u8 *rcv_buf;
u8 *snd_buf;
struct urb *rcv_urb;
struct urb *snd_urb;
struct completion rcv_done;
struct completion snd_done;
};
/* the following three functions are stolen from drivers/usb/core/message.c */
static void cxacru_blocking_completion(struct urb *urb, struct pt_regs *regs)
{
complete((struct completion *)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;
int status;
init_timer(&timer);
timer.expires = jiffies + CMD_TIMEOUT;
timer.data = (unsigned long) urb;
timer.function = cxacru_timeout_kill;
add_timer(&timer);
wait_for_completion(done);
status = urb->status;
if (status == -ECONNRESET)
status = -ETIMEDOUT;
del_timer_sync(&timer);
if (actual_length)
*actual_length = urb->actual_length;
return status;
}
static int cxacru_cm(struct cxacru_instance_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) {
dbg("too big transfer requested");
ret = -ENOMEM;
goto fail;
}
down(&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) {
err("submitting read urb for cm %#x failed", cm);
ret = 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) {
err("submitting write urb for cm %#x failed", cm);
ret = ret;
goto fail;
}
ret = cxacru_start_wait_urb(instance->snd_urb, &instance->snd_done,
NULL);
if (ret < 0) {
err("sending cm %#x failed", cm);
ret = ret;
goto fail;
}
ret = cxacru_start_wait_urb(instance->rcv_urb, &instance->rcv_done,
&actlen);
if (ret < 0) {
err("receiving cm %#x failed", cm);
ret = ret;
goto fail;
}
if (actlen % CMD_PACKET_SIZE || !actlen) {
err("response is not a positive multiple of %d: %#x",
CMD_PACKET_SIZE, 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) {
err("wrong cm %#x in response", rbuf[offb]);
ret = -EIO;
goto fail;
}
if (rbuf[offb + 1] != CM_STATUS_SUCCESS) {
err("response failed: %#x", 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;
dbg("cm %#x", cm);
fail:
up(&instance->cm_serialize);
return ret;
}
static int cxacru_cm_get_array(struct cxacru_instance_data *instance, enum
cxacru_cm_request cm,
u32 *data, int size)
{
int ret, len;
u32 *buf;
int offb, 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 > stride || l > (len - offb) / 2) {
err("wrong data length %#x in response", l);
ret = -EIO;
goto cleanup;
}
while (l--) {
offd = le32_to_cpu(buf[offb++]);
if (offd >= size) {
err("wrong index %#x in response", offd);
ret = -EIO;
goto cleanup;
}
data[offd] = le32_to_cpu(buf[offb++]);
}
}
ret = 0;
cleanup:
kfree(buf);
return ret;
}
static void cxacru_got_firmware(struct cxacru_instance_data *instance,
int got_it)
{
int ret;
dbg("got_firmware %d", got_it);
down(&instance->u.serialize); /* vs self, cxacru_firmware_start */
if (instance->u.status == UDSL_LOADED_FIRMWARE)
goto out;
if (!got_it) {
instance->u.status = UDSL_NO_FIRMWARE;
goto out;
}
ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0);
if (ret < 0) {
err("cxacru_got_firmware: CARD_GET_STATUS returned %d", ret);
instance->u.status = UDSL_NO_FIRMWARE;
goto out;
}
/* Read MAC address */
memset(instance->u.atm_dev->esi, 0, sizeof(instance->u.atm_dev->esi));
ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_MAC_ADDRESS, NULL, 0,
instance->u.atm_dev->esi,
sizeof(instance->u.atm_dev->esi));
if (ret < 0) {
err("cxacru_got_firmware: CARD_GET_MAC_ADDRESS returned %d",
ret);
instance->u.status = UDSL_NO_FIRMWARE;
goto out;
}
dev_info(&instance->u.usb_dev->dev,
"cxacru: MAC = %02x", instance->u.atm_dev->esi[0]);
for (ret = 1; ret < sizeof(instance->u.atm_dev->esi); ret++)
printk(":%02x", instance->u.atm_dev->esi[ret]);
printk("\n");
/* start ADSL */
ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0,
NULL, 0);
if (ret < 0) {
err("cxacru_got_firmware: CHIP_ADSL_LINE_START returned %d",
ret);
instance->u.status = UDSL_NO_FIRMWARE;
goto out;
}
/* Start status polling */
mod_timer(&instance->poll_timer, jiffies + (1 * HZ));
instance->u.status = UDSL_LOADED_FIRMWARE;
tasklet_schedule(&instance->u.receive_tasklet);
out:
up(&instance->u.serialize);
wake_up_interruptible(&instance->u.firmware_waiters);
}
static void cxacru_poll_status(struct cxacru_instance_data *instance)
{
u32 buf[CXINF_MAX] = {};
int ret;
ret = cxacru_cm_get_array(instance, CM_REQUEST_CARD_INFO_GET, buf,
CXINF_MAX);
if (ret < 0) {
dev_warn(&instance->u.usb_dev->dev, "poll status: error %d\n",
ret);
return;
}
if (instance->line_status == buf[CXINF_LINE_STATUS])
return;
instance->line_status = buf[CXINF_LINE_STATUS];
switch (instance->line_status) {
case 0:
instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
dev_info(&instance->u.usb_dev->dev, "ADSL line: down\n");
break;
case 1:
instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
dev_info(&instance->u.usb_dev->dev, "ADSL line: attemtping to
activate\n");
break;
case 2:
instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
dev_info(&instance->u.usb_dev->dev, "ADSL line: training\n");
break;
case 3:
instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
dev_info(&instance->u.usb_dev->dev, "ADSL line: channel
analysis\n");
break;
case 4:
instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
dev_info(&instance->u.usb_dev->dev, "ADSL line: exchange\n");
break;
case 5:
instance->u.atm_dev->link_rate = buf[CXINF_DOWNSTREAM_RATE] *
1000 / 424;
instance->u.atm_dev->signal = ATM_PHY_SIG_FOUND;
dev_info(&instance->u.usb_dev->dev, "ADSL line: up (%d Kib/s
down | %d Kib/s up)\n",
buf[CXINF_DOWNSTREAM_RATE], buf[CXINF_UPSTREAM_RATE]);
break;
case 6:
instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
dev_info(&instance->u.usb_dev->dev, "ADSL line: waiting\n");
break;
case 7:
instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
dev_info(&instance->u.usb_dev->dev, "ADSL line:
initializing\n");
break;
default:
instance->u.atm_dev->signal = ATM_PHY_SIG_UNKNOWN;
dev_info(&instance->u.usb_dev->dev, "Unknown line state %02x\n",
instance->line_status);
break;
}
}
static void cxacru_timer_poll(unsigned long data)
{
struct cxacru_instance_data *instance = (void *)data;
schedule_work(&instance->poll_work);
mod_timer(&instance->poll_timer, jiffies + (5 * HZ));
}
#ifdef USE_FW_LOADER
static int cxacru_fw(struct cxacru_instance_data *instance, enum
cxacru_fw_request fw,
u8 code1, u8 code2, u32 addr, u8 *data, int size)
{
int ret;
u8 *buf;
int offd, offb;
const int stride = CMD_PACKET_SIZE - 8;
struct usb_device *usb_dev = instance->u.usb_dev;
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;
*((u32 *) (buf + offb)) = cpu_to_le32(addr);
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) {
err("sending fw %#x failed", fw);
goto cleanup;
}
offb = 0;
}
} while(offd < size);
dbg("sent fw %#x", fw);
ret = 0;
cleanup:
free_page((unsigned long) buf);
return ret;
}
static void cxacru_upload_firmware(struct cxacru_instance_data *instance,
const struct firmware *fw,
const struct firmware *br,
const struct firmware *cf)
{
int ret;
int off;
struct usb_device *dev = instance->u.usb_dev;
const u32 vid = dev->descriptor.idVendor;
const u32 pid = dev->descriptor.idProduct;
u32 val;
dbg("cxacru_upload_firmware");
/* FirmwarePllFClkValue */
val = br ? PLLFCLK_OLD : PLLFCLK_NEW;
ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0, PLLFCLK_ADDR, (u8 *)
&val, 4);
if (ret) {
err("FirmwarePllFClkValue failed: %d", ret);
goto fail;
}
/* FirmwarePllBClkValue */
val = br ? PLLBCLK_OLD : PLLBCLK_NEW;
ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0, PLLBCLK_ADDR, (u8 *)
&val, 4);
if (ret) {
err("FirmwarePllBClkValue failed: %d", ret);
goto fail;
}
/* Enable SDRAM */
val = SDRAM_ENA;
ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0, SDRAMEN_ADDR, (u8 *)
&val, 4);
if (ret) {
err("Enable SDRAM failed: %d", ret);
goto fail;
}
/* Firmware */
ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0, FW_ADDR, fw->data,
fw->size);
if (ret) {
err("Firmware upload failed: %d", ret);
goto fail;
}
/* Boot ROM patch */
if (br) {
ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0, BR_ADDR,
br->data, br->size);
if (ret) {
err("Boot ROM patching failed: %d", ret);
goto fail;
}
}
/* Signature */
val = (pid << 16) | (vid & 0xffff);
ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0, SIG_ADDR, (u8 *)
&val, 4);
if (ret) {
err("Signature storing failed: %d", ret);
goto fail;
}
if (br) {
val = BR_ADDR;
ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0,
BR_STACK_ADDR, (u8 *) &val, 4);
}
else {
ret = cxacru_fw(instance, FW_GOTO_MEM, 0x0, 0x0, FW_ADDR, NULL,
0);
}
if (ret) {
err("Passing control to firmware failed: %d", ret);
goto fail;
}
/* Delay to allow firmware to start up. We can do this here
because we're in our own kernel thread anyway. */
msleep(1000);
usb_clear_halt(dev, usb_sndbulkpipe(dev, CXACRU_EP_CMD));
usb_clear_halt(dev, usb_rcvbulkpipe(dev, CXACRU_EP_CMD));
usb_clear_halt(dev, usb_sndbulkpipe(dev, CXACRU_EP_DATA));
usb_clear_halt(dev, usb_rcvbulkpipe(dev, CXACRU_EP_DATA));
ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0);
if (ret < 0) {
err("modem failed to initialize: %d", ret);
goto fail;
}
/* Load config data (le32), doing one packet at a time */
for (off = 0; off < cf->size / 4; ) {
u32 buf[CMD_PACKET_SIZE / 4 - 1];
int i, len = min_t(int, cf->size / 4 - off, CMD_PACKET_SIZE / 4
/ 2 - 1);
buf[0] = cpu_to_le32(len);
for (i = 0; i < len; i++, off++) {
buf[i * 2 + 1] = cpu_to_le32(off);
memcpy(buf + i * 2 + 2, cf->data + off * 4, 4);
}
ret = cxacru_cm(instance, CM_REQUEST_CARD_DATA_SET,
(u8 *) buf, len, NULL, 0);
if (ret < 0) {
err("load config data failed: %d", ret);
goto fail;
}
}
msleep(4000);
dbg("done setting up the modem");
cxacru_got_firmware(instance, 1);
return;
fail:
cxacru_got_firmware(instance, 0);
}
static int cxacru_find_firmware(struct cxacru_instance_data *instance,
char* phase, const struct firmware **fw_p)
{
char buf[16];
sprintf(buf, "cxacru-%s.bin", phase);
dbg("cxacru_find_firmware: looking for %s", buf);
if (request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) {
dev_warn(&instance->u.usb_dev->dev, "no stage %s firmware
found\n", phase);
return -ENOENT;
}
dev_info(&instance->u.usb_dev->dev, "found firmware %s\n", buf);
return 0;
}
static int cxacru_load_firmware(void *arg)
{
const struct firmware *fw, *bp, *cf;
struct cxacru_instance_data *instance = arg;
BUG_ON(!instance);
daemonize("firmware/cxacru");
if (!cxacru_find_firmware(instance, "fw", &fw)) {
if (!cxacru_find_firmware(instance, "cf", &cf)) {
if (!cxacru_find_firmware(instance, "bp", &bp))
/* ok, assume it's not needed */
bp = NULL;
cxacru_upload_firmware(instance, fw, bp, cf);
release_firmware(cf);
}
release_firmware(fw);
}
/* In case we failed, set state back to NO_FIRMWARE so that
another later attempt may work. Otherwise, we never actually
manage to recover if, for example, the firmware is on /usr and
we look for it too early. */
cxacru_got_firmware(instance, 0);
module_put(THIS_MODULE);
udsl_put_instance(&instance->u);
return 0;
}
#endif /* USE_FW_LOADER */
static void cxacru_firmware_start(struct cxacru_instance_data *instance)
{
#ifdef USE_FW_LOADER
int ret;
#endif
dbg("cxacru_firmware_start");
down(&instance->u.serialize); /* vs self, cxacru_got_firmware */
if (instance->u.status >= UDSL_LOADING_FIRMWARE) {
up(&instance->u.serialize);
return;
}
instance->u.status = UDSL_LOADING_FIRMWARE;
up(&instance->u.serialize);
#ifdef USE_FW_LOADER
udsl_get_instance(&instance->u);
try_module_get(THIS_MODULE);
ret = kernel_thread(cxacru_load_firmware, instance,
CLONE_FS | CLONE_FILES);
if (ret >= 0)
return; /* OK */
dbg("cxacru_firmware_start: kernel_thread failed (%d)!", ret);
module_put(THIS_MODULE);
udsl_put_instance(&instance->u);
/* Just pretend it never happened... hope modem_run happens */
#endif /* USE_FW_LOADER */
cxacru_got_firmware(instance, 0);
}
static int cxacru_firmware_wait(struct udsl_instance_data *instance)
{
cxacru_firmware_start((void *)instance);
if (wait_event_interruptible(instance->firmware_waiters,
instance->status != UDSL_LOADING_FIRMWARE) < 0)
return -ERESTARTSYS;
return (instance->status == UDSL_LOADED_FIRMWARE) ? 0 : -EAGAIN;
}
static int cxacru_usb_ioctl(struct usb_interface *intf, unsigned int code,
void *user_data)
{
struct cxacru_instance_data *instance = usb_get_intfdata(intf);
dbg("cxacru_usb_ioctl entered");
if (!instance) {
dbg("cxacru_usb_ioctl: NULL instance!");
return -ENODEV;
}
switch (code) {
case UDSL_IOCTL_LINE_UP:
instance->u.atm_dev->signal = ATM_PHY_SIG_FOUND;
cxacru_got_firmware(instance, 1);
return (instance->u.status == UDSL_LOADED_FIRMWARE) ? 0 : -EIO;
case UDSL_IOCTL_LINE_DOWN:
instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_STOP, NULL, 0,
NULL, 0);
return 0;
default:
return -ENOTTY;
}
}
static int cxacru_usb_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct cxacru_instance_data *instance;
int ret;
dbg("cxacru_usb_probe: trying device with vendor=0x%x, product=0x%x",
dev->descriptor.idVendor, dev->descriptor.idProduct);
/* instance init */
instance = kmalloc(sizeof(*instance), GFP_KERNEL);
if (!instance) {
dbg("cxacru_usb_probe: no memory for instance data");
return -ENOMEM;
}
memset(instance, 0, sizeof(struct cxacru_instance_data));
instance->rcv_buf = (u8 *) __get_free_page(GFP_KERNEL);
if (!instance->rcv_buf) {
dbg("cxacru_usb_probe: no memory for rcv_buf");
ret = -ENOMEM;
goto fail;
}
instance->snd_buf = (u8 *) __get_free_page(GFP_KERNEL);
if (!instance->snd_buf) {
dbg("cxacru_usb_probe: no memory for snd_buf");
ret = -ENOMEM;
goto fail;
}
instance->rcv_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!instance->rcv_urb) {
dbg("cxacru_usb_probe: no memory for rcv_urb");
ret = -ENOMEM;
goto fail;
}
instance->snd_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!instance->snd_urb) {
dbg("cxacru_usb_probe: no memory for snd_urb");
ret = -ENOMEM;
goto fail;
}
instance->u.data_endpoint = CXACRU_EP_DATA;
instance->u.firmware_wait = cxacru_firmware_wait;
instance->u.snd_padding = 11;
instance->u.rcv_padding = 3;
instance->u.driver_name = cxacru_driver_name;
ret = udsl_instance_setup(dev, &instance->u);
if (ret)
goto fail;
usb_fill_int_urb(instance->rcv_urb, dev, usb_rcvintpipe(dev,
CXACRU_EP_CMD),
instance->rcv_buf, PAGE_SIZE,
cxacru_blocking_completion, &instance->rcv_done, 1);
instance->rcv_urb->transfer_flags |= URB_ASYNC_UNLINK;
usb_fill_int_urb(instance->snd_urb, dev, usb_sndintpipe(dev,
CXACRU_EP_CMD),
instance->snd_buf, PAGE_SIZE,
cxacru_blocking_completion, &instance->snd_done, 4);
instance->snd_urb->transfer_flags |= URB_ASYNC_UNLINK;
init_MUTEX(&instance->cm_serialize);
init_timer(&instance->poll_timer);
instance->poll_timer.function = cxacru_timer_poll;
instance->poll_timer.data = (unsigned long)instance;
INIT_WORK(&instance->poll_work, (void *)cxacru_poll_status, instance);
/* First check whether the modem already seems to be alive */
/*
ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0);
if (ret < 0)
*/
cxacru_firmware_start(instance);
/*
else {
dbg("firmware appears to be already loaded");
cxacru_got_firmware(instance, 1);
cxacru_poll_status(instance);
}
*/
usb_set_intfdata(intf, instance);
return 0;
fail:
if(instance->snd_buf)
free_page((unsigned long) instance->snd_buf);
if(instance->rcv_buf)
free_page((unsigned long) instance->rcv_buf);
if(instance->snd_urb)
usb_free_urb(instance->snd_urb);
if(instance->rcv_urb)
usb_free_urb(instance->rcv_urb);
kfree(instance);
return ret;
}
static void cxacru_usb_disconnect(struct usb_interface *intf)
{
struct cxacru_instance_data *instance = usb_get_intfdata(intf);
dbg("cxacru_usb_disconnect entered");
if (!instance) {
dbg("cxacru_usb_disconnect: NULL instance!");
return;
}
del_timer_sync(&instance->poll_timer);
wmb();
flush_scheduled_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);
udsl_instance_disconnect(&instance->u);
/* clean up */
usb_set_intfdata(intf, NULL);
udsl_put_instance(&instance->u);
}
static struct usb_device_id cxacru_usb_ids[] = {
{USB_DEVICE(0x0572, 0xcafe)}, /* V = Conexant
P = ADSL modem (Euphrates project) */
{USB_DEVICE(0x0572, 0xcb00)}, /* V = Conexant
P = ADSL modem (Hasbani project) */
{USB_DEVICE(0x0572, 0xcb01)}, /* V = Conexant
P = ADSL modem */
{USB_DEVICE(0x0572, 0xcb06)}, /* V = Conexant
P = ADSL modem */
{USB_DEVICE(0x08e3, 0x0100)}, /* V = Olitec
P = ADSL modem version 2 */
{USB_DEVICE(0x08e3, 0x0102)}, /* V = Olitec
P = ADSL modem version 3 */
{USB_DEVICE(0x0eb0, 0x3457)}, /* V = Trust/Amigo Technology Co.
P = AMX-CA86U */
{USB_DEVICE(0x1803, 0x5510)}, /* V = Zoom
P = 5510 */
{USB_DEVICE(0x0675, 0x0200)}, /* V = Draytek
P = Vigor 318 */
{USB_DEVICE(0x0586, 0x330a)}, /* V = Zyxel
P = 630-C1 aka OMNI ADSL USB modem */
{}
};
MODULE_DEVICE_TABLE(usb, cxacru_usb_ids);
static struct usb_driver cxacru_usb_driver = {
.owner = THIS_MODULE,
.name = cxacru_driver_name,
.probe = cxacru_usb_probe,
.disconnect = cxacru_usb_disconnect,
.ioctl = cxacru_usb_ioctl,
.id_table = cxacru_usb_ids,
};
static int __init cxacru_usb_init(void)
{
dbg("cxacru_usb_init: driver version " DRIVER_VERSION);
return usb_register(&cxacru_usb_driver);
}
static void __exit cxacru_usb_cleanup(void)
{
dbg("cxacru_usb_cleanup entered");
usb_deregister(&cxacru_usb_driver);
}
module_init(cxacru_usb_init);
module_exit(cxacru_usb_cleanup);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);
-------------------------------------------------------
SF email is sponsored by - The IT Product Guide
Read honest & candid reviews on hundreds of IT Products from real users.
Discover which products truly live up to the hype. Start reading now.
http://productguide.itmanagersjournal.com/
_______________________________________________
[email protected]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel