kernel-fxtec-pro1x/drivers/media/video/cpia_usb.c
David Howells 7d12e780e0 IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.

The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around.  On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).

Where appropriate, an arch may override the generic storage facility and do
something different with the variable.  On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.

Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions.  Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller.  A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.

I've build this code with allyesconfig for x86_64 and i386.  I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.

This will affect all archs.  Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:

	struct pt_regs *old_regs = set_irq_regs(regs);

And put the old one back at the end:

	set_irq_regs(old_regs);

Don't pass regs through to generic_handle_irq() or __do_IRQ().

In timer_interrupt(), this sort of change will be necessary:

	-	update_process_times(user_mode(regs));
	-	profile_tick(CPU_PROFILING, regs);
	+	update_process_times(user_mode(get_irq_regs()));
	+	profile_tick(CPU_PROFILING);

I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().

Some notes on the interrupt handling in the drivers:

 (*) input_dev() is now gone entirely.  The regs pointer is no longer stored in
     the input_dev struct.

 (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking.  It does
     something different depending on whether it's been supplied with a regs
     pointer or not.

 (*) Various IRQ handler function pointers have been moved to type
     irq_handler_t.

Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:10:12 +01:00

643 lines
16 KiB
C

/*
* cpia_usb CPiA USB driver
*
* Supports CPiA based parallel port Video Camera's.
*
* Copyright (C) 1999 Jochen Scharrlach <Jochen.Scharrlach@schwaben.de>
* Copyright (C) 1999, 2000 Johannes Erdfelt <johannes@erdfelt.com>
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* define _CPIA_DEBUG_ for verbose debug output (see cpia.h) */
/* #define _CPIA_DEBUG_ 1 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/usb.h>
#include "cpia.h"
#define USB_REQ_CPIA_GRAB_FRAME 0xC1
#define USB_REQ_CPIA_UPLOAD_FRAME 0xC2
#define WAIT_FOR_NEXT_FRAME 0
#define FORCE_FRAME_UPLOAD 1
#define FRAMES_PER_DESC 10
#define FRAME_SIZE_PER_DESC 960 /* Shouldn't be hardcoded */
#define CPIA_NUMSBUF 2
#define STREAM_BUF_SIZE (PAGE_SIZE * 4)
#define SCRATCH_BUF_SIZE (STREAM_BUF_SIZE * 2)
struct cpia_sbuf {
char *data;
struct urb *urb;
};
#define FRAMEBUF_LEN (CPIA_MAX_FRAME_SIZE+100)
enum framebuf_status {
FRAME_EMPTY,
FRAME_READING,
FRAME_READY,
FRAME_ERROR,
};
struct framebuf {
int length;
enum framebuf_status status;
u8 data[FRAMEBUF_LEN];
struct framebuf *next;
};
struct usb_cpia {
/* Device structure */
struct usb_device *dev;
unsigned char iface;
wait_queue_head_t wq_stream;
int cursbuf; /* Current receiving sbuf */
struct cpia_sbuf sbuf[CPIA_NUMSBUF]; /* Double buffering */
int streaming;
int open;
int present;
struct framebuf *buffers[3];
struct framebuf *curbuff, *workbuff;
};
static int cpia_usb_open(void *privdata);
static int cpia_usb_registerCallback(void *privdata, void (*cb) (void *cbdata),
void *cbdata);
static int cpia_usb_transferCmd(void *privdata, u8 *command, u8 *data);
static int cpia_usb_streamStart(void *privdata);
static int cpia_usb_streamStop(void *privdata);
static int cpia_usb_streamRead(void *privdata, u8 *frame, int noblock);
static int cpia_usb_close(void *privdata);
#define ABOUT "USB driver for Vision CPiA based cameras"
static struct cpia_camera_ops cpia_usb_ops = {
cpia_usb_open,
cpia_usb_registerCallback,
cpia_usb_transferCmd,
cpia_usb_streamStart,
cpia_usb_streamStop,
cpia_usb_streamRead,
cpia_usb_close,
0,
THIS_MODULE
};
static LIST_HEAD(cam_list);
static spinlock_t cam_list_lock_usb;
static void cpia_usb_complete(struct urb *urb)
{
int i;
char *cdata;
struct usb_cpia *ucpia;
if (!urb || !urb->context)
return;
ucpia = (struct usb_cpia *) urb->context;
if (!ucpia->dev || !ucpia->streaming || !ucpia->present || !ucpia->open)
return;
if (ucpia->workbuff->status == FRAME_EMPTY) {
ucpia->workbuff->status = FRAME_READING;
ucpia->workbuff->length = 0;
}
for (i = 0; i < urb->number_of_packets; i++) {
int n = urb->iso_frame_desc[i].actual_length;
int st = urb->iso_frame_desc[i].status;
cdata = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
if (st)
printk(KERN_DEBUG "cpia data error: [%d] len=%d, status=%X\n", i, n, st);
if (FRAMEBUF_LEN < ucpia->workbuff->length + n) {
printk(KERN_DEBUG "cpia: scratch buf overflow!scr_len: %d, n: %d\n", ucpia->workbuff->length, n);
return;
}
if (n) {
if ((ucpia->workbuff->length > 0) ||
(0x19 == cdata[0] && 0x68 == cdata[1])) {
memcpy(ucpia->workbuff->data + ucpia->workbuff->length, cdata, n);
ucpia->workbuff->length += n;
} else
DBG("Ignoring packet!\n");
} else {
if (ucpia->workbuff->length > 4 &&
0xff == ucpia->workbuff->data[ucpia->workbuff->length-1] &&
0xff == ucpia->workbuff->data[ucpia->workbuff->length-2] &&
0xff == ucpia->workbuff->data[ucpia->workbuff->length-3] &&
0xff == ucpia->workbuff->data[ucpia->workbuff->length-4]) {
ucpia->workbuff->status = FRAME_READY;
ucpia->curbuff = ucpia->workbuff;
ucpia->workbuff = ucpia->workbuff->next;
ucpia->workbuff->status = FRAME_EMPTY;
ucpia->workbuff->length = 0;
if (waitqueue_active(&ucpia->wq_stream))
wake_up_interruptible(&ucpia->wq_stream);
}
}
}
/* resubmit */
urb->dev = ucpia->dev;
if ((i = usb_submit_urb(urb, GFP_ATOMIC)) != 0)
printk(KERN_ERR "%s: usb_submit_urb ret %d\n", __FUNCTION__, i);
}
static int cpia_usb_open(void *privdata)
{
struct usb_cpia *ucpia = (struct usb_cpia *) privdata;
struct urb *urb;
int ret, retval = 0, fx, err;
if (!ucpia)
return -EINVAL;
ucpia->sbuf[0].data = kmalloc(FRAMES_PER_DESC * FRAME_SIZE_PER_DESC, GFP_KERNEL);
if (!ucpia->sbuf[0].data)
return -EINVAL;
ucpia->sbuf[1].data = kmalloc(FRAMES_PER_DESC * FRAME_SIZE_PER_DESC, GFP_KERNEL);
if (!ucpia->sbuf[1].data) {
retval = -EINVAL;
goto error_0;
}
ret = usb_set_interface(ucpia->dev, ucpia->iface, 3);
if (ret < 0) {
printk(KERN_ERR "cpia_usb_open: usb_set_interface error (ret = %d)\n", ret);
retval = -EBUSY;
goto error_1;
}
ucpia->buffers[0]->status = FRAME_EMPTY;
ucpia->buffers[0]->length = 0;
ucpia->buffers[1]->status = FRAME_EMPTY;
ucpia->buffers[1]->length = 0;
ucpia->buffers[2]->status = FRAME_EMPTY;
ucpia->buffers[2]->length = 0;
ucpia->curbuff = ucpia->buffers[0];
ucpia->workbuff = ucpia->buffers[1];
/* We double buffer the Iso lists, and also know the polling
* interval is every frame (1 == (1 << (bInterval -1))).
*/
urb = usb_alloc_urb(FRAMES_PER_DESC, GFP_KERNEL);
if (!urb) {
printk(KERN_ERR "cpia_init_isoc: usb_alloc_urb 0\n");
retval = -ENOMEM;
goto error_1;
}
ucpia->sbuf[0].urb = urb;
urb->dev = ucpia->dev;
urb->context = ucpia;
urb->pipe = usb_rcvisocpipe(ucpia->dev, 1);
urb->transfer_flags = URB_ISO_ASAP;
urb->transfer_buffer = ucpia->sbuf[0].data;
urb->complete = cpia_usb_complete;
urb->number_of_packets = FRAMES_PER_DESC;
urb->interval = 1;
urb->transfer_buffer_length = FRAME_SIZE_PER_DESC * FRAMES_PER_DESC;
for (fx = 0; fx < FRAMES_PER_DESC; fx++) {
urb->iso_frame_desc[fx].offset = FRAME_SIZE_PER_DESC * fx;
urb->iso_frame_desc[fx].length = FRAME_SIZE_PER_DESC;
}
urb = usb_alloc_urb(FRAMES_PER_DESC, GFP_KERNEL);
if (!urb) {
printk(KERN_ERR "cpia_init_isoc: usb_alloc_urb 1\n");
retval = -ENOMEM;
goto error_urb0;
}
ucpia->sbuf[1].urb = urb;
urb->dev = ucpia->dev;
urb->context = ucpia;
urb->pipe = usb_rcvisocpipe(ucpia->dev, 1);
urb->transfer_flags = URB_ISO_ASAP;
urb->transfer_buffer = ucpia->sbuf[1].data;
urb->complete = cpia_usb_complete;
urb->number_of_packets = FRAMES_PER_DESC;
urb->interval = 1;
urb->transfer_buffer_length = FRAME_SIZE_PER_DESC * FRAMES_PER_DESC;
for (fx = 0; fx < FRAMES_PER_DESC; fx++) {
urb->iso_frame_desc[fx].offset = FRAME_SIZE_PER_DESC * fx;
urb->iso_frame_desc[fx].length = FRAME_SIZE_PER_DESC;
}
/* queue the ISO urbs, and resubmit in the completion handler */
err = usb_submit_urb(ucpia->sbuf[0].urb, GFP_KERNEL);
if (err) {
printk(KERN_ERR "cpia_init_isoc: usb_submit_urb 0 ret %d\n",
err);
goto error_urb1;
}
err = usb_submit_urb(ucpia->sbuf[1].urb, GFP_KERNEL);
if (err) {
printk(KERN_ERR "cpia_init_isoc: usb_submit_urb 1 ret %d\n",
err);
goto error_urb1;
}
ucpia->streaming = 1;
ucpia->open = 1;
return 0;
error_urb1: /* free urb 1 */
usb_free_urb(ucpia->sbuf[1].urb);
ucpia->sbuf[1].urb = NULL;
error_urb0: /* free urb 0 */
usb_free_urb(ucpia->sbuf[0].urb);
ucpia->sbuf[0].urb = NULL;
error_1:
kfree (ucpia->sbuf[1].data);
ucpia->sbuf[1].data = NULL;
error_0:
kfree (ucpia->sbuf[0].data);
ucpia->sbuf[0].data = NULL;
return retval;
}
//
// convenience functions
//
/****************************************************************************
*
* WritePacket
*
***************************************************************************/
static int WritePacket(struct usb_device *udev, const u8 *packet, u8 *buf, size_t size)
{
if (!packet)
return -EINVAL;
return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
packet[1] + (packet[0] << 8),
USB_TYPE_VENDOR | USB_RECIP_DEVICE,
packet[2] + (packet[3] << 8),
packet[4] + (packet[5] << 8), buf, size, 1000);
}
/****************************************************************************
*
* ReadPacket
*
***************************************************************************/
static int ReadPacket(struct usb_device *udev, u8 *packet, u8 *buf, size_t size)
{
if (!packet || size <= 0)
return -EINVAL;
return usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
packet[1] + (packet[0] << 8),
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
packet[2] + (packet[3] << 8),
packet[4] + (packet[5] << 8), buf, size, 1000);
}
static int cpia_usb_transferCmd(void *privdata, u8 *command, u8 *data)
{
int err = 0;
int databytes;
struct usb_cpia *ucpia = (struct usb_cpia *)privdata;
struct usb_device *udev = ucpia->dev;
if (!udev) {
DBG("Internal driver error: udev is NULL\n");
return -EINVAL;
}
if (!command) {
DBG("Internal driver error: command is NULL\n");
return -EINVAL;
}
databytes = (((int)command[7])<<8) | command[6];
if (command[0] == DATA_IN) {
u8 buffer[8];
if (!data) {
DBG("Internal driver error: data is NULL\n");
return -EINVAL;
}
err = ReadPacket(udev, command, buffer, 8);
if (err < 0)
return err;
memcpy(data, buffer, databytes);
} else if(command[0] == DATA_OUT)
WritePacket(udev, command, data, databytes);
else {
DBG("Unexpected first byte of command: %x\n", command[0]);
err = -EINVAL;
}
return 0;
}
static int cpia_usb_registerCallback(void *privdata, void (*cb) (void *cbdata),
void *cbdata)
{
return -ENODEV;
}
static int cpia_usb_streamStart(void *privdata)
{
return -ENODEV;
}
static int cpia_usb_streamStop(void *privdata)
{
return -ENODEV;
}
static int cpia_usb_streamRead(void *privdata, u8 *frame, int noblock)
{
struct usb_cpia *ucpia = (struct usb_cpia *) privdata;
struct framebuf *mybuff;
if (!ucpia || !ucpia->present)
return -1;
if (ucpia->curbuff->status != FRAME_READY)
interruptible_sleep_on(&ucpia->wq_stream);
else
DBG("Frame already waiting!\n");
mybuff = ucpia->curbuff;
if (!mybuff)
return -1;
if (mybuff->status != FRAME_READY || mybuff->length < 4) {
DBG("Something went wrong!\n");
return -1;
}
memcpy(frame, mybuff->data, mybuff->length);
mybuff->status = FRAME_EMPTY;
/* DBG("read done, %d bytes, Header: %x/%x, Footer: %x%x%x%x\n", */
/* mybuff->length, frame[0], frame[1], */
/* frame[mybuff->length-4], frame[mybuff->length-3], */
/* frame[mybuff->length-2], frame[mybuff->length-1]); */
return mybuff->length;
}
static void cpia_usb_free_resources(struct usb_cpia *ucpia, int try)
{
if (!ucpia->streaming)
return;
ucpia->streaming = 0;
/* Set packet size to 0 */
if (try) {
int ret;
ret = usb_set_interface(ucpia->dev, ucpia->iface, 0);
if (ret < 0) {
printk(KERN_ERR "usb_set_interface error (ret = %d)\n", ret);
return;
}
}
/* Unschedule all of the iso td's */
if (ucpia->sbuf[1].urb) {
usb_kill_urb(ucpia->sbuf[1].urb);
usb_free_urb(ucpia->sbuf[1].urb);
ucpia->sbuf[1].urb = NULL;
}
kfree(ucpia->sbuf[1].data);
ucpia->sbuf[1].data = NULL;
if (ucpia->sbuf[0].urb) {
usb_kill_urb(ucpia->sbuf[0].urb);
usb_free_urb(ucpia->sbuf[0].urb);
ucpia->sbuf[0].urb = NULL;
}
kfree(ucpia->sbuf[0].data);
ucpia->sbuf[0].data = NULL;
}
static int cpia_usb_close(void *privdata)
{
struct usb_cpia *ucpia = (struct usb_cpia *) privdata;
if(!ucpia)
return -ENODEV;
ucpia->open = 0;
/* ucpia->present = 0 protects against trying to reset the
* alt setting if camera is physically disconnected while open */
cpia_usb_free_resources(ucpia, ucpia->present);
return 0;
}
/* Probing and initializing */
static int cpia_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_cpia *ucpia;
struct cam_data *cam;
int ret;
/* A multi-config CPiA camera? */
if (udev->descriptor.bNumConfigurations != 1)
return -ENODEV;
interface = intf->cur_altsetting;
printk(KERN_INFO "USB CPiA camera found\n");
ucpia = kzalloc(sizeof(*ucpia), GFP_KERNEL);
if (!ucpia) {
printk(KERN_ERR "couldn't kmalloc cpia struct\n");
return -ENOMEM;
}
ucpia->dev = udev;
ucpia->iface = interface->desc.bInterfaceNumber;
init_waitqueue_head(&ucpia->wq_stream);
ucpia->buffers[0] = vmalloc(sizeof(*ucpia->buffers[0]));
if (!ucpia->buffers[0]) {
printk(KERN_ERR "couldn't vmalloc frame buffer 0\n");
goto fail_alloc_0;
}
ucpia->buffers[1] = vmalloc(sizeof(*ucpia->buffers[1]));
if (!ucpia->buffers[1]) {
printk(KERN_ERR "couldn't vmalloc frame buffer 1\n");
goto fail_alloc_1;
}
ucpia->buffers[2] = vmalloc(sizeof(*ucpia->buffers[2]));
if (!ucpia->buffers[2]) {
printk(KERN_ERR "couldn't vmalloc frame buffer 2\n");
goto fail_alloc_2;
}
ucpia->buffers[0]->next = ucpia->buffers[1];
ucpia->buffers[1]->next = ucpia->buffers[2];
ucpia->buffers[2]->next = ucpia->buffers[0];
ret = usb_set_interface(udev, ucpia->iface, 0);
if (ret < 0) {
printk(KERN_ERR "cpia_probe: usb_set_interface error (ret = %d)\n", ret);
/* goto fail_all; */
}
/* Before register_camera, important */
ucpia->present = 1;
cam = cpia_register_camera(&cpia_usb_ops, ucpia);
if (!cam) {
LOG("failed to cpia_register_camera\n");
goto fail_all;
}
spin_lock( &cam_list_lock_usb );
list_add( &cam->cam_data_list, &cam_list );
spin_unlock( &cam_list_lock_usb );
usb_set_intfdata(intf, cam);
return 0;
fail_all:
vfree(ucpia->buffers[2]);
ucpia->buffers[2] = NULL;
fail_alloc_2:
vfree(ucpia->buffers[1]);
ucpia->buffers[1] = NULL;
fail_alloc_1:
vfree(ucpia->buffers[0]);
ucpia->buffers[0] = NULL;
fail_alloc_0:
kfree(ucpia);
return -EIO;
}
static void cpia_disconnect(struct usb_interface *intf);
static struct usb_device_id cpia_id_table [] = {
{ USB_DEVICE(0x0553, 0x0002) },
{ USB_DEVICE(0x0813, 0x0001) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, cpia_id_table);
MODULE_LICENSE("GPL");
static struct usb_driver cpia_driver = {
.name = "cpia",
.probe = cpia_probe,
.disconnect = cpia_disconnect,
.id_table = cpia_id_table,
};
static void cpia_disconnect(struct usb_interface *intf)
{
struct cam_data *cam = usb_get_intfdata(intf);
struct usb_cpia *ucpia;
struct usb_device *udev;
usb_set_intfdata(intf, NULL);
if (!cam)
return;
ucpia = (struct usb_cpia *) cam->lowlevel_data;
spin_lock( &cam_list_lock_usb );
list_del(&cam->cam_data_list);
spin_unlock( &cam_list_lock_usb );
ucpia->present = 0;
cpia_unregister_camera(cam);
if(ucpia->open)
cpia_usb_close(cam->lowlevel_data);
ucpia->curbuff->status = FRAME_ERROR;
if (waitqueue_active(&ucpia->wq_stream))
wake_up_interruptible(&ucpia->wq_stream);
udev = interface_to_usbdev(intf);
ucpia->curbuff = ucpia->workbuff = NULL;
vfree(ucpia->buffers[2]);
ucpia->buffers[2] = NULL;
vfree(ucpia->buffers[1]);
ucpia->buffers[1] = NULL;
vfree(ucpia->buffers[0]);
ucpia->buffers[0] = NULL;
cam->lowlevel_data = NULL;
kfree(ucpia);
}
static int __init usb_cpia_init(void)
{
printk(KERN_INFO "%s v%d.%d.%d\n",ABOUT,
CPIA_USB_MAJ_VER,CPIA_USB_MIN_VER,CPIA_USB_PATCH_VER);
spin_lock_init(&cam_list_lock_usb);
return usb_register(&cpia_driver);
}
static void __exit usb_cpia_cleanup(void)
{
usb_deregister(&cpia_driver);
}
module_init (usb_cpia_init);
module_exit (usb_cpia_cleanup);