Re: [PATCH] USB interrupt support for radio-si470x FM radio driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello, Edouard

(add Tobias and Douglas on c/c)

On Tue, Jun 16, 2009 at 5:11 PM, Edouard Lafargue<edouard@xxxxxxxxxxxxx> wrote:
> Please find below my attempt at removing 'manual' polling from the
> radio-si470x USB FM radio driver, by moving to URB interrupt callback
> handlers.
>
> This code significantly improves RDS reception - radiotext in
> particular - and improves audio quality on my Silabs FM USB dongle, as
> lots of pops and clicks have now disappeared. Your mileage may vary.
> Issues remain when userspace programs do V4L ioctl calls to the
> dongle: this creates clicks in the audio.
>
> I am by no means a professional driver writer and the code below is
> most probably in need of a proper review by someone more experienced
> than me in writing Linux USB drivers. It does not raise issues on the
> machines where I have tested it, but this is hardly proof it's good.
> Any help will be much appreciated. It is only tested on the dongle I
> own, a Silabs "Reference Design" FM receiver dongle.
>
> Last: there are a few bits left commented out in my patched version,
> those are kept to help code review if someone wants to help, they
> should probably be removed from a final release.
>
> Thanks for your feedback!
>
> Signed-off-by: Edouard Lafargue <edouard@xxxxxxxxxxxxx>
>
> diff -Nur ../radio-si470x.c radio-si470x.c
> --- ../radio-si470x.c   2008-12-25 00:26:37.000000000 +0100
> +++ radio-si470x.c      2009-06-16 14:20:07.000000000 +0200
> @@ -97,9 +97,14 @@
>  *             - add support for KWorld USB FM Radio FM700
>  *             - blacklisted KWorld radio in hid-core.c and hid-ids.h
>  *
> + * 2009-06-16   Edouard Lafargue <edouard@xxxxxxxxxxxxx>
> + *             Version 1.0.9
> + *             - add support for interrupt mode for RDS endpoint, instead of polling.
> + *
>  * ToDo:
>  * - add firmware download/update support
> - * - RDS support: interrupt mode, instead of polling
> + * - RDS support: debug interrupt mode and use of URBs further!
> + * - Use URBs for all USB communications

Well, i'm not usb expert, but if such wrappers over URB like
usb_control_msg works fine, why to throw them away? I don't know but
may be there is big reason for that.

>  * - add LED status output (check if that's not already done in firmware)
>  */
>
> @@ -107,10 +112,10 @@
>  /* driver definitions */
>  #define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@xxxxxxx>"
>  #define DRIVER_NAME "radio-si470x"
> -#define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 8)
> +#define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 9)
>  #define DRIVER_CARD "Silicon Labs Si470x FM Radio Receiver"
>  #define DRIVER_DESC "USB radio driver for Si470x FM Radio Receivers"
> -#define DRIVER_VERSION "1.0.8"
> +#define DRIVER_VERSION "1.0.9"
>
>
>  /* kernel includes */
> @@ -207,13 +212,14 @@
>  MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
>
>  /* RDS poll frequency */
> -static unsigned int rds_poll_time = 40;
> +/* TODO: remove this since not used anymore as we now use the interrupt URB */
> +/* static unsigned int rds_poll_time = 40; */
>  /* 40 is used by the original USBRadio.exe */
>  /* 50 is used by radio-cadet */
>  /* 75 should be okay */
>  /* 80 is the usual RDS receive interval */
> -module_param(rds_poll_time, uint, 0644);
> -MODULE_PARM_DESC(rds_poll_time, "RDS poll time (ms): *40*");
> +/* module_param(rds_poll_time, uint, 0644); */
> +/* MODULE_PARM_DESC(rds_poll_time, "RDS poll time (ms): *40*"); */
>
>
>
> @@ -440,6 +446,12 @@
>        struct usb_interface *intf;
>        struct video_device *videodev;
>
> +       /* Interrupt endpoint handling */
> +       char                            *int_in_buffer;
> +       struct usb_endpoint_descriptor *int_in_endpoint;
> +       struct urb                      *int_in_urb;
> +       int                             int_in_running;
> +
>        /* driver management */
>        unsigned int users;
>        unsigned char disconnected;
> @@ -578,37 +590,6 @@
>  }
>
>
> -/*
> - * si470x_get_rds_registers - read rds registers
> - */
> -static int si470x_get_rds_registers(struct si470x_device *radio)
> -{
> -       unsigned char buf[RDS_REPORT_SIZE];
> -       int retval;
> -       int size;
> -       unsigned char regnr;
> -
> -       buf[0] = RDS_REPORT;
> -
> -       retval = usb_interrupt_msg(radio->usbdev,
> -               usb_rcvintpipe(radio->usbdev, 1),
> -               (void *) &buf, sizeof(buf), &size, usb_timeout);
> -       if (size != sizeof(buf))
> -               printk(KERN_WARNING DRIVER_NAME ": si470x_get_rds_registers: "
> -                       "return size differs: %d != %zu\n", size, sizeof(buf));
> -       if (retval < 0)
> -               printk(KERN_WARNING DRIVER_NAME ": si470x_get_rds_registers: "
> -                       "usb_interrupt_msg returned %d\n", retval);
> -
> -       if (retval >= 0)
> -               for (regnr = 0; regnr < RDS_REGISTER_NUM; regnr++)
> -                       radio->registers[STATUSRSSI + regnr] =
> -                               get_unaligned_be16(
> -                               &buf[regnr * RADIO_REGISTER_SIZE + 1]);
> -
> -       return (retval < 0) ? -EINVAL : 0;
> -}
> -
>
>  /*
>  * si470x_set_chan - set the channel
> @@ -882,105 +863,118 @@
>  **************************************************************************/
>
>  /*
> - * si470x_rds - rds processing function
> + * si470x_int_in_callback - rds callback and processing function
> + *
> + * TODO: do we need to use mutex locks in some sections?
>  */
> -static void si470x_rds(struct si470x_device *radio)
> +static void si470x_int_in_callback(struct urb *urb)
>  {
> +       struct si470x_device *radio = urb->context;
> +       unsigned char buf[RDS_REPORT_SIZE];
> +       int retval;
> +       unsigned char regnr;
>        unsigned char blocknum;
>        unsigned short bler; /* rds block errors */
>        unsigned short rds;
>        unsigned char tmpbuf[3];
>
> -       /* get rds blocks */
> -       if (si470x_get_rds_registers(radio) < 0)
> -               return;
> -       if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) {
> -               /* No RDS group ready */
> -               return;
> -       }
> -       if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSS) == 0) {
> -               /* RDS decoder not synchronized */
> -               return;
> -       }
> -
> -       /* copy all four RDS blocks to internal buffer */
> -       mutex_lock(&radio->lock);
> -       for (blocknum = 0; blocknum < 4; blocknum++) {
> -               switch (blocknum) {
> -               default:
> -                       bler = (radio->registers[STATUSRSSI] &
> -                                       STATUSRSSI_BLERA) >> 9;
> -                       rds = radio->registers[RDSA];
> -                       break;
> -               case 1:
> -                       bler = (radio->registers[READCHAN] &
> -                                       READCHAN_BLERB) >> 14;
> -                       rds = radio->registers[RDSB];
> -                       break;
> -               case 2:
> -                       bler = (radio->registers[READCHAN] &
> -                                       READCHAN_BLERC) >> 12;
> -                       rds = radio->registers[RDSC];
> -                       break;
> -               case 3:
> -                       bler = (radio->registers[READCHAN] &
> -                                       READCHAN_BLERD) >> 10;
> -                       rds = radio->registers[RDSD];
> -                       break;
> -               };
> -
> -               /* Fill the V4L2 RDS buffer */
> -               put_unaligned_le16(rds, &tmpbuf);
> -               tmpbuf[2] = blocknum;           /* offset name */
> -               tmpbuf[2] |= blocknum << 3;     /* received offset */
> -               if (bler > max_rds_errors)
> -                       tmpbuf[2] |= 0x80; /* uncorrectable errors */
> -               else if (bler > 0)
> -                       tmpbuf[2] |= 0x40; /* corrected error(s) */
> -
> -               /* copy RDS block to internal buffer */
> -               memcpy(&radio->buffer[radio->wr_index], &tmpbuf, 3);
> -               radio->wr_index += 3;
> -
> -               /* wrap write pointer */
> -               if (radio->wr_index >= radio->buf_size)
> -                       radio->wr_index = 0;
> -
> -               /* check for overflow */
> -               if (radio->wr_index == radio->rd_index) {
> -                       /* increment and wrap read pointer */
> -                       radio->rd_index += 3;
> -                       if (radio->rd_index >= radio->buf_size)
> -                               radio->rd_index = 0;
> +       if (urb->status) {
> +               if (urb->status == -ENOENT ||
> +                               urb->status == -ECONNRESET ||
> +                               urb->status == -ESHUTDOWN) {
> +                       return;
> +               } else {
> +                       printk(KERN_WARNING DRIVER_NAME ": non-zero urb status (%d)\n",
> urb->status);
> +                       goto resubmit; /* Maybe we can recover. */
>                }
>        }
> -       mutex_unlock(&radio->lock);
> -
> -       /* wake up read queue */
> -       if (radio->wr_index != radio->rd_index)
> -               wake_up_interruptible(&radio->read_queue);
> -}
> -
> -
> -/*
> - * si470x_work - rds work function
> - */
> -static void si470x_work(struct work_struct *work)
> -{
> -       struct si470x_device *radio = container_of(work, struct si470x_device,
> -               work.work);
>
>        /* safety checks */
>        if (radio->disconnected)
> -               return;
> +               goto resubmit;

Hmmm, if device is disconnected you goto to resubmit urb. Is it okay?

>        if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
> -               return;
> +               goto resubmit;
>
> -       si470x_rds(radio);
> -       schedule_delayed_work(&radio->work, msecs_to_jiffies(rds_poll_time));
> -}
> +       if (urb->actual_length > 0) {
> +               /* Update RDS registers with URB data */
> +               buf[0] = RDS_REPORT;
> +               for (regnr = 0; regnr < RDS_REGISTER_NUM; regnr++)
> +                       radio->registers[STATUSRSSI + regnr] =
> +                               get_unaligned_be16(
> +                               &radio->int_in_buffer[regnr * RADIO_REGISTER_SIZE + 1]);
> +               /* get rds blocks */
> +               if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) {
> +                       /* No RDS group ready */
> +                       goto resubmit;
> +               }
> +               if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSS) == 0) {
> +                       /* RDS decoder not synchronized */
> +                       goto resubmit;
> +               }
> +               for (blocknum = 0; blocknum < 4; blocknum++) {
> +                       switch (blocknum) {
> +                       default:
> +                               bler = (radio->registers[STATUSRSSI] &
> +                                               STATUSRSSI_BLERA) >> 9;
> +                               rds = radio->registers[RDSA];
> +                               break;
> +                       case 1:
> +                               bler = (radio->registers[READCHAN] &
> +                                               READCHAN_BLERB) >> 14;
> +                               rds = radio->registers[RDSB];
> +                               break;
> +                       case 2:
> +                               bler = (radio->registers[READCHAN] &
> +                                               READCHAN_BLERC) >> 12;
> +                               rds = radio->registers[RDSC];
> +                               break;
> +                       case 3:
> +                               bler = (radio->registers[READCHAN] &
> +                                               READCHAN_BLERD) >> 10;
> +                               rds = radio->registers[RDSD];
> +                               break;
> +                       };
> +
> +                       /* Fill the V4L2 RDS buffer */
> +                       put_unaligned_le16(rds, &tmpbuf);
> +                       tmpbuf[2] = blocknum;           /* offset name */
> +                       tmpbuf[2] |= blocknum << 3;     /* received offset */
> +                       if (bler > max_rds_errors)
> +                               tmpbuf[2] |= 0x80; /* uncorrectable errors */
> +                       else if (bler > 0)
> +                               tmpbuf[2] |= 0x40; /* corrected error(s) */
> +
> +                       /* copy RDS block to internal buffer */
> +                       memcpy(&radio->buffer[radio->wr_index], &tmpbuf, 3);
> +                       radio->wr_index += 3;
> +
> +                       /* wrap write pointer */
> +                       if (radio->wr_index >= radio->buf_size)
> +                               radio->wr_index = 0;
> +
> +                       /* check for overflow */
> +                       if (radio->wr_index == radio->rd_index) {
> +                               /* increment and wrap read pointer */
> +                               radio->rd_index += 3;
> +                               if (radio->rd_index >= radio->buf_size)
> +                                       radio->rd_index = 0;
> +                       }
> +               }
> +               if (radio->wr_index != radio->rd_index)
> +                       wake_up_interruptible(&radio->read_queue);
> +       }
>
> +resubmit:
> +       /* Resubmit if we're still running. */
> +       if (radio->int_in_running && radio->usbdev) {
> +               retval = usb_submit_urb(radio->int_in_urb, GFP_ATOMIC);
> +               if (retval) {
> +                       printk(KERN_WARNING DRIVER_NAME ": resubmitting urb failed (%d)", retval);
> +                       radio->int_in_running = 0;
> +               }
> +       }
>
> +}
>
>  /**************************************************************************
>  * File Operations Interface
> @@ -999,8 +993,6 @@
>        /* switch on rds reception */
>        if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) {
>                si470x_rds_on(radio);
> -               schedule_delayed_work(&radio->work,
> -                       msecs_to_jiffies(rds_poll_time));
>        }
>
>        /* block if no new data available */
> @@ -1059,8 +1051,6 @@
>        /* switch on rds reception */
>        if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) {
>                si470x_rds_on(radio);
> -               schedule_delayed_work(&radio->work,
> -                       msecs_to_jiffies(rds_poll_time));
>        }
>
>        poll_wait(file, &radio->read_queue, pts);
> @@ -1090,10 +1080,34 @@
>                goto done;
>        }
>
> +       printk(KERN_INFO DRIVER_NAME ": Opened radio (users now: %i)\n",radio->users);
> +
>        if (radio->users == 1) {
>                retval = si470x_start(radio);
> -               if (retval < 0)
> +               if (retval < 0) {
> +                       usb_autopm_put_interface(radio->intf);
> +                       goto done;
> +               }
> +
> +               /* Initialize interrupt URB. */
> +               usb_fill_int_urb(radio->int_in_urb, radio->usbdev,
> +                               usb_rcvintpipe(radio->usbdev, radio->int_in_endpoint->bEndpointAddress),
> +                               radio->int_in_buffer,
> +                               le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize),
> +                               si470x_int_in_callback,
> +                               radio,
> +                               radio->int_in_endpoint->bInterval);
> +
> +               radio->int_in_running = 1;
> +               mb();
> +
> +               retval = usb_submit_urb(radio->int_in_urb, GFP_KERNEL);
> +               if (retval) {
> +                       printk(KERN_INFO DRIVER_NAME ": submitting int urb failed (%d)\n", retval);
> +                       radio->int_in_running = 0;
>                        usb_autopm_put_interface(radio->intf);
> +                       goto done;
> +               }
>        }
>
>  done:
> @@ -1118,6 +1132,7 @@
>
>        mutex_lock(&radio->disconnect_lock);
>        radio->users--;
> +       printk(KERN_INFO DRIVER_NAME ": Closed radio (remaining
> users:%i)\n",radio->users);
>        if (radio->users == 0) {
>                if (radio->disconnected) {
>                        video_unregister_device(radio->videodev);
> @@ -1126,8 +1141,12 @@
>                        goto unlock;
>                }
>
> -               /* stop rds reception */
> -               cancel_delayed_work_sync(&radio->work);
> +               /* Shutdown Interrupt handler */
> +               if (radio->int_in_running) {
> +                       radio->int_in_running = 0;
> +               if (radio->int_in_urb)
> +                       usb_kill_urb(radio->int_in_urb);
> +               }
>
>                /* cancel read processes */
>                wake_up_interruptible(&radio->read_queue);
> @@ -1580,7 +1599,9 @@
>                const struct usb_device_id *id)
>  {
>        struct si470x_device *radio;
> -       int retval = 0;
> +       struct usb_host_interface *iface_desc;
> +       struct usb_endpoint_descriptor *endpoint;
> +       int i,int_end_size,retval = 0;
>
>        /* private data allocation and initialization */
>        radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL);
> @@ -1595,6 +1616,39 @@
>        mutex_init(&radio->disconnect_lock);
>        mutex_init(&radio->lock);
>
> +       iface_desc = intf->cur_altsetting;
> +
> +       /* Set up interrupt endpoint information. */
> +       for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
> +               endpoint = &iface_desc->endpoint[i].desc;
> +
> +               if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN)
> +                               && ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
> +                                       USB_ENDPOINT_XFER_INT))
> +                       radio->int_in_endpoint = endpoint;
> +
> +       }
> +       if (! radio->int_in_endpoint) {
> +               printk(KERN_INFO DRIVER_NAME ": could not find interrupt in endpoint\n");
> +               goto err_radio;

If i'm not wrong you don't set retval variable which used like error
indicator here. I looked into code above and retval will be 0 here, so
this function returns 0 (success?) after this goto.
Is this correct?

> +       }
> +
> +       int_end_size = le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize);
> +
> +       radio->int_in_buffer = kmalloc(int_end_size, GFP_KERNEL);
> +       if (! radio->int_in_buffer) {
> +               printk(KERN_INFO DRIVER_NAME "could not allocate int_in_buffer");
> +               retval = -ENOMEM;
> +               goto err_radio;
> +       }
> +
> +       radio->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
> +       if (! radio->int_in_urb) {
> +               printk(KERN_INFO DRIVER_NAME "could not allocate int_in_urb");
> +               retval = -ENOMEM;
> +               goto err_radio;

Hmm, you goes to err_radio, which does only kfree(radio) and how about
kfree(radio->int_in_buffer) ? Don't you need to update cleanup
procedure in this function?
Probably you need to add one more label or two labels.
Please, make clean up procedure here more carefull.

> +       }
> +
>        /* video device allocation and initialization */
>        radio->videodev = video_device_alloc();
>        if (!radio->videodev) {
> @@ -1645,9 +1699,6 @@
>        radio->rd_index = 0;
>        init_waitqueue_head(&radio->read_queue);
>
> -       /* prepare rds work function */
> -       INIT_DELAYED_WORK(&radio->work, si470x_work);
> -
>        /* register video device */
>        retval = video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr);
>        if (retval) {
> @@ -1674,12 +1725,10 @@
>  static int si470x_usb_driver_suspend(struct usb_interface *intf,
>                pm_message_t message)
>  {
> -       struct si470x_device *radio = usb_get_intfdata(intf);
> -
> +/*     struct si470x_device *radio = usb_get_intfdata(intf);
> + */
>        printk(KERN_INFO DRIVER_NAME ": suspending now...\n");
>
> -       cancel_delayed_work_sync(&radio->work);
> -
>        return 0;
>  }
>
> @@ -1689,16 +1738,17 @@
>  */
>  static int si470x_usb_driver_resume(struct usb_interface *intf)
>  {
> -       struct si470x_device *radio = usb_get_intfdata(intf);
> -
> +/*     struct si470x_device *radio = usb_get_intfdata(intf);
> + */
>        printk(KERN_INFO DRIVER_NAME ": resuming now...\n");
>
> -       mutex_lock(&radio->lock);
> +/*     mutex_lock(&radio->lock);
>        if (radio->users && radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS)
>                schedule_delayed_work(&radio->work,
>                        msecs_to_jiffies(rds_poll_time));
> -       mutex_unlock(&radio->lock);
>
> +       mutex_unlock(&radio->lock);
> +*/
>        return 0;
>  }
>
> @@ -1712,13 +1762,19 @@
>
>        mutex_lock(&radio->disconnect_lock);
>        radio->disconnected = 1;
> -       cancel_delayed_work_sync(&radio->work);
>        usb_set_intfdata(intf, NULL);
> -       if (radio->users == 0) {
> -               video_unregister_device(radio->videodev);
> -               kfree(radio->buffer);
> -               kfree(radio);
> -       }
> +/*     if (radio->users == 0) { */
> +
> +       /* Free data structures. */
> +       if (radio->int_in_urb)
> +               usb_free_urb(radio->int_in_urb);
> +
> +       kfree(radio->int_in_buffer);
> +       video_unregister_device(radio->videodev);
> +       kfree(radio->buffer);
> +       kfree(radio);
> +/*     } */

It's harder to see changes in driver if you use comments like /* and */
Please, don't use them. If you want to remove line - just remove it.

Well, i think Douglas and Tobias can test this patch, and it's really
good if audio quality will be improved with this patch.

-- 
Best regards, Klimov Alexey
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux