On Wed, 2004-09-29 at 08:50 +0200, Duncan Sands wrote:
> > I'll fix your version, and merge in the missing parts from mine.
> 
> Great, thanks.  I will try to do some polishing/testing/thinking
> tonight.

OK... if the firmware is found in /lib/firmware this loads it, then
starts up the modem and monitors the status. If the firmware _isn't_
found it waits for modem_run as before. 

It pokes at the modem first to see if it responds to a status request;
if it _does_ then it knows the firmware is loaded, and doesn't try to
load it again. If the initial status request fails, we load the
firmware.

You were releasing interface #2 after loading the firmware -- I stopped
doing that, because I think we want to keep it reserved to prevent the
user from running modem_run. 

The interrupt handling isn't tested -- I think the Rev 4 modem (which I
have) doesn't generate interrupts, and I don't actually have it plugged
into a DSL-capable line so it never actually tells me the link is up
anyway.

I also changed the name of the second stage firmware, because we have
different versions for rev[123] and rev4.

--- speedtch.c.orig     2004-09-28 10:47:11.000000000 +0100
+++ speedtch.c  2004-09-29 12:00:23.712398336 +0100
@@ -61,6 +61,7 @@
  *
  */
 
+#include <asm/atomic.h>
 #include <asm/semaphore.h>
 #include <linux/module.h>
 #include <linux/moduleparam.h>
@@ -70,6 +71,7 @@
 #include <linux/errno.h>
 #include <linux/proc_fs.h>
 #include <linux/slab.h>
+#include <linux/wait.h>
 #include <linux/list.h>
 #include <asm/uaccess.h>
 #include <linux/smp_lock.h>
@@ -78,6 +80,7 @@
 #include <linux/atmdev.h>
 #include <linux/crc32.h>
 #include <linux/init.h>
+#include <linux/firmware.h>
 
 /*
 #define DEBUG
@@ -127,12 +130,31 @@
 #define UDSL_DEFAULT_RCV_BUF_SIZE      64 /* ATM cells */
 #define UDSL_DEFAULT_SND_BUF_SIZE      64 /* ATM cells */
 
+/* Timeout in jiffies */
+#define CTRL_TIMEOUT (2*HZ)
+#define DATA_TIMEOUT (2*HZ)
+
+#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 TOTAL    15
+
+#define SIZE_7 1
+#define SIZE_b 8
+#define SIZE_d 4
+#define SIZE_e 1
+#define SIZE_f 1
+
 static unsigned int num_rcv_urbs = UDSL_DEFAULT_RCV_URBS;
 static unsigned int num_snd_urbs = UDSL_DEFAULT_SND_URBS;
 static unsigned int num_rcv_bufs = UDSL_DEFAULT_RCV_BUFS;
 static unsigned int num_snd_bufs = UDSL_DEFAULT_SND_BUFS;
 static unsigned int rcv_buf_size = UDSL_DEFAULT_RCV_BUF_SIZE;
 static unsigned int snd_buf_size = UDSL_DEFAULT_SND_BUF_SIZE;
+static int dl_512_first = 0;
+static int sw_buffering = 0;
 
 module_param (num_rcv_urbs, uint, 0444);
 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) ")");
@@ -152,11 +174,20 @@
 module_param (snd_buf_size, uint, 0444);
 MODULE_PARM_DESC (snd_buf_size, "Size of the buffers used for transmission (range: 
0-" __MODULE_STRING (UDSL_MAX_SND_BUF_SIZE) ", default: " __MODULE_STRING 
(UDSL_DEFAULT_SND_BUF_SIZE) ")");
 
+module_param (dl_512_first, bool, 0444);
+MODULE_PARM_DESC (dl_512_first, "Read 512 bytes before sending firmware");
+
+module_param (sw_buffering, uint, 0444);
+MODULE_PARM_DESC (sw_buffering, "Enable software buffering");
+
 #define UDSL_IOCTL_LINE_UP             1
 #define UDSL_IOCTL_LINE_DOWN           2
 
+#define UDSL_ENDPOINT_INT              0x81
 #define UDSL_ENDPOINT_DATA_OUT         0x07
 #define UDSL_ENDPOINT_DATA_IN          0x87
+#define UDSL_ENDPOINT_FIRMWARE_OUT     0x05
+#define UDSL_ENDPOINT_FIRMWARE_IN      0x85
 
 #define ATM_CELL_HEADER                        (ATM_CELL_SIZE - ATM_CELL_PAYLOAD)
 #define UDSL_NUM_CELLS(x)              (((x) + ATM_AAL5_TRAILER + ATM_CELL_PAYLOAD - 
1) / ATM_CELL_PAYLOAD)
@@ -225,18 +256,35 @@
 
 /* main driver data */
 
+enum udsl_status {
+       UDSL_NO_FIRMWARE,
+       UDSL_LOADING_FIRMWARE,
+       UDSL_LOADED_FIRMWARE
+};
+
 struct udsl_instance_data {
+       atomic_t refcount;
        struct semaphore serialize;
 
        /* USB device part */
        struct usb_device *usb_dev;
        char description [64];
-       int firmware_loaded;
+       int revision;
+
+       /* Status */
+       struct urb *int_urb;
+       unsigned char int_data[16];
+       struct work_struct poll_work;
+       struct timer_list poll_timer;
 
        /* ATM device part */
        struct atm_dev *atm_dev;
        struct list_head vcc_list;
 
+       /* firmware */
+       enum udsl_status status;
+       wait_queue_head_t firmware_waiters;
+
        /* receive */
        struct udsl_receiver receivers [UDSL_MAX_RCV_URBS];
        struct udsl_receive_buffer receive_buffers [UDSL_MAX_RCV_BUFS];
@@ -288,6 +336,8 @@
 static int udsl_usb_probe (struct usb_interface *intf, const struct usb_device_id 
*id);
 static void udsl_usb_disconnect (struct usb_interface *intf);
 static int udsl_usb_ioctl (struct usb_interface *intf, unsigned int code, void 
*user_data);
+static void udsl_handle_int (struct urb *urb, struct pt_regs *regs);
+static void udsl_poll_status(struct udsl_instance_data *instance);
 
 static struct usb_driver udsl_usb_driver = {
        .owner =        THIS_MODULE,
@@ -773,7 +823,7 @@
 
        vdbg ("udsl_atm_send called (skb 0x%p, len %u)", skb, skb->len);
 
-       if (!instance || !instance->usb_dev) {
+       if (!instance) {
                dbg ("udsl_atm_send: NULL data!");
                err = -ENODEV;
                goto fail;
@@ -805,25 +855,611 @@
 }
 
 
-/**********
-**  ATM  **
-**********/
+/********************
+**  bean counting  **
+********************/
 
-static void udsl_atm_dev_close (struct atm_dev *dev)
+static inline void udsl_get_instance (struct udsl_instance_data *instance)
 {
-       struct udsl_instance_data *instance = dev->dev_data;
+       atomic_inc (&instance->refcount);
+}
+
+static inline void udsl_put_instance (struct udsl_instance_data *instance)
+{
+       if (atomic_dec_and_test (&instance->refcount)) {
+               tasklet_kill (&instance->receive_tasklet);
+               tasklet_kill (&instance->send_tasklet);
+               usb_put_dev (instance->usb_dev);
+               kfree (instance);
+       }
+}
+
+
+/***************
+**  firmware  **
+***************/
+
+static void udsl_got_firmware (struct udsl_instance_data *instance, int got_it)
+{
+       int err;
+       struct usb_interface *intf;
+
+       down (&instance->serialize); /* vs self, udsl_firmware_start */
+       if (instance->status == UDSL_LOADED_FIRMWARE)
+               goto out;
+       if (!got_it) {
+               instance->status = UDSL_NO_FIRMWARE;
+               goto out;
+       }
+       if ((err = usb_set_interface (instance->usb_dev, 1,
+                                     instance->revision?1:2)) < 0) {
+               dbg ("udsl_set_alternate: usb_set_interface returned %d!", err);
+               instance->status = UDSL_NO_FIRMWARE;
+               goto out;
+       }
+
+       /* Set up interrupt endpoint */
+       intf = usb_ifnum_to_if(instance->usb_dev, 0);
+       if (intf && !usb_driver_claim_interface (&udsl_usb_driver, intf, NULL)) {
+
+               instance->int_urb = usb_alloc_urb(0, GFP_KERNEL);
+               if (instance->int_urb) {
+
+                       usb_fill_int_urb(instance->int_urb, instance->usb_dev,
+                                        usb_rcvintpipe(instance->usb_dev, 
UDSL_ENDPOINT_INT),
+                                        instance->int_data, 
sizeof(instance->int_data),
+                                        udsl_handle_int, instance, 50);
+                       err = usb_submit_urb(instance->int_urb, GFP_KERNEL);
+                       if (err) {
+                               /* Doesn't matter; we'll poll anyway */
+                               dbg ("udsl_usb_probe: Submission of interrupt URB 
failed %d", err);
+                               usb_free_urb(instance->int_urb);
+                               instance->int_urb = NULL;
+                               usb_driver_release_interface (&udsl_usb_driver, intf);
+                       }
+               }
+       }
+       /* Start status polling */
+       mod_timer(&instance->poll_timer, jiffies + (1*HZ));
+
+       instance->status = UDSL_LOADED_FIRMWARE;
+       tasklet_schedule (&instance->receive_tasklet);
+out:
+       up (&instance->serialize);
+       wake_up_interruptible (&instance->firmware_waiters);
+}
+
+#ifdef CONFIG_FW_LOADER
+static int udsl_set_swbuff (struct udsl_instance_data *instance, int state)
+{
+       struct usb_device *dev = instance->usb_dev;
+       int ret;
+       
+       ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+                             0x32, 0x40, state?0x01:0x00,
+                             0x00, NULL, 0, 100);
+       if (ret < 0) {
+               printk("Warning: %sabling SW buffering: usb_control_msg returned %d\n",
+                      state?"En":"Dis", ret);
+               return ret;
+       }
+
+       dbg("udsl_set_swbuff: %sbled SW buffering", state?"En":"Dis");
+       return 0;
+}
+
+static void udsl_test_sequence(struct udsl_instance_data *instance)
+{
+       struct usb_device *dev = instance->usb_dev;
+       unsigned char buf[10];
+       int ret;
+
+       /* URB 147 */
+       buf[0] = 0x1c; buf[1] = 0x50;
+       ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+                             0x01, 0x40, 0x0b, 0x00, buf, 2, 100);
+       if (ret < 0)
+               printk(KERN_WARNING "%s failed on URB147: %d\n", __func__, ret);
+
+       /* URB 148 */
+       buf[0] = 0x32; buf[1] = 0x00;
+       ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+                             0x01, 0x40, 0x02, 0x00, buf, 2, 100);
+       if (ret < 0)
+               printk(KERN_WARNING "%s failed on URB148: %d\n", __func__, ret);
+
+       /* URB 149 */
+       buf[0] = 0x01; buf[1] = 0x00; buf[2] = 0x01;
+       ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+                             0x01, 0x40, 0x03, 0x00, buf, 3, 100);
+       if (ret < 0)
+               printk(KERN_WARNING "%s failed on URB149: %d\n", __func__, ret);
+       
+       /* URB 150 */
+       buf[0] = 0x01; buf[1] = 0x00; buf[2] = 0x01;
+       ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+                             0x01, 0x40, 0x04, 0x00, buf, 3, 100);
+       if (ret < 0)
+               printk(KERN_WARNING "%s failed on URB150: %d\n", __func__, ret);
+}
+
+static int udsl_start_synchro (struct udsl_instance_data *instance)
+{
+       struct usb_device *dev = instance->usb_dev;
+       unsigned char buf[2];
+       int ret;
+       
+       ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+                             0x12, 0xc0, 0x04, 0x00,
+                             buf, sizeof(buf), CTRL_TIMEOUT);
+       if (ret < 0) {
+               printk(KERN_WARNING "SpeedTouch: Failed to start ADSL synchronisation: 
%d\n", ret);
+               return ret;
+       }
+
+       dbg("udsl_start_synchro: modem prodded. %d Bytes returned: %02x %02x", ret, 
buf[0], buf[1]);
+       return 0;
+}
+
+static void udsl_handle_int (struct urb *urb, struct pt_regs *regs)
+{
+       struct udsl_instance_data *instance = urb->context;
+       unsigned int count = urb->actual_length;
+       int ret;
+
+       /* The magic interrupt for "up state" */
+       const static unsigned char up_int[6]   = { 0xa1, 0x00, 0x01, 0x00, 0x00, 0x00};
+       /* The magic interrupt for "down state" */
+       const static unsigned char down_int[6] = { 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+
+       switch (urb->status) {
+        case 0:
+                /* success */
+                break;
+        case -ECONNRESET:
+        case -ENOENT:
+        case -ESHUTDOWN:
+                /* this urb is terminated, clean up */
+                dbg("%s - urb shutting down with status: %d", __func__, urb->status);
+                return;
+        default:
+                dbg("%s - nonzero urb status received: %d", __func__, urb->status);
+                goto exit;
+        }
+
+        if (count < 6) {
+                dbg("%s - int packet too short", __func__);
+                goto exit;
+        }
+
+       if (!memcmp(up_int, instance->int_data, 6)) {
+               del_timer(&instance->poll_timer);
+               printk(KERN_NOTICE "DSL line goes up\n");
+       } else if (!memcmp(down_int, instance->int_data, 6)) {
+               del_timer(&instance->poll_timer);
+               printk(KERN_NOTICE "DSL line goes down\n");
+       } else {
+               int i;
+
+               printk(KERN_DEBUG "Unknown interrupt packet of %d bytes:", count);
+               for (i=0; i < count; i++)
+                       printk(" %02x", instance->int_data[i]);
+               printk("\n");
+       }
+       schedule_work(&instance->poll_work);
+
+ exit:
+       rmb();
+       if (!instance->int_urb)
+               return;
+
+       ret = usb_submit_urb (urb, GFP_ATOMIC);
+       if (ret)
+               err ("%s - usb_submit_urb failed with result %d",
+                    __func__, ret);
+}
+
+static int udsl_get_status(struct udsl_instance_data *instance, unsigned char *buf)
+{
+       struct usb_device *dev = instance->usb_dev;
+       int ret;
+
+       memset(buf,0,TOTAL);
+
+       ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+                             0x12, 0xc0, 0x07, 0x00, buf+OFFSET_7, SIZE_7, 
CTRL_TIMEOUT);
+       if (ret<0) {
+               dbg("MSG 7 failed");
+               return(ret);
+       }
+
+       ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+                             0x12, 0xc0, 0x0b, 0x00, buf+OFFSET_b, SIZE_b, 
CTRL_TIMEOUT);
+       if (ret<0) {
+               dbg("MSG B failed");
+               return(ret);
+       }
+
+       ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+                             0x12, 0xc0, 0x0d, 0x00, buf+OFFSET_d, SIZE_d, 
CTRL_TIMEOUT);
+       if (ret<0) {
+               dbg("MSG D failed");
+               return(ret);
+       }
+
+       ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+                             0x01, 0xc0, 0x0e, 0x00, buf+OFFSET_e, SIZE_e, 
CTRL_TIMEOUT);
+       if (ret<0) {
+               dbg("MSG E failed");
+               return(ret);
+       }
+
+       ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+                             0x01, 0xc0, 0x0f, 0x00, buf+OFFSET_f, SIZE_f, 
CTRL_TIMEOUT);
+       if (ret<0) {
+               dbg("MSG F failed");
+               return(ret);
+       }
+
+       return 0;
+}
+
+static void udsl_poll_status(struct udsl_instance_data *instance)
+{
+       unsigned char buf[TOTAL];
+       int ret;
+
+       ret = udsl_get_status(instance, buf);
+       if (ret) {
+               printk(KERN_WARNING "SpeedTouch: Error %d fetching device status\n", 
ret);
+               return;
+       }
+
+       dbg("Line state %02x", buf[OFFSET_7]);
+
+       switch (buf[OFFSET_7]) {
+       case 0:
+               if (instance->atm_dev->signal != ATM_PHY_SIG_LOST) {
+                       instance->atm_dev->signal = ATM_PHY_SIG_LOST;
+                       printk(KERN_NOTICE "ADSL line is down\n");
+               }
+               break;
+
+       case 0x08:
+               if (instance->atm_dev->signal != ATM_PHY_SIG_UNKNOWN) {
+                       instance->atm_dev->signal = ATM_PHY_SIG_UNKNOWN;
+                       printk(KERN_NOTICE "ADSL line is blocked?\n");
+               }
+               break;
+
+       case 0x10:
+               if (instance->atm_dev->signal != ATM_PHY_SIG_LOST) {
+                       instance->atm_dev->signal = ATM_PHY_SIG_LOST;
+                       printk(KERN_NOTICE "ADSL line is synchronising\n");
+               }
+               break;
+
+       case 0x20:
+               if (instance->atm_dev->signal != ATM_PHY_SIG_FOUND) {
+                       int down_speed = buf[OFFSET_b] | (buf[OFFSET_b+1]<<8)
+                               | (buf[OFFSET_b+2]<<16) | (buf[OFFSET_b+3]<<24);
+                       int 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;
+                       } 
+                       instance->atm_dev->link_rate = down_speed * 1000 / 424;
+                       instance->atm_dev->signal = ATM_PHY_SIG_FOUND;
+
+                       printk(KERN_NOTICE "ADSL line is up (%d kb/s down | %d kb/s 
up)\n", 
+                              down_speed, up_speed);
+               }
+               break;
+
+       default:
+               if (instance->atm_dev->signal != ATM_PHY_SIG_UNKNOWN) {
+                       instance->atm_dev->signal = ATM_PHY_SIG_UNKNOWN;
+                       printk(KERN_NOTICE "Unknown line state %02x\n", buf[OFFSET_7]);
+               }
+               break;
+       }
+}
+
+static void udsl_timer_poll(struct udsl_instance_data *instance)
+{
+       schedule_work(&instance->poll_work);
+       mod_timer(&instance->poll_timer, jiffies + (5*HZ));
+}
+
+static void udsl_firmware_stage2 (const struct firmware *fw, void *context)
+{
+       unsigned char *buffer;
+       struct udsl_instance_data *instance = context;
+       struct usb_interface *intf;
+       int actual_length, ret;
+       int pg;
+
+       dbg ("udsl_firmware_stage2");
+
+       ret = -1;
+
+       if (!instance) {
+               dbg ("udsl_firmware_stage2: NULL instance!");
+               return; /* deep doggy do */
+       }
+
+       if (!(intf = usb_ifnum_to_if (instance->usb_dev, 2))) {
+               dbg ("udsl_firmware_stage2: interface not found!");
+               goto finish;
+       }
+
+       if (!fw) {
+               dbg ("udsl_firmware_stage2: no firmware!");
+               goto finish;
+       }
+
+       if (!(buffer = kmalloc (0x1000, GFP_KERNEL))) {
+               dbg ("udsl_firmware_stage2: no memory for buffer!");
+               goto finish;
+       }
+
+       /* URBs 12 to 139 - USB led blinking green, ADSL led off */
+       for (pg = 0; pg * 0x1000 < fw->size; pg++) {
+               int thislen = min_t(int, 0x1000, fw->size - (pg*0x1000));
+               memcpy(buffer, fw->data + (pg*0x1000), thislen);
+
+               ret = usb_bulk_msg (instance->usb_dev,
+                                   usb_sndbulkpipe (instance->usb_dev, 
UDSL_ENDPOINT_FIRMWARE_OUT),
+                                   buffer,
+                                   thislen,
+                                   &actual_length,
+                                   DATA_TIMEOUT);
+
+               if (ret < 0) {
+                       dbg ("udsl_firmware_stage2: write to modem failed (%d)!", ret);
+                       goto out;
+               }
+       }
+
+       /* USB led static green, ADSL led static red */
+
+       /* URB 142 */
+       ret = usb_bulk_msg (instance->usb_dev,
+                           usb_rcvbulkpipe (instance->usb_dev, 
UDSL_ENDPOINT_FIRMWARE_IN),
+                           buffer,
+                           0x200,
+                           &actual_length,
+                           DATA_TIMEOUT);
+
+       if (ret < 0) {
+               dbg ("udsl_firmware_stage2: read from modem failed (%d)!", ret);
+               goto out;
+       }
+
+       /* success */
+
+       /* Delay to allow firmware to start up. We can do this here
+          because we're in our own kernel thread anyway. */
+       msleep(1000);
+
+       /* Enable software buffering, if requested */
+       if (sw_buffering)
+               udsl_set_swbuff(instance, 1);
+       
+       /* Magic spell; don't ask us what this does */
+       udsl_test_sequence(instance);
+
+       /* Start modem synchronisation */
+       if (udsl_start_synchro(instance))
+               dbg("udsl_start_synchro: failed\n");
+
+       udsl_got_firmware(instance, 1);
+       
+ out:
+       kfree (buffer);
+
+ finish:
+       if (ret) {
+               /* We don't release interface #2 if loading the firmware succeeded.
+                  This prevents the userspace modem_run tool from trying to load
+                  the firmware itself */
+               usb_driver_release_interface (&udsl_usb_driver, intf);
+               udsl_got_firmware(instance, 0);
+       }
+
+       udsl_got_firmware (instance, (ret < 0) ? 0 : 1);
+       udsl_put_instance (instance);
+}
+
+static void udsl_firmware_stage1 (const struct firmware *fw, void *context)
+{
+       unsigned char *buffer;
+       struct udsl_instance_data *instance = context;
+       struct usb_interface *intf;
+       int actual_length, ret;
+       const char *fwname;
+       int pg;
+
+       dbg ("udsl_firmware_stage1");
+
+       ret = -1;
 
        if (!instance) {
-               dbg ("udsl_atm_dev_close: NULL instance!");
+               dbg ("udsl_firmware_stage1: NULL instance!");
+               return; /* deep doggy do */
+       }
+
+       if (!(intf = usb_ifnum_to_if (instance->usb_dev, 2))) {
+               dbg ("udsl_firmware_stage1: interface not found!");
+               goto finish;
+       }
+
+       if (!(buffer = kmalloc (0x1000, GFP_KERNEL))) {
+               dbg ("udsl_firmware_stage1: no memory for buffer!");
+               goto fail;
+       }
+
+       if (!fw) {
+               dbg ("udsl_firmware_stage1: no firmware!");
+               goto fail;
+       }
+
+       /* URB 7 */
+       if (dl_512_first) { /* some modems need a read before writing the firmware */
+               ret = usb_bulk_msg (instance->usb_dev,
+                                   usb_rcvbulkpipe (instance->usb_dev, 
UDSL_ENDPOINT_FIRMWARE_IN),
+                                   buffer,
+                                   0x200,
+                                   &actual_length,
+                                   2 * HZ);
+
+               if (ret < 0 && ret != -ETIMEDOUT)
+                       dbg ("udsl_firmware_stage1: initial read from modem failed 
(%d)!", ret);
+       }
+
+       /* URB 8 : both leds are static green */
+       for (pg = 0; pg * 0x1000 < fw->size; pg++) {
+               int thislen = min_t(int, 0x1000, fw->size - (pg*0x1000));
+               memcpy(buffer, fw->data + (pg*0x1000), thislen);
+
+               ret = usb_bulk_msg (instance->usb_dev,
+                                   usb_sndbulkpipe (instance->usb_dev, 
UDSL_ENDPOINT_FIRMWARE_OUT),
+                                   buffer,
+                                   thislen,
+                                   &actual_length,
+                                   DATA_TIMEOUT);
+
+               if (ret < 0) {
+                       dbg ("udsl_firmware_stage1: write to modem failed (%d)!", ret);
+                       goto fail;
+               }
+       }
+
+       /* USB led blinking green, ADSL led off */
+
+       /* URB 11 */
+       ret = usb_bulk_msg (instance->usb_dev,
+                           usb_rcvbulkpipe (instance->usb_dev, 
UDSL_ENDPOINT_FIRMWARE_IN),
+                           buffer,
+                           0x200,
+                           &actual_length,
+                           DATA_TIMEOUT);
+
+       if (ret < 0) {
+               dbg ("udsl_firmware_stage1: read from modem failed (%d)!", ret);
+               goto fail;
+       }
+
+
+       if (instance->revision == 4)
+               fwname = "speedtch_fw2_rev4";
+       else
+               fwname = "speedtch_fw2_rev123";
+
+       ret = request_firmware_nowait (THIS_MODULE,
+                                      fwname,
+                                      &instance->usb_dev->dev,
+                                      instance,
+                                      udsl_firmware_stage2);
+
+       if (ret < 0) {
+               dbg ("udsl_firmware_stage1: request_firmware_nowait failed (%d)!", 
ret);
+               goto fail;
+       }
+
+       /* success */
+       kfree (buffer);
+       return;
+
+fail:
+       kfree (buffer);
+       usb_driver_release_interface (&udsl_usb_driver, intf);
+finish:
+       udsl_got_firmware (instance, (ret < 0) ? 0 : 1);
+       udsl_put_instance (instance);
+}
+
+#endif /* CONFIG_FW_LOADER */
+
+static void udsl_firmware_start (struct udsl_instance_data *instance)
+{
+#ifdef CONFIG_FW_LOADER
+       struct usb_interface *intf;
+       int ret;
+#endif
+
+       dbg ("udsl_firmware_start");
+
+       down (&instance->serialize); /* vs self, udsl_got_firmware */
+
+       if (instance->status >= UDSL_LOADING_FIRMWARE) {
+               up (&instance->serialize);
                return;
        }
 
-       dbg ("udsl_atm_dev_close: queue has %u elements", instance->sndqueue.qlen);
+       instance->status = UDSL_LOADING_FIRMWARE;
+       up (&instance->serialize);
+
+       udsl_get_instance (instance);
+
+#ifdef CONFIG_FW_LOADER
+       if (!(intf = usb_ifnum_to_if (instance->usb_dev, 2))) {
+               dbg ("udsl_firmware_start: interface not found!");
+               goto finish;
+       }
+
+        if ((ret = usb_driver_claim_interface (&udsl_usb_driver, intf, NULL)) < 0) {
+               dbg ("udsl_firmware_start: interface in use (%d)!", ret);
+               goto finish;
+       }
+
+       ret = request_firmware_nowait (THIS_MODULE,
+                                      "speedtch_fw1",
+                                      &instance->usb_dev->dev,
+                                      instance,
+                                      udsl_firmware_stage1);
+
+       if (ret < 0) {
+               dbg ("udsl_firmware_start: request_firmware_nowait failed (%d)!", ret);
+               goto fail;
+       }
+
+       return;
+
+fail:
+       usb_driver_release_interface (&udsl_usb_driver, intf);
+finish:
+#endif /* CONFIG_FW_LOADER */
+       udsl_got_firmware (instance, 0);
+       udsl_put_instance (instance);
+}
+
+static int udsl_firmware_wait (struct udsl_instance_data *instance)
+{
+       udsl_firmware_start (instance);
+
+       if (wait_event_interruptible (instance->firmware_waiters, instance->status != 
UDSL_LOADING_FIRMWARE) < 0)
+               return -ERESTARTSYS;
+
+       return (instance->status == UDSL_LOADED_FIRMWARE) ? 0 : -EAGAIN;
+}
+
+
+/**********
+**  ATM  **
+**********/
+
+static void udsl_atm_dev_close (struct atm_dev *dev)
+{
+       struct udsl_instance_data *instance = dev->dev_data;
 
-       tasklet_kill (&instance->receive_tasklet);
-       tasklet_kill (&instance->send_tasklet);
-       kfree (instance);
        dev->dev_data = NULL;
+       udsl_put_instance (instance);
 }
 
 static int udsl_atm_proc_read (struct atm_dev *atm_dev, loff_t *pos, char *page)
@@ -865,13 +1501,16 @@
                        break;
                }
 
-               if (instance->usb_dev) {
-                       if (!instance->firmware_loaded)
-                               strcat (page, ", no firmware\n");
-                       else
-                               strcat (page, ", firmware loaded\n");
-               } else
+               if (instance->usb_dev->state == USB_STATE_NOTATTACHED)
                        strcat (page, ", disconnected\n");
+               else {
+                       if (instance->status == UDSL_LOADED_FIRMWARE)
+                               strcat (page, ", firmware loaded\n");
+                       else if (instance->status == UDSL_LOADING_FIRMWARE)
+                               strcat (page, ", firmware loading\n");
+                       else
+                               strcat (page, ", no firmware\n");
+               }
 
                return strlen (page);
        }
@@ -886,10 +1525,11 @@
        unsigned int max_pdu;
        int vci = vcc->vci;
        short vpi = vcc->vpi;
+       int err;
 
        dbg ("udsl_atm_open: vpi %hd, vci %d", vpi, vci);
 
-       if (!instance || !instance->usb_dev) {
+       if (!instance) {
                dbg ("udsl_atm_open: NULL data!");
                return -ENODEV;
        }
@@ -900,9 +1540,9 @@
                return -EINVAL;
        }
 
-       if (!instance->firmware_loaded) {
-               dbg ("udsl_atm_open: firmware not loaded!");
-               return -EAGAIN;
+       if ((err = udsl_firmware_wait (instance)) < 0) {
+               dbg ("udsl_atm_open: firmware not loaded (%d)!", err);
+               return err;
        }
 
        down (&instance->serialize); /* vs self, udsl_atm_close */
@@ -1006,26 +1646,6 @@
 **  USB  **
 **********/
 
-static int udsl_set_alternate (struct udsl_instance_data *instance)
-{
-       down (&instance->serialize); /* vs self */
-       if (!instance->firmware_loaded) {
-               int ret;
-
-               if ((ret = usb_set_interface (instance->usb_dev, 1, 1)) < 0) {
-                       dbg ("udsl_set_alternate: usb_set_interface returned %d!", 
ret);
-                       up (&instance->serialize);
-                       return ret;
-               }
-               instance->firmware_loaded = 1;
-       }
-       up (&instance->serialize);
-
-       tasklet_schedule (&instance->receive_tasklet);
-
-       return 0;
-}
-
 static int udsl_usb_ioctl (struct usb_interface *intf, unsigned int code, void 
*user_data)
 {
        struct udsl_instance_data *instance = usb_get_intfdata (intf);
@@ -1040,7 +1660,8 @@
        switch (code) {
        case UDSL_IOCTL_LINE_UP:
                instance->atm_dev->signal = ATM_PHY_SIG_FOUND;
-               return udsl_set_alternate (instance);
+               udsl_got_firmware (instance, 1);
+               return (instance->status == UDSL_LOADED_FIRMWARE) ? 0 : -EIO;
        case UDSL_IOCTL_LINE_DOWN:
                instance->atm_dev->signal = ATM_PHY_SIG_LOST;
                return 0;
@@ -1055,8 +1676,9 @@
        int ifnum = intf->altsetting->desc.bInterfaceNumber;
        struct udsl_instance_data *instance;
        unsigned char mac_str [13];
-       int i, length;
+       int err, i, length;
        char *buf;
+       char buf7[SIZE_7];
 
        dbg ("udsl_usb_probe: trying device with vendor=0x%x, product=0x%x, ifnum %d",
             dev->descriptor.idVendor, dev->descriptor.idProduct, ifnum);
@@ -1076,12 +1698,23 @@
 
        memset (instance, 0, sizeof (struct udsl_instance_data));
 
+       init_timer(&instance->poll_timer);
+       instance->poll_timer.function = (void *)&udsl_timer_poll;
+       instance->poll_timer.data = (unsigned long)instance;
+
+       INIT_WORK(&instance->poll_work, (void *)udsl_poll_status, instance);
+
+       atomic_set (&instance->refcount, 2); /* one for USB, one for ATM */
+
        init_MUTEX (&instance->serialize);
 
        instance->usb_dev = dev;
 
        INIT_LIST_HEAD (&instance->vcc_list);
 
+       instance->status = UDSL_NO_FIRMWARE;
+       init_waitqueue_head (&instance->firmware_waiters);
+
        spin_lock_init (&instance->receive_lock);
        INIT_LIST_HEAD (&instance->spare_receivers);
        INIT_LIST_HEAD (&instance->filled_receive_buffers);
@@ -1098,6 +1731,33 @@
        tasklet_init (&instance->send_tasklet, udsl_process_send, (unsigned long) 
instance);
        INIT_LIST_HEAD (&instance->filled_send_buffers);
 
+       switch (dev->descriptor.bcdDevice) {
+       case 0x0000:
+               instance->revision = 0;
+               break;
+       case 0x0200:
+               instance->revision = 2;
+               break;
+       case 0x0400:
+               instance->revision = 4;
+               break;
+       default:
+               instance->revision = 2;
+               printk(KERN_INFO "Unexpected SpeedTouch revision %04x, treating as Rev 
0200.\n",
+                      dev->descriptor.bcdDevice);
+               break;
+       }
+
+       if ((err = usb_set_interface (dev, 0, 0)) < 0)
+               return err;
+
+       if ((err = usb_set_interface (dev, 1, instance->revision?2:1)) < 0)
+               return err;
+
+       if ((err = usb_set_interface (dev, 2, 0)) < 0)
+               return err;
+
+
        /* receive init */
        for (i = 0; i < num_rcv_urbs; i++) {
                struct udsl_receiver *rcv = &(instance->receivers [i]);
@@ -1194,8 +1854,22 @@
        wmb ();
        instance->atm_dev->dev_data = instance;
 
+       usb_get_dev (dev);
+
        usb_set_intfdata (intf, instance);
 
+       /* First check whether the modem already seems to be alive */
+       err = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+                             0x12, 0xc0, 0x07, 0x00, buf7, SIZE_7, HZ/2);
+
+       if (err == SIZE_7) {
+               dbg("firmware appears to be already loaded");
+               udsl_got_firmware(instance, 1);
+               udsl_poll_status(instance);
+       } else {
+               udsl_firmware_start (instance);
+       }
+
        return 0;
 
 fail:
@@ -1223,9 +1897,9 @@
        unsigned int count;
        int result, i;
 
-       dbg ("udsl_usb_disconnect entered");
+/* QQ - need to handle disconnects on interface 2! */
 
-       usb_set_intfdata (intf, NULL);
+       dbg ("udsl_usb_disconnect entered");
 
        if (!instance) {
                dbg ("udsl_usb_disconnect: NULL instance!");
@@ -1292,6 +1966,19 @@
                schedule ();
        } while (1);
 
+       if (instance->int_urb) {
+               struct urb *int_urb = instance->int_urb;
+               instance->int_urb = NULL;
+               wmb();
+               usb_unlink_urb(int_urb);
+               usb_free_urb(int_urb);
+       }
+
+       instance->int_data[0] = 1;
+       del_timer_sync(&instance->poll_timer);
+       wmb();
+       flush_scheduled_work();
+
        /* no need to take the spinlock */
        INIT_LIST_HEAD (&instance->spare_senders);
        INIT_LIST_HEAD (&instance->spare_send_buffers);
@@ -1305,11 +1992,12 @@
        for (i = 0; i < num_snd_bufs; i++)
                kfree (instance->send_buffers [i].base);
 
-       wmb ();
-       instance->usb_dev = NULL;
-
        /* ATM finalize */
-       shutdown_atm_dev (instance->atm_dev); /* frees instance, kills tasklets */
+       shutdown_atm_dev (instance->atm_dev);
+
+       /* clean up */
+       usb_set_intfdata (intf, NULL);
+       udsl_put_instance (instance);
 }
 
 


-- 
dwmw2



-------------------------------------------------------
This SF.net email is sponsored by: IT Product Guide on ITManagersJournal
Use IT products in your business? Tell us what you think of them. Give us
Your Opinions, Get Free ThinkGeek Gift Certificates! Click to find out more
http://productguide.itmanagersjournal.com/guidepromo.tmpl
_______________________________________________
[EMAIL PROTECTED]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel

Reply via email to