/shark/trunk/drivers/usb/input/hid-input.c |
---|
0,0 → 1,641 |
/* |
* $Id: hid-input.c,v 1.1 2004-09-13 15:04:47 giacomo Exp $ |
* |
* Copyright (c) 2000-2001 Vojtech Pavlik |
* |
* USB HID to Linux Input mapping |
*/ |
/* |
* 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 |
* |
* Should you need to contact me, the author, you can do so either by |
* e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: |
* Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic |
*/ |
#include <linuxcomp.h> |
#include <linux/module.h> |
#include <linux/slab.h> |
#include <linux/kernel.h> |
#include <linux/input.h> |
#include <linux/usb.h> |
#include "hid.h" |
#define unk KEY_UNKNOWN |
static unsigned char hid_keyboard[256] = { |
0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, |
50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, |
4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, |
27, 43, 84, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, |
65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106, |
105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, |
72, 73, 82, 83, 86,127,116,117, 85, 89, 90, 91, 92, 93, 94, 95, |
120,121,122,123,134,138,130,132,128,129,131,137,133,135,136,113, |
115,114,unk,unk,unk,124,unk,181,182,183,184,185,186,187,188,189, |
190,191,192,193,194,195,196,197,198,unk,unk,unk,unk,unk,unk,unk, |
unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk, |
unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk, |
unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk, |
unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk, |
29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113, |
150,158,159,128,136,177,178,176,142,152,173,140,unk,unk,unk,unk |
}; |
static struct { |
__s32 x; |
__s32 y; |
} hid_hat_to_axis[] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; |
static struct input_dev *find_input(struct hid_device *hid, struct hid_field *field) |
{ |
struct list_head *lh; |
struct hid_input *hidinput; |
list_for_each (lh, &hid->inputs) { |
int i; |
hidinput = list_entry(lh, struct hid_input, list); |
if (! hidinput->report) |
continue; |
for (i = 0; i < hidinput->report->maxfield; i++) |
if (hidinput->report->field[i] == field) |
return &hidinput->input; |
} |
/* Assume we only have one input and use it */ |
if (!list_empty(&hid->inputs)) { |
hidinput = list_entry(hid->inputs.next, struct hid_input, list); |
return &hidinput->input; |
} |
/* This is really a bug */ |
return NULL; |
} |
static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_field *field, |
struct hid_usage *usage) |
{ |
struct input_dev *input = &hidinput->input; |
struct hid_device *device = hidinput->input.private; |
int max; |
int is_abs = 0; |
unsigned long *bit; |
switch (usage->hid & HID_USAGE_PAGE) { |
case HID_UP_KEYBOARD: |
set_bit(EV_REP, input->evbit); |
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX; |
if ((usage->hid & HID_USAGE) < 256) { |
if (!(usage->code = hid_keyboard[usage->hid & HID_USAGE])) |
return; |
clear_bit(usage->code, bit); |
} else |
usage->code = KEY_UNKNOWN; |
break; |
case HID_UP_BUTTON: |
usage->code = ((usage->hid - 1) & 0xf) + 0x100; |
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX; |
switch (field->application) { |
case HID_GD_GAMEPAD: usage->code += 0x10; |
case HID_GD_JOYSTICK: usage->code += 0x10; |
case HID_GD_MOUSE: usage->code += 0x10; break; |
default: |
if (field->physical == HID_GD_POINTER) |
usage->code += 0x10; |
break; |
} |
break; |
case HID_UP_GENDESK: |
if ((usage->hid & 0xf0) == 0x80) { /* SystemControl */ |
switch (usage->hid & 0xf) { |
case 0x1: usage->code = KEY_POWER; break; |
case 0x2: usage->code = KEY_SLEEP; break; |
case 0x3: usage->code = KEY_WAKEUP; break; |
default: usage->code = KEY_UNKNOWN; break; |
} |
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX; |
break; |
} |
usage->code = usage->hid & 0xf; |
if (field->report_size == 1) { |
usage->code = BTN_MISC; |
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX; |
break; |
} |
if (field->flags & HID_MAIN_ITEM_RELATIVE) { |
usage->type = EV_REL; bit = input->relbit; max = REL_MAX; |
break; |
} |
usage->type = EV_ABS; bit = input->absbit; max = ABS_MAX; |
if (usage->hid == HID_GD_HATSWITCH) { |
usage->code = ABS_HAT0X; |
usage->hat_min = field->logical_minimum; |
usage->hat_max = field->logical_maximum; |
} |
break; |
case HID_UP_LED: |
usage->code = (usage->hid - 1) & 0xf; |
usage->type = EV_LED; bit = input->ledbit; max = LED_MAX; |
break; |
case HID_UP_DIGITIZER: |
switch (usage->hid & 0xff) { |
case 0x30: /* TipPressure */ |
if (!test_bit(BTN_TOUCH, input->keybit)) { |
device->quirks |= HID_QUIRK_NOTOUCH; |
set_bit(EV_KEY, input->evbit); |
set_bit(BTN_TOUCH, input->keybit); |
} |
usage->type = EV_ABS; bit = input->absbit; max = ABS_MAX; |
usage->code = ABS_PRESSURE; |
clear_bit(usage->code, bit); |
break; |
case 0x32: /* InRange */ |
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX; |
switch (field->physical & 0xff) { |
case 0x21: usage->code = BTN_TOOL_MOUSE; break; |
case 0x22: usage->code = BTN_TOOL_FINGER; break; |
default: usage->code = BTN_TOOL_PEN; break; |
} |
break; |
case 0x3c: /* Invert */ |
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX; |
usage->code = BTN_TOOL_RUBBER; |
clear_bit(usage->code, bit); |
break; |
case 0x33: /* Touch */ |
case 0x42: /* TipSwitch */ |
case 0x43: /* TipSwitch2 */ |
device->quirks &= ~HID_QUIRK_NOTOUCH; |
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX; |
usage->code = BTN_TOUCH; |
clear_bit(usage->code, bit); |
break; |
case 0x44: /* BarrelSwitch */ |
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX; |
usage->code = BTN_STYLUS; |
clear_bit(usage->code, bit); |
break; |
default: goto unknown; |
} |
break; |
case HID_UP_CONSUMER: /* USB HUT v1.1, pages 56-62 */ |
set_bit(EV_REP, input->evbit); |
switch (usage->hid & HID_USAGE) { |
case 0x000: usage->code = 0; break; |
case 0x034: usage->code = KEY_SLEEP; break; |
case 0x036: usage->code = BTN_MISC; break; |
case 0x08a: usage->code = KEY_WWW; break; |
case 0x095: usage->code = KEY_HELP; break; |
case 0x0b0: usage->code = KEY_PLAY; break; |
case 0x0b1: usage->code = KEY_PAUSE; break; |
case 0x0b2: usage->code = KEY_RECORD; break; |
case 0x0b3: usage->code = KEY_FASTFORWARD; break; |
case 0x0b4: usage->code = KEY_REWIND; break; |
case 0x0b5: usage->code = KEY_NEXTSONG; break; |
case 0x0b6: usage->code = KEY_PREVIOUSSONG; break; |
case 0x0b7: usage->code = KEY_STOPCD; break; |
case 0x0b8: usage->code = KEY_EJECTCD; break; |
case 0x0cd: usage->code = KEY_PLAYPAUSE; break; |
case 0x0e0: is_abs = 1; |
usage->code = ABS_VOLUME; |
break; |
case 0x0e2: usage->code = KEY_MUTE; break; |
case 0x0e5: usage->code = KEY_BASSBOOST; break; |
case 0x0e9: usage->code = KEY_VOLUMEUP; break; |
case 0x0ea: usage->code = KEY_VOLUMEDOWN; break; |
case 0x183: usage->code = KEY_CONFIG; break; |
case 0x18a: usage->code = KEY_MAIL; break; |
case 0x192: usage->code = KEY_CALC; break; |
case 0x194: usage->code = KEY_FILE; break; |
case 0x21a: usage->code = KEY_UNDO; break; |
case 0x21b: usage->code = KEY_COPY; break; |
case 0x21c: usage->code = KEY_CUT; break; |
case 0x21d: usage->code = KEY_PASTE; break; |
case 0x221: usage->code = KEY_FIND; break; |
case 0x223: usage->code = KEY_HOMEPAGE; break; |
case 0x224: usage->code = KEY_BACK; break; |
case 0x225: usage->code = KEY_FORWARD; break; |
case 0x226: usage->code = KEY_STOP; break; |
case 0x227: usage->code = KEY_REFRESH; break; |
case 0x22a: usage->code = KEY_BOOKMARKS; break; |
default: usage->code = KEY_UNKNOWN; break; |
} |
if (is_abs) { |
usage->type = EV_ABS; bit = input->absbit; max = ABS_MAX; |
} else { |
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX; |
} |
break; |
case HID_UP_HPVENDOR: /* Reported on a Dutch layout HP5308 */ |
set_bit(EV_REP, input->evbit); |
switch (usage->hid & HID_USAGE) { |
case 0x021: usage->code = KEY_PRINT; break; |
case 0x070: usage->code = KEY_HP; break; |
case 0x071: usage->code = KEY_CAMERA; break; |
case 0x072: usage->code = KEY_SOUND; break; |
case 0x073: usage->code = KEY_QUESTION; break; |
case 0x080: usage->code = KEY_EMAIL; break; |
case 0x081: usage->code = KEY_CHAT; break; |
case 0x082: usage->code = KEY_SEARCH; break; |
case 0x083: usage->code = KEY_CONNECT; break; |
case 0x084: usage->code = KEY_FINANCE; break; |
case 0x085: usage->code = KEY_SPORT; break; |
case 0x086: usage->code = KEY_SHOP; break; |
default: usage->code = KEY_UNKNOWN; break; |
} |
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX; |
break; |
case HID_UP_PID: |
usage->type = EV_FF; bit = input->ffbit; max = FF_MAX; |
switch(usage->hid & HID_USAGE) { |
case 0x26: set_bit(FF_CONSTANT, input->ffbit); break; |
case 0x27: set_bit(FF_RAMP, input->ffbit); break; |
case 0x28: set_bit(FF_CUSTOM, input->ffbit); break; |
case 0x30: set_bit(FF_SQUARE, input->ffbit); |
set_bit(FF_PERIODIC, input->ffbit); break; |
case 0x31: set_bit(FF_SINE, input->ffbit); |
set_bit(FF_PERIODIC, input->ffbit); break; |
case 0x32: set_bit(FF_TRIANGLE, input->ffbit); |
set_bit(FF_PERIODIC, input->ffbit); break; |
case 0x33: set_bit(FF_SAW_UP, input->ffbit); |
set_bit(FF_PERIODIC, input->ffbit); break; |
case 0x34: set_bit(FF_SAW_DOWN, input->ffbit); |
set_bit(FF_PERIODIC, input->ffbit); break; |
case 0x40: set_bit(FF_SPRING, input->ffbit); break; |
case 0x41: set_bit(FF_DAMPER, input->ffbit); break; |
case 0x42: set_bit(FF_INERTIA , input->ffbit); break; |
case 0x43: set_bit(FF_FRICTION, input->ffbit); break; |
case 0x7e: usage->code = FF_GAIN; break; |
case 0x83: /* Simultaneous Effects Max */ |
input->ff_effects_max = (field->value[0]); |
dbg("Maximum Effects - %d",input->ff_effects_max); |
break; |
case 0x98: /* Device Control */ |
usage->code = FF_AUTOCENTER; break; |
case 0xa4: /* Safety Switch */ |
usage->code = BTN_DEAD; |
bit = input->keybit; |
usage->type = EV_KEY; |
max = KEY_MAX; |
dbg("Safety Switch Report\n"); |
break; |
case 0x9f: /* Device Paused */ |
case 0xa0: /* Actuators Enabled */ |
dbg("Not telling the input API about "); |
resolv_usage(usage->hid); |
return; |
} |
break; |
default: |
unknown: |
resolv_usage(usage->hid); |
if (field->report_size == 1) { |
if (field->report->type == HID_OUTPUT_REPORT) { |
usage->code = LED_MISC; |
usage->type = EV_LED; bit = input->ledbit; max = LED_MAX; |
break; |
} |
usage->code = BTN_MISC; |
usage->type = EV_KEY; bit = input->keybit; max = KEY_MAX; |
break; |
} |
if (field->flags & HID_MAIN_ITEM_RELATIVE) { |
usage->code = REL_MISC; |
usage->type = EV_REL; bit = input->relbit; max = REL_MAX; |
break; |
} |
usage->code = ABS_MISC; |
usage->type = EV_ABS; bit = input->absbit; max = ABS_MAX; |
break; |
} |
set_bit(usage->type, input->evbit); |
if ((usage->type == EV_REL) |
&& (device->quirks & HID_QUIRK_2WHEEL_MOUSE_HACK) |
&& (usage->code == REL_WHEEL)) { |
set_bit(REL_HWHEEL, bit); |
} |
while (usage->code <= max && test_and_set_bit(usage->code, bit)) { |
usage->code = find_next_zero_bit(bit, max + 1, usage->code); |
} |
if (usage->code > max) |
return; |
if (usage->type == EV_ABS) { |
int a = field->logical_minimum; |
int b = field->logical_maximum; |
if ((device->quirks & HID_QUIRK_BADPAD) && (usage->code == ABS_X || usage->code == ABS_Y)) { |
a = field->logical_minimum = 0; |
b = field->logical_maximum = 255; |
} |
input->absmin[usage->code] = a; |
input->absmax[usage->code] = b; |
input->absfuzz[usage->code] = 0; |
input->absflat[usage->code] = 0; |
if (field->application == HID_GD_GAMEPAD || field->application == HID_GD_JOYSTICK) { |
input->absfuzz[usage->code] = (b - a) >> 8; |
input->absflat[usage->code] = (b - a) >> 4; |
} |
} |
if (usage->hat_min != usage->hat_max) { |
int i; |
for (i = usage->code; i < usage->code + 2 && i <= max; i++) { |
input->absmax[i] = 1; |
input->absmin[i] = -1; |
input->absfuzz[i] = 0; |
input->absflat[i] = 0; |
} |
set_bit(usage->code + 1, input->absbit); |
} |
} |
void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value, struct pt_regs *regs) |
{ |
struct input_dev *input = find_input(hid, field); |
int *quirks = &hid->quirks; |
if (!input) |
return; |
input_regs(input, regs); |
if ((hid->quirks & HID_QUIRK_2WHEEL_MOUSE_HACK) |
&& (usage->code == BTN_BACK)) { |
if (value) |
hid->quirks |= HID_QUIRK_2WHEEL_MOUSE_HACK_ON; |
else |
hid->quirks &= ~HID_QUIRK_2WHEEL_MOUSE_HACK_ON; |
return; |
} |
if ((hid->quirks & HID_QUIRK_2WHEEL_MOUSE_HACK_ON) |
&& (usage->code == REL_WHEEL)) { |
input_event(input, usage->type, REL_HWHEEL, value); |
return; |
} |
if (usage->hat_min != usage->hat_max) { |
value = (value - usage->hat_min) * 8 / (usage->hat_max - usage->hat_min + 1) + 1; |
if (value < 0 || value > 8) value = 0; |
input_event(input, usage->type, usage->code , hid_hat_to_axis[value].x); |
input_event(input, usage->type, usage->code + 1, hid_hat_to_axis[value].y); |
return; |
} |
if (usage->hid == (HID_UP_DIGITIZER | 0x003c)) { /* Invert */ |
*quirks = value ? (*quirks | HID_QUIRK_INVERT) : (*quirks & ~HID_QUIRK_INVERT); |
return; |
} |
if (usage->hid == (HID_UP_DIGITIZER | 0x0032)) { /* InRange */ |
if (value) { |
input_event(input, usage->type, (*quirks & HID_QUIRK_INVERT) ? BTN_TOOL_RUBBER : usage->code, 1); |
return; |
} |
input_event(input, usage->type, usage->code, 0); |
input_event(input, usage->type, BTN_TOOL_RUBBER, 0); |
return; |
} |
if (usage->hid == (HID_UP_DIGITIZER | 0x0030) && (*quirks & HID_QUIRK_NOTOUCH)) { /* Pressure */ |
int a = field->logical_minimum; |
int b = field->logical_maximum; |
input_event(input, EV_KEY, BTN_TOUCH, value > a + ((b - a) >> 3)); |
} |
if (usage->hid == (HID_UP_PID | 0x83UL)) { /* Simultaneous Effects Max */ |
input->ff_effects_max = value; |
dbg("Maximum Effects - %d",input->ff_effects_max); |
return; |
} |
if (usage->hid == (HID_UP_PID | 0x7fUL)) { |
dbg("PID Pool Report\n"); |
return; |
} |
if((usage->type == EV_KEY) && (usage->code == 0)) /* Key 0 is "unassigned", not KEY_UKNOWN */ |
return; |
input_event(input, usage->type, usage->code, value); |
if ((field->flags & HID_MAIN_ITEM_RELATIVE) && (usage->type == EV_KEY)) |
input_event(input, usage->type, usage->code, 0); |
} |
void hidinput_report_event(struct hid_device *hid, struct hid_report *report) |
{ |
struct list_head *lh; |
struct hid_input *hidinput; |
list_for_each (lh, &hid->inputs) { |
hidinput = list_entry(lh, struct hid_input, list); |
input_sync(&hidinput->input); |
} |
} |
static int hidinput_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) |
{ |
struct hid_device *hid = dev->private; |
struct hid_field *field = NULL; |
int offset; |
if (type == EV_FF) |
return hid_ff_event(hid, dev, type, code, value); |
if ((offset = hid_find_field(hid, type, code, &field)) == -1) { |
warn("event field not found"); |
return -1; |
} |
hid_set_field(field, offset, value); |
hid_submit_report(hid, field->report, USB_DIR_OUT); |
return 0; |
} |
static int hidinput_open(struct input_dev *dev) |
{ |
struct hid_device *hid = dev->private; |
return hid_open(hid); |
} |
static void hidinput_close(struct input_dev *dev) |
{ |
struct hid_device *hid = dev->private; |
hid_close(hid); |
} |
/* |
* Register the input device; print a message. |
* Configure the input layer interface |
* Read all reports and initialize the absolute field values. |
*/ |
int hidinput_connect(struct hid_device *hid) |
{ |
struct usb_device *dev = hid->dev; |
struct hid_report_enum *report_enum; |
struct hid_report *report; |
struct list_head *list; |
struct hid_input *hidinput = NULL; |
int i, j, k; |
INIT_LIST_HEAD(&hid->inputs); |
for (i = 0; i < hid->maxcollection; i++) |
if (hid->collection[i].type == HID_COLLECTION_APPLICATION && |
IS_INPUT_APPLICATION(hid->collection[i].usage)) |
break; |
if (i == hid->maxcollection) |
return -1; |
for (k = HID_INPUT_REPORT; k <= HID_OUTPUT_REPORT; k++) { |
report_enum = hid->report_enum + k; |
list = report_enum->report_list.next; |
while (list != &report_enum->report_list) { |
report = (struct hid_report *) list; |
if (!report->maxfield) |
continue; |
if (!hidinput) { |
hidinput = kmalloc(sizeof(*hidinput), GFP_KERNEL); |
if (!hidinput) { |
err("Out of memory during hid input probe"); |
return -1; |
} |
memset(hidinput, 0, sizeof(*hidinput)); |
list_add_tail(&hidinput->list, &hid->inputs); |
hidinput->input.private = hid; |
hidinput->input.event = hidinput_input_event; |
hidinput->input.open = hidinput_open; |
hidinput->input.close = hidinput_close; |
hidinput->input.name = hid->name; |
hidinput->input.phys = hid->phys; |
hidinput->input.uniq = hid->uniq; |
hidinput->input.id.bustype = BUS_USB; |
hidinput->input.id.vendor = dev->descriptor.idVendor; |
hidinput->input.id.product = dev->descriptor.idProduct; |
hidinput->input.id.version = dev->descriptor.bcdDevice; |
} |
for (i = 0; i < report->maxfield; i++) |
for (j = 0; j < report->field[i]->maxusage; j++) |
hidinput_configure_usage(hidinput, report->field[i], |
report->field[i]->usage + j); |
if (hid->quirks & HID_QUIRK_MULTI_INPUT) { |
/* This will leave hidinput NULL, so that it |
* allocates another one if we have more inputs on |
* the same interface. Some devices (e.g. Happ's |
* UGCI) cram a lot of unrelated inputs into the |
* same interface. */ |
hidinput->report = report; |
input_register_device(&hidinput->input); |
hidinput = NULL; |
} |
list = list->next; |
} |
} |
/* This only gets called when we are a single-input (most of the |
* time). IOW, not a HID_QUIRK_MULTI_INPUT. The hid_ff_init() is |
* only useful in this case, and not for multi-input quirks. */ |
if (hidinput) { |
hid_ff_init(hid); |
input_register_device(&hidinput->input); |
} |
return 0; |
} |
void hidinput_disconnect(struct hid_device *hid) |
{ |
struct list_head *lh, *next; |
struct hid_input *hidinput; |
list_for_each_safe (lh, next, &hid->inputs) { |
hidinput = list_entry(lh, struct hid_input, list); |
input_unregister_device(&hidinput->input); |
list_del(&hidinput->list); |
kfree(hidinput); |
} |
} |
/shark/trunk/drivers/usb/input/usbmouse.c |
---|
0,0 → 1,262 |
/* |
* $Id: usbmouse.c,v 1.1 2004-09-13 15:04:47 giacomo Exp $ |
* |
* Copyright (c) 1999-2001 Vojtech Pavlik |
* |
* USB HIDBP Mouse support |
*/ |
/* |
* 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 |
* |
* Should you need to contact me, the author, you can do so either by |
* e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: |
* Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic |
*/ |
#include <linux/kernel.h> |
#include <linux/slab.h> |
#include <linux/input.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/usb.h> |
/* |
* Version Information |
*/ |
#define DRIVER_VERSION "v1.6" |
#define DRIVER_AUTHOR "Vojtech Pavlik <vojtech@ucw.cz>" |
#define DRIVER_DESC "USB HID Boot Protocol mouse driver" |
#define DRIVER_LICENSE "GPL" |
MODULE_AUTHOR(DRIVER_AUTHOR); |
MODULE_DESCRIPTION(DRIVER_DESC); |
MODULE_LICENSE(DRIVER_LICENSE); |
struct usb_mouse { |
char name[128]; |
char phys[64]; |
struct usb_device *usbdev; |
struct input_dev dev; |
struct urb *irq; |
int open; |
signed char *data; |
dma_addr_t data_dma; |
}; |
static void usb_mouse_irq(struct urb *urb, struct pt_regs *regs) |
{ |
struct usb_mouse *mouse = urb->context; |
signed char *data = mouse->data; |
struct input_dev *dev = &mouse->dev; |
int status; |
switch (urb->status) { |
case 0: /* success */ |
break; |
case -ECONNRESET: /* unlink */ |
case -ENOENT: |
case -ESHUTDOWN: |
return; |
/* -EPIPE: should clear the halt */ |
default: /* error */ |
goto resubmit; |
} |
input_regs(dev, regs); |
input_report_key(dev, BTN_LEFT, data[0] & 0x01); |
input_report_key(dev, BTN_RIGHT, data[0] & 0x02); |
input_report_key(dev, BTN_MIDDLE, data[0] & 0x04); |
input_report_key(dev, BTN_SIDE, data[0] & 0x08); |
input_report_key(dev, BTN_EXTRA, data[0] & 0x10); |
input_report_rel(dev, REL_X, data[1]); |
input_report_rel(dev, REL_Y, data[2]); |
input_report_rel(dev, REL_WHEEL, data[3]); |
input_sync(dev); |
resubmit: |
status = usb_submit_urb (urb, SLAB_ATOMIC); |
if (status) |
err ("can't resubmit intr, %s-%s/input0, status %d", |
mouse->usbdev->bus->bus_name, |
mouse->usbdev->devpath, status); |
} |
static int usb_mouse_open(struct input_dev *dev) |
{ |
struct usb_mouse *mouse = dev->private; |
if (mouse->open++) |
return 0; |
mouse->irq->dev = mouse->usbdev; |
if (usb_submit_urb(mouse->irq, GFP_KERNEL)) { |
mouse->open--; |
return -EIO; |
} |
return 0; |
} |
static void usb_mouse_close(struct input_dev *dev) |
{ |
struct usb_mouse *mouse = dev->private; |
if (!--mouse->open) |
usb_unlink_urb(mouse->irq); |
} |
static int usb_mouse_probe(struct usb_interface * intf, const struct usb_device_id * id) |
{ |
struct usb_device * dev = interface_to_usbdev(intf); |
struct usb_host_interface *interface; |
struct usb_endpoint_descriptor *endpoint; |
struct usb_mouse *mouse; |
int pipe, maxp; |
char path[64]; |
char *buf; |
interface = &intf->altsetting[intf->act_altsetting]; |
if (interface->desc.bNumEndpoints != 1) |
return -ENODEV; |
endpoint = &interface->endpoint[0].desc; |
if (!(endpoint->bEndpointAddress & 0x80)) |
return -ENODEV; |
if ((endpoint->bmAttributes & 3) != 3) |
return -ENODEV; |
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); |
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); |
if (!(mouse = kmalloc(sizeof(struct usb_mouse), GFP_KERNEL))) |
return -ENOMEM; |
memset(mouse, 0, sizeof(struct usb_mouse)); |
mouse->data = usb_buffer_alloc(dev, 8, SLAB_ATOMIC, &mouse->data_dma); |
if (!mouse->data) { |
kfree(mouse); |
return -ENOMEM; |
} |
mouse->irq = usb_alloc_urb(0, GFP_KERNEL); |
if (!mouse->irq) { |
usb_buffer_free(dev, 8, mouse->data, mouse->data_dma); |
kfree(mouse); |
return -ENODEV; |
} |
mouse->usbdev = dev; |
mouse->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL); |
mouse->dev.keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE); |
mouse->dev.relbit[0] = BIT(REL_X) | BIT(REL_Y); |
mouse->dev.keybit[LONG(BTN_MOUSE)] |= BIT(BTN_SIDE) | BIT(BTN_EXTRA); |
mouse->dev.relbit[0] |= BIT(REL_WHEEL); |
mouse->dev.private = mouse; |
mouse->dev.open = usb_mouse_open; |
mouse->dev.close = usb_mouse_close; |
usb_make_path(dev, path, 64); |
sprintf26(mouse->phys, "%s/input0", path); |
mouse->dev.name = mouse->name; |
mouse->dev.phys = mouse->phys; |
mouse->dev.id.bustype = BUS_USB; |
mouse->dev.id.vendor = dev->descriptor.idVendor; |
mouse->dev.id.product = dev->descriptor.idProduct; |
mouse->dev.id.version = dev->descriptor.bcdDevice; |
if (!(buf = kmalloc(63, GFP_KERNEL))) { |
usb_buffer_free(dev, 8, mouse->data, mouse->data_dma); |
kfree(mouse); |
return -ENOMEM; |
} |
if (dev->descriptor.iManufacturer && |
usb_string(dev, dev->descriptor.iManufacturer, buf, 63) > 0) |
strcat(mouse->name, buf); |
if (dev->descriptor.iProduct && |
usb_string(dev, dev->descriptor.iProduct, buf, 63) > 0) |
sprintf26(mouse->name, "%s %s", mouse->name, buf); |
if (!strlen(mouse->name)) |
sprintf26(mouse->name, "USB HIDBP Mouse %04x:%04x", |
mouse->dev.id.vendor, mouse->dev.id.product); |
kfree(buf); |
usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data, |
(maxp > 8 ? 8 : maxp), |
usb_mouse_irq, mouse, endpoint->bInterval); |
mouse->irq->transfer_dma = mouse->data_dma; |
mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; |
input_register_device(&mouse->dev); |
printk(KERN_INFO "input: %s on %s\n", mouse->name, path); |
usb_set_intfdata(intf, mouse); |
return 0; |
} |
static void usb_mouse_disconnect(struct usb_interface *intf) |
{ |
struct usb_mouse *mouse = usb_get_intfdata (intf); |
usb_set_intfdata(intf, NULL); |
if (mouse) { |
usb_unlink_urb(mouse->irq); |
input_unregister_device(&mouse->dev); |
usb_free_urb(mouse->irq); |
usb_buffer_free(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma); |
kfree(mouse); |
} |
} |
static struct usb_device_id usb_mouse_id_table [] = { |
{ USB_INTERFACE_INFO(3, 1, 2) }, |
{ } /* Terminating entry */ |
}; |
MODULE_DEVICE_TABLE (usb, usb_mouse_id_table); |
static struct usb_driver usb_mouse_driver = { |
.owner = THIS_MODULE, |
.name = "usbmouse", |
.probe = usb_mouse_probe, |
.disconnect = usb_mouse_disconnect, |
.id_table = usb_mouse_id_table, |
}; |
/*static*/ int __init usb_mouse_init(void) |
{ |
int retval = usb_register(&usb_mouse_driver); |
if (retval == 0) |
info(DRIVER_VERSION ":" DRIVER_DESC); |
return retval; |
} |
/*static*/ void __exit usb_mouse_exit(void) |
{ |
usb_deregister(&usb_mouse_driver); |
} |
module_init(usb_mouse_init); |
module_exit(usb_mouse_exit); |
/shark/trunk/drivers/usb/input/hid.h |
---|
0,0 → 1,469 |
#ifndef __HID_H |
#define __HID_H |
/* |
* $Id: hid.h,v 1.1 2004-09-13 15:04:47 giacomo Exp $ |
* |
* Copyright (c) 1999 Andreas Gal |
* Copyright (c) 2000-2001 Vojtech Pavlik |
*/ |
/* |
* 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 |
* |
* Should you need to contact me, the author, you can do so either by |
* e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: |
* Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic |
*/ |
#include <linux/types.h> |
#include <linux/slab.h> |
#include <linux/list.h> |
/* |
* USB HID (Human Interface Device) interface class code |
*/ |
#define USB_INTERFACE_CLASS_HID 3 |
/* |
* HID class requests |
*/ |
#define HID_REQ_GET_REPORT 0x01 |
#define HID_REQ_GET_IDLE 0x02 |
#define HID_REQ_GET_PROTOCOL 0x03 |
#define HID_REQ_SET_REPORT 0x09 |
#define HID_REQ_SET_IDLE 0x0A |
#define HID_REQ_SET_PROTOCOL 0x0B |
/* |
* HID class descriptor types |
*/ |
#define HID_DT_HID (USB_TYPE_CLASS | 0x01) |
#define HID_DT_REPORT (USB_TYPE_CLASS | 0x02) |
#define HID_DT_PHYSICAL (USB_TYPE_CLASS | 0x03) |
/* |
* We parse each description item into this structure. Short items data |
* values are expanded to 32-bit signed int, long items contain a pointer |
* into the data area. |
*/ |
struct hid_item { |
unsigned format; |
__u8 size; |
__u8 type; |
__u8 tag; |
union { |
__u8 u8; |
__s8 s8; |
__u16 u16; |
__s16 s16; |
__u32 u32; |
__s32 s32; |
__u8 *longdata; |
} data; |
}; |
/* |
* HID report item format |
*/ |
#define HID_ITEM_FORMAT_SHORT 0 |
#define HID_ITEM_FORMAT_LONG 1 |
/* |
* Special tag indicating long items |
*/ |
#define HID_ITEM_TAG_LONG 15 |
/* |
* HID report descriptor item type (prefix bit 2,3) |
*/ |
#define HID_ITEM_TYPE_MAIN 0 |
#define HID_ITEM_TYPE_GLOBAL 1 |
#define HID_ITEM_TYPE_LOCAL 2 |
#define HID_ITEM_TYPE_RESERVED 3 |
/* |
* HID report descriptor main item tags |
*/ |
#define HID_MAIN_ITEM_TAG_INPUT 8 |
#define HID_MAIN_ITEM_TAG_OUTPUT 9 |
#define HID_MAIN_ITEM_TAG_FEATURE 11 |
#define HID_MAIN_ITEM_TAG_BEGIN_COLLECTION 10 |
#define HID_MAIN_ITEM_TAG_END_COLLECTION 12 |
/* |
* HID report descriptor main item contents |
*/ |
#define HID_MAIN_ITEM_CONSTANT 0x001 |
#define HID_MAIN_ITEM_VARIABLE 0x002 |
#define HID_MAIN_ITEM_RELATIVE 0x004 |
#define HID_MAIN_ITEM_WRAP 0x008 |
#define HID_MAIN_ITEM_NONLINEAR 0x010 |
#define HID_MAIN_ITEM_NO_PREFERRED 0x020 |
#define HID_MAIN_ITEM_NULL_STATE 0x040 |
#define HID_MAIN_ITEM_VOLATILE 0x080 |
#define HID_MAIN_ITEM_BUFFERED_BYTE 0x100 |
/* |
* HID report descriptor collection item types |
*/ |
#define HID_COLLECTION_PHYSICAL 0 |
#define HID_COLLECTION_APPLICATION 1 |
#define HID_COLLECTION_LOGICAL 2 |
/* |
* HID report descriptor global item tags |
*/ |
#define HID_GLOBAL_ITEM_TAG_USAGE_PAGE 0 |
#define HID_GLOBAL_ITEM_TAG_LOGICAL_MINIMUM 1 |
#define HID_GLOBAL_ITEM_TAG_LOGICAL_MAXIMUM 2 |
#define HID_GLOBAL_ITEM_TAG_PHYSICAL_MINIMUM 3 |
#define HID_GLOBAL_ITEM_TAG_PHYSICAL_MAXIMUM 4 |
#define HID_GLOBAL_ITEM_TAG_UNIT_EXPONENT 5 |
#define HID_GLOBAL_ITEM_TAG_UNIT 6 |
#define HID_GLOBAL_ITEM_TAG_REPORT_SIZE 7 |
#define HID_GLOBAL_ITEM_TAG_REPORT_ID 8 |
#define HID_GLOBAL_ITEM_TAG_REPORT_COUNT 9 |
#define HID_GLOBAL_ITEM_TAG_PUSH 10 |
#define HID_GLOBAL_ITEM_TAG_POP 11 |
/* |
* HID report descriptor local item tags |
*/ |
#define HID_LOCAL_ITEM_TAG_USAGE 0 |
#define HID_LOCAL_ITEM_TAG_USAGE_MINIMUM 1 |
#define HID_LOCAL_ITEM_TAG_USAGE_MAXIMUM 2 |
#define HID_LOCAL_ITEM_TAG_DESIGNATOR_INDEX 3 |
#define HID_LOCAL_ITEM_TAG_DESIGNATOR_MINIMUM 4 |
#define HID_LOCAL_ITEM_TAG_DESIGNATOR_MAXIMUM 5 |
#define HID_LOCAL_ITEM_TAG_STRING_INDEX 7 |
#define HID_LOCAL_ITEM_TAG_STRING_MINIMUM 8 |
#define HID_LOCAL_ITEM_TAG_STRING_MAXIMUM 9 |
#define HID_LOCAL_ITEM_TAG_DELIMITER 10 |
/* |
* HID usage tables |
*/ |
#define HID_USAGE_PAGE 0xffff0000 |
#define HID_UP_GENDESK 0x00010000 |
#define HID_UP_KEYBOARD 0x00070000 |
#define HID_UP_LED 0x00080000 |
#define HID_UP_BUTTON 0x00090000 |
#define HID_UP_ORDINAL 0x000a0000 |
#define HID_UP_CONSUMER 0x000c0000 |
#define HID_UP_DIGITIZER 0x000d0000 |
#define HID_UP_PID 0x000f0000 |
#define HID_UP_HPVENDOR 0xff7f0000 |
#define HID_USAGE 0x0000ffff |
#define HID_GD_POINTER 0x00010001 |
#define HID_GD_MOUSE 0x00010002 |
#define HID_GD_JOYSTICK 0x00010004 |
#define HID_GD_GAMEPAD 0x00010005 |
#define HID_GD_HATSWITCH 0x00010039 |
/* |
* HID report types --- Ouch! HID spec says 1 2 3! |
*/ |
#define HID_INPUT_REPORT 0 |
#define HID_OUTPUT_REPORT 1 |
#define HID_FEATURE_REPORT 2 |
/* |
* HID device quirks. |
*/ |
#define HID_QUIRK_INVERT 0x001 |
#define HID_QUIRK_NOTOUCH 0x002 |
#define HID_QUIRK_IGNORE 0x004 |
#define HID_QUIRK_NOGET 0x008 |
#define HID_QUIRK_HIDDEV 0x010 |
#define HID_QUIRK_BADPAD 0x020 |
#define HID_QUIRK_MULTI_INPUT 0x040 |
#define HID_QUIRK_2WHEEL_MOUSE_HACK 0x080 |
#define HID_QUIRK_2WHEEL_MOUSE_HACK_ON 0x100 |
/* |
* This is the global environment of the parser. This information is |
* persistent for main-items. The global environment can be saved and |
* restored with PUSH/POP statements. |
*/ |
struct hid_global { |
unsigned usage_page; |
__s32 logical_minimum; |
__s32 logical_maximum; |
__s32 physical_minimum; |
__s32 physical_maximum; |
__s32 unit_exponent; |
unsigned unit; |
unsigned report_id; |
unsigned report_size; |
unsigned report_count; |
}; |
/* |
* This is the local environment. It is persistent up the next main-item. |
*/ |
#define HID_MAX_DESCRIPTOR_SIZE 4096 |
#define HID_MAX_USAGES 1024 |
#define HID_DEFAULT_NUM_COLLECTIONS 16 |
struct hid_local { |
unsigned usage[HID_MAX_USAGES]; /* usage array */ |
unsigned collection_index[HID_MAX_USAGES]; /* collection index array */ |
unsigned usage_index; |
unsigned usage_minimum; |
unsigned delimiter_depth; |
unsigned delimiter_branch; |
}; |
/* |
* This is the collection stack. We climb up the stack to determine |
* application and function of each field. |
*/ |
struct hid_collection { |
unsigned type; |
unsigned usage; |
unsigned level; |
}; |
struct hid_usage { |
unsigned hid; /* hid usage code */ |
unsigned collection_index; /* index into collection array */ |
__u16 code; /* input driver code */ |
__u8 type; /* input driver type */ |
__s8 hat_min; /* hat switch fun */ |
__s8 hat_max; /* ditto */ |
}; |
struct hid_field { |
unsigned physical; /* physical usage for this field */ |
unsigned logical; /* logical usage for this field */ |
unsigned application; /* application usage for this field */ |
struct hid_usage *usage; /* usage table for this function */ |
unsigned maxusage; /* maximum usage index */ |
unsigned flags; /* main-item flags (i.e. volatile,array,constant) */ |
unsigned report_offset; /* bit offset in the report */ |
unsigned report_size; /* size of this field in the report */ |
unsigned report_count; /* number of this field in the report */ |
unsigned report_type; /* (input,output,feature) */ |
__s32 *value; /* last known value(s) */ |
__s32 logical_minimum; |
__s32 logical_maximum; |
__s32 physical_minimum; |
__s32 physical_maximum; |
__s32 unit_exponent; |
unsigned unit; |
struct hid_report *report; /* associated report */ |
unsigned index; /* index into report->field[] */ |
}; |
#define HID_MAX_FIELDS 64 |
struct hid_report { |
struct list_head list; |
unsigned id; /* id of this report */ |
unsigned type; /* report type */ |
struct hid_field *field[HID_MAX_FIELDS]; /* fields of the report */ |
unsigned maxfield; /* maximum valid field index */ |
unsigned size; /* size of the report (bits) */ |
struct hid_device *device; /* associated device */ |
}; |
struct hid_report_enum { |
unsigned numbered; |
struct list_head report_list; |
struct hid_report *report_id_hash[256]; |
}; |
#define HID_REPORT_TYPES 3 |
#define HID_BUFFER_SIZE 32 |
#define HID_CONTROL_FIFO_SIZE 256 /* to init devices with >100 reports */ |
#define HID_OUTPUT_FIFO_SIZE 64 |
struct hid_control_fifo { |
unsigned char dir; |
struct hid_report *report; |
}; |
#define HID_CLAIMED_INPUT 1 |
#define HID_CLAIMED_HIDDEV 2 |
#define HID_CTRL_RUNNING 1 |
#define HID_OUT_RUNNING 2 |
struct hid_input { |
struct list_head list; |
struct hid_report *report; |
struct input_dev input; |
}; |
struct hid_device { /* device report descriptor */ |
__u8 *rdesc; |
unsigned rsize; |
struct hid_collection *collection; /* List of HID collections */ |
unsigned collection_size; /* Number of allocated hid_collections */ |
unsigned maxcollection; /* Number of parsed collections */ |
unsigned maxapplication; /* Number of applications */ |
unsigned version; /* HID version */ |
unsigned country; /* HID country */ |
struct hid_report_enum report_enum[HID_REPORT_TYPES]; |
struct usb_device *dev; /* USB device */ |
int ifnum; /* USB interface number */ |
unsigned long iofl; /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */ |
struct urb *urbin; /* Input URB */ |
char *inbuf; /* Input buffer */ |
dma_addr_t inbuf_dma; /* Input buffer dma */ |
struct urb *urbctrl; /* Control URB */ |
struct usb_ctrlrequest *cr; /* Control request struct */ |
dma_addr_t cr_dma; /* Control request struct dma */ |
struct hid_control_fifo ctrl[HID_CONTROL_FIFO_SIZE]; /* Control fifo */ |
unsigned char ctrlhead, ctrltail; /* Control fifo head & tail */ |
char *ctrlbuf; /* Control buffer */ |
dma_addr_t ctrlbuf_dma; /* Control buffer dma */ |
spinlock_t ctrllock; /* Control fifo spinlock */ |
struct urb *urbout; /* Output URB */ |
struct hid_report *out[HID_CONTROL_FIFO_SIZE]; /* Output pipe fifo */ |
unsigned char outhead, outtail; /* Output pipe fifo head & tail */ |
char *outbuf; /* Output buffer */ |
dma_addr_t outbuf_dma; /* Output buffer dma */ |
spinlock_t outlock; /* Output fifo spinlock */ |
unsigned claimed; /* Claimed by hidinput, hiddev? */ |
unsigned quirks; /* Various quirks the device can pull on us */ |
struct list_head inputs; /* The list of inputs */ |
void *hiddev; /* The hiddev structure */ |
int minor; /* Hiddev minor number */ |
wait_queue_head_t wait; /* For sleeping */ |
int open; /* is the device open by anyone? */ |
char name[128]; /* Device name */ |
char phys[64]; /* Device physical location */ |
char uniq[64]; /* Device unique identifier (serial #) */ |
void *ff_private; /* Private data for the force-feedback driver */ |
void (*ff_exit)(struct hid_device*); /* Called by hid_exit_ff(hid) */ |
int (*ff_event)(struct hid_device *hid, struct input_dev *input, |
unsigned int type, unsigned int code, int value); |
}; |
#define HID_GLOBAL_STACK_SIZE 4 |
#define HID_COLLECTION_STACK_SIZE 4 |
struct hid_parser { |
struct hid_global global; |
struct hid_global global_stack[HID_GLOBAL_STACK_SIZE]; |
unsigned global_stack_ptr; |
struct hid_local local; |
unsigned collection_stack[HID_COLLECTION_STACK_SIZE]; |
unsigned collection_stack_ptr; |
struct hid_device *device; |
}; |
struct hid_class_descriptor { |
__u8 bDescriptorType; |
__u16 wDescriptorLength; |
} __attribute__ ((packed)); |
struct hid_descriptor { |
__u8 bLength; |
__u8 bDescriptorType; |
__u16 bcdHID; |
__u8 bCountryCode; |
__u8 bNumDescriptors; |
struct hid_class_descriptor desc[1]; |
} __attribute__ ((packed)); |
#ifdef DEBUG |
#include "hid-debug.h" |
#else |
#define hid_dump_input(a,b) do { } while (0) |
#define hid_dump_device(c) do { } while (0) |
#define hid_dump_field(a,b) do { } while (0) |
#define resolv_usage(a) do { } while (0) |
#endif |
#endif |
#ifdef CONFIG_USB_HIDINPUT |
/* Applications from HID Usage Tables 4/8/99 Version 1.1 */ |
/* We ignore a few input applications that are not widely used */ |
#define IS_INPUT_APPLICATION(a) (((a >= 0x00010000) && (a <= 0x00010008)) || ( a == 0x00010080) || ( a == 0x000c0001)) |
extern void hidinput_hid_event(struct hid_device *, struct hid_field *, struct hid_usage *, __s32, struct pt_regs *regs); |
extern void hidinput_report_event(struct hid_device *hid, struct hid_report *report); |
extern int hidinput_connect(struct hid_device *); |
extern void hidinput_disconnect(struct hid_device *); |
#else |
#define IS_INPUT_APPLICATION(a) (0) |
static inline void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value, struct pt_regs *regs) { } |
static inline void hidinput_report_event(struct hid_device *hid, struct hid_report *report) { } |
static inline int hidinput_connect(struct hid_device *hid) { return -ENODEV; } |
static inline void hidinput_disconnect(struct hid_device *hid) { } |
#endif |
int hid_open(struct hid_device *); |
void hid_close(struct hid_device *); |
int hid_find_field(struct hid_device *, unsigned int, unsigned int, struct hid_field **); |
int hid_set_field(struct hid_field *, unsigned, __s32); |
void hid_submit_report(struct hid_device *, struct hid_report *, unsigned char dir); |
void hid_init_reports(struct hid_device *hid); |
int hid_find_report_by_usage(struct hid_device *hid, __u32 wanted_usage, struct hid_report **report, int type); |
#ifdef CONFIG_HID_FF |
int hid_ff_init(struct hid_device *hid); |
#else |
static inline int hid_ff_init(struct hid_device *hid) { return -1; } |
#endif |
static inline void hid_ff_exit(struct hid_device *hid) |
{ |
if (hid->ff_exit) |
hid->ff_exit(hid); |
} |
static inline int hid_ff_event(struct hid_device *hid, struct input_dev *input, |
unsigned int type, unsigned int code, int value) |
{ |
if (hid->ff_event) |
return hid->ff_event(hid, input, type, code, value); |
return -ENOSYS; |
} |
/shark/trunk/drivers/usb/input/usbkbd.c |
---|
0,0 → 1,381 |
/* |
* $Id: usbkbd.c,v 1.1 2004-09-13 15:04:47 giacomo Exp $ |
* |
* Copyright (c) 1999-2001 Vojtech Pavlik |
* |
* USB HIDBP Keyboard support |
*/ |
/* |
* 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 |
* |
* Should you need to contact me, the author, you can do so either by |
* e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: |
* Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic |
*/ |
#include <linux/kernel.h> |
#include <linux/slab.h> |
#include <linux/module.h> |
#include <linux/input.h> |
#include <linux/init.h> |
#include <linux/usb.h> |
/* |
* Version Information |
*/ |
#define DRIVER_VERSION "" |
#define DRIVER_AUTHOR "Vojtech Pavlik <vojtech@ucw.cz>" |
#define DRIVER_DESC "USB HID Boot Protocol keyboard driver" |
#define DRIVER_LICENSE "GPL" |
MODULE_AUTHOR(DRIVER_AUTHOR); |
MODULE_DESCRIPTION(DRIVER_DESC); |
MODULE_LICENSE(DRIVER_LICENSE); |
static unsigned char usb_kbd_keycode[256] = { |
0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, |
50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, |
4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, |
27, 43, 84, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, |
65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106, |
105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, |
72, 73, 82, 83, 86,127,116,117, 85, 89, 90, 91, 92, 93, 94, 95, |
120,121,122,123,134,138,130,132,128,129,131,137,133,135,136,113, |
115,114, 0, 0, 0,124, 0,181,182,183,184,185,186,187,188,189, |
190,191,192,193,194,195,196,197,198, 0, 0, 0, 0, 0, 0, 0, |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113, |
150,158,159,128,136,177,178,176,142,152,173,140 |
}; |
struct usb_kbd { |
struct input_dev dev; |
struct usb_device *usbdev; |
unsigned char old[8]; |
struct urb *irq, *led; |
unsigned char newleds; |
char name[128]; |
char phys[64]; |
int open; |
unsigned char *new; |
struct usb_ctrlrequest *cr; |
unsigned char *leds; |
dma_addr_t cr_dma; |
dma_addr_t new_dma; |
dma_addr_t leds_dma; |
}; |
static void usb_kbd_irq(struct urb *urb, struct pt_regs *regs) |
{ |
struct usb_kbd *kbd = urb->context; |
int i; |
switch (urb->status) { |
case 0: /* success */ |
break; |
case -ECONNRESET: /* unlink */ |
case -ENOENT: |
case -ESHUTDOWN: |
return; |
/* -EPIPE: should clear the halt */ |
default: /* error */ |
goto resubmit; |
} |
input_regs(&kbd->dev, regs); |
for (i = 0; i < 8; i++) |
input_report_key(&kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1); |
for (i = 2; i < 8; i++) { |
if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) { |
if (usb_kbd_keycode[kbd->old[i]]) |
input_report_key(&kbd->dev, usb_kbd_keycode[kbd->old[i]], 0); |
else |
info("Unknown key (scancode %#x) released.", kbd->old[i]); |
} |
if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) { |
if (usb_kbd_keycode[kbd->new[i]]) |
input_report_key(&kbd->dev, usb_kbd_keycode[kbd->new[i]], 1); |
else |
info("Unknown key (scancode %#x) pressed.", kbd->new[i]); |
} |
} |
input_sync(&kbd->dev); |
memcpy(kbd->old, kbd->new, 8); |
resubmit: |
i = usb_submit_urb (urb, SLAB_ATOMIC); |
if (i) |
err ("can't resubmit intr, %s-%s/input0, status %d", |
kbd->usbdev->bus->bus_name, |
kbd->usbdev->devpath, i); |
} |
int usb_kbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) |
{ |
struct usb_kbd *kbd = dev->private; |
if (type != EV_LED) |
return -1; |
kbd->newleds = (!!test_bit(LED_KANA, dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) | |
(!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL, dev->led) << 1) | |
(!!test_bit(LED_NUML, dev->led)); |
if (kbd->led->status == -EINPROGRESS) |
return 0; |
if (*(kbd->leds) == kbd->newleds) |
return 0; |
*(kbd->leds) = kbd->newleds; |
kbd->led->dev = kbd->usbdev; |
if (usb_submit_urb(kbd->led, GFP_ATOMIC)) |
err("usb_submit_urb(leds) failed"); |
return 0; |
} |
static void usb_kbd_led(struct urb *urb, struct pt_regs *regs) |
{ |
struct usb_kbd *kbd = urb->context; |
if (urb->status) |
warn("led urb status %d received", urb->status); |
if (*(kbd->leds) == kbd->newleds) |
return; |
*(kbd->leds) = kbd->newleds; |
kbd->led->dev = kbd->usbdev; |
if (usb_submit_urb(kbd->led, GFP_ATOMIC)) |
err("usb_submit_urb(leds) failed"); |
} |
static int usb_kbd_open(struct input_dev *dev) |
{ |
struct usb_kbd *kbd = dev->private; |
if (kbd->open++) |
return 0; |
kbd->irq->dev = kbd->usbdev; |
if (usb_submit_urb(kbd->irq, GFP_KERNEL)) { |
kbd->open--; |
return -EIO; |
} |
return 0; |
} |
static void usb_kbd_close(struct input_dev *dev) |
{ |
struct usb_kbd *kbd = dev->private; |
if (!--kbd->open) |
usb_unlink_urb(kbd->irq); |
} |
static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd) |
{ |
if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL))) |
return -1; |
if (!(kbd->led = usb_alloc_urb(0, GFP_KERNEL))) |
return -1; |
if (!(kbd->new = usb_buffer_alloc(dev, 8, SLAB_ATOMIC, &kbd->new_dma))) |
return -1; |
if (!(kbd->cr = usb_buffer_alloc(dev, sizeof(struct usb_ctrlrequest), SLAB_ATOMIC, &kbd->cr_dma))) |
return -1; |
if (!(kbd->leds = usb_buffer_alloc(dev, 1, SLAB_ATOMIC, &kbd->leds_dma))) |
return -1; |
return 0; |
} |
static void usb_kbd_free_mem(struct usb_device *dev, struct usb_kbd *kbd) |
{ |
if (kbd->irq) |
usb_free_urb(kbd->irq); |
if (kbd->led) |
usb_free_urb(kbd->led); |
if (kbd->new) |
usb_buffer_free(dev, 8, kbd->new, kbd->new_dma); |
if (kbd->cr) |
usb_buffer_free(dev, sizeof(struct usb_ctrlrequest), kbd->cr, kbd->cr_dma); |
if (kbd->leds) |
usb_buffer_free(dev, 1, kbd->leds, kbd->leds_dma); |
} |
static int usb_kbd_probe(struct usb_interface *iface, |
const struct usb_device_id *id) |
{ |
struct usb_device * dev = interface_to_usbdev(iface); |
struct usb_host_interface *interface; |
struct usb_endpoint_descriptor *endpoint; |
struct usb_kbd *kbd; |
int i, pipe, maxp; |
char path[64]; |
char *buf; |
interface = &iface->altsetting[iface->act_altsetting]; |
if (interface->desc.bNumEndpoints != 1) |
return -ENODEV; |
endpoint = &interface->endpoint[0].desc; |
if (!(endpoint->bEndpointAddress & 0x80)) |
return -ENODEV; |
if ((endpoint->bmAttributes & 3) != 3) |
return -ENODEV; |
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); |
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); |
if (!(kbd = kmalloc(sizeof(struct usb_kbd), GFP_KERNEL))) |
return -ENOMEM; |
memset(kbd, 0, sizeof(struct usb_kbd)); |
if (usb_kbd_alloc_mem(dev, kbd)) { |
usb_kbd_free_mem(dev, kbd); |
kfree(kbd); |
return -ENOMEM; |
} |
kbd->usbdev = dev; |
kbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_LED) | BIT(EV_REP); |
kbd->dev.ledbit[0] = BIT(LED_NUML) | BIT(LED_CAPSL) | BIT(LED_SCROLLL) | BIT(LED_COMPOSE) | BIT(LED_KANA); |
for (i = 0; i < 255; i++) |
set_bit(usb_kbd_keycode[i], kbd->dev.keybit); |
clear_bit(0, kbd->dev.keybit); |
kbd->dev.private = kbd; |
kbd->dev.event = usb_kbd_event; |
kbd->dev.open = usb_kbd_open; |
kbd->dev.close = usb_kbd_close; |
usb_fill_int_urb(kbd->irq, dev, pipe, |
kbd->new, (maxp > 8 ? 8 : maxp), |
usb_kbd_irq, kbd, endpoint->bInterval); |
kbd->irq->transfer_dma = kbd->new_dma; |
kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; |
kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE; |
kbd->cr->bRequest = 0x09; |
kbd->cr->wValue = cpu_to_le16(0x200); |
kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber); |
kbd->cr->wLength = cpu_to_le16(1); |
usb_make_path(dev, path, 64); |
sprintf26(kbd->phys, "%s/input0", path); |
kbd->dev.name = kbd->name; |
kbd->dev.phys = kbd->phys; |
kbd->dev.id.bustype = BUS_USB; |
kbd->dev.id.vendor = dev->descriptor.idVendor; |
kbd->dev.id.product = dev->descriptor.idProduct; |
kbd->dev.id.version = dev->descriptor.bcdDevice; |
if (!(buf = kmalloc(63, GFP_KERNEL))) { |
usb_free_urb(kbd->irq); |
usb_kbd_free_mem(dev, kbd); |
kfree(kbd); |
return -ENOMEM; |
} |
if (dev->descriptor.iManufacturer && |
usb_string(dev, dev->descriptor.iManufacturer, buf, 63) > 0) |
strcat(kbd->name, buf); |
if (dev->descriptor.iProduct && |
usb_string(dev, dev->descriptor.iProduct, buf, 63) > 0) |
sprintf26(kbd->name, "%s %s", kbd->name, buf); |
if (!strlen(kbd->name)) |
sprintf26(kbd->name, "USB HIDBP Keyboard %04x:%04x", |
kbd->dev.id.vendor, kbd->dev.id.product); |
kfree(buf); |
usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0), |
(void *) kbd->cr, kbd->leds, 1, |
usb_kbd_led, kbd); |
kbd->led->setup_dma = kbd->cr_dma; |
kbd->led->transfer_dma = kbd->leds_dma; |
kbd->led->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP |
| URB_NO_SETUP_DMA_MAP); |
input_register_device(&kbd->dev); |
printk(KERN_INFO "input: %s on %s\n", kbd->name, path); |
usb_set_intfdata(iface, kbd); |
return 0; |
} |
static void usb_kbd_disconnect(struct usb_interface *intf) |
{ |
struct usb_kbd *kbd = usb_get_intfdata (intf); |
usb_set_intfdata(intf, NULL); |
if (kbd) { |
usb_unlink_urb(kbd->irq); |
input_unregister_device(&kbd->dev); |
usb_kbd_free_mem(interface_to_usbdev(intf), kbd); |
kfree(kbd); |
} |
} |
static struct usb_device_id usb_kbd_id_table [] = { |
{ USB_INTERFACE_INFO(3, 1, 1) }, |
{ } /* Terminating entry */ |
}; |
MODULE_DEVICE_TABLE (usb, usb_kbd_id_table); |
static struct usb_driver usb_kbd_driver = { |
.owner = THIS_MODULE, |
.name = "usbkbd", |
.probe = usb_kbd_probe, |
.disconnect = usb_kbd_disconnect, |
.id_table = usb_kbd_id_table, |
}; |
/*static*/ int /*__init*/ usb_kbd_init(void) |
{ |
int result = usb_register(&usb_kbd_driver); |
if (result == 0) |
info(DRIVER_VERSION ":" DRIVER_DESC); |
return result; |
} |
/*static*/ void /*__exit*/ usb_kbd_exit(void) |
{ |
usb_deregister(&usb_kbd_driver); |
} |
module_init(usb_kbd_init); |
module_exit(usb_kbd_exit); |
/shark/trunk/drivers/usb/input/hid-core.c |
---|
0,0 → 1,1724 |
/* |
* USB HID support for Linux |
* |
* Copyright (c) 1999 Andreas Gal |
* Copyright (c) 2000-2001 Vojtech Pavlik <vojtech@suse.cz> |
*/ |
/* |
* 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. |
*/ |
#include <linuxcomp.h> |
#include <linux/module.h> |
#include <linux/slab.h> |
#include <linux/init.h> |
#include <linux/kernel.h> |
#include <linux/sched.h> |
#include <linux/list.h> |
#include <linux/mm.h> |
#include <linux/smp_lock.h> |
#include <linux/spinlock.h> |
#include <asm/unaligned.h> |
#include <asm/byteorder.h> |
#include <linux/input.h> |
#undef DEBUG |
#undef DEBUG_DATA |
#include <linux/usb.h> |
#include "hid.h" |
#include <linux/hiddev.h> |
/* |
* Version Information |
*/ |
#define DRIVER_VERSION "v2.0" |
#define DRIVER_AUTHOR "Andreas Gal, Vojtech Pavlik" |
#define DRIVER_DESC "USB HID core driver" |
#define DRIVER_LICENSE "GPL" |
static char *hid_types[] = {"Device", "Pointer", "Mouse", "Device", "Joystick", |
"Gamepad", "Keyboard", "Keypad", "Multi-Axis Controller"}; |
/* |
* Register a new report for a device. |
*/ |
static struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id) |
{ |
struct hid_report_enum *report_enum = device->report_enum + type; |
struct hid_report *report; |
if (report_enum->report_id_hash[id]) |
return report_enum->report_id_hash[id]; |
if (!(report = kmalloc(sizeof(struct hid_report), GFP_KERNEL))) |
return NULL; |
memset(report, 0, sizeof(struct hid_report)); |
if (id != 0) |
report_enum->numbered = 1; |
report->id = id; |
report->type = type; |
report->size = 0; |
report->device = device; |
report_enum->report_id_hash[id] = report; |
list_add_tail(&report->list, &report_enum->report_list); |
return report; |
} |
/* |
* Register a new field for this report. |
*/ |
static struct hid_field *hid_register_field(struct hid_report *report, unsigned usages, unsigned values) |
{ |
struct hid_field *field; |
if (report->maxfield == HID_MAX_FIELDS) { |
dbg("too many fields in report"); |
return NULL; |
} |
if (!(field = kmalloc(sizeof(struct hid_field) + usages * sizeof(struct hid_usage) |
+ values * sizeof(unsigned), GFP_KERNEL))) return NULL; |
memset(field, 0, sizeof(struct hid_field) + usages * sizeof(struct hid_usage) |
+ values * sizeof(unsigned)); |
report->field[report->maxfield++] = field; |
field->usage = (struct hid_usage *)(field + 1); |
field->value = (unsigned *)(field->usage + usages); |
field->report = report; |
return field; |
} |
/* |
* Open a collection. The type/usage is pushed on the stack. |
*/ |
static int open_collection(struct hid_parser *parser, unsigned type) |
{ |
struct hid_collection *collection; |
unsigned usage; |
usage = parser->local.usage[0]; |
if (parser->collection_stack_ptr == HID_COLLECTION_STACK_SIZE) { |
dbg("collection stack overflow"); |
return -1; |
} |
if (parser->device->maxcollection == parser->device->collection_size) { |
collection = kmalloc(sizeof(struct hid_collection) * |
parser->device->collection_size * 2, |
GFP_KERNEL); |
if (collection == NULL) { |
dbg("failed to reallocate collection array"); |
return -1; |
} |
memcpy(collection, parser->device->collection, |
sizeof(struct hid_collection) * |
parser->device->collection_size); |
memset(collection + parser->device->collection_size, 0, |
sizeof(struct hid_collection) * |
parser->device->collection_size); |
kfree(parser->device->collection); |
parser->device->collection = collection; |
parser->device->collection_size *= 2; |
} |
parser->collection_stack[parser->collection_stack_ptr++] = |
parser->device->maxcollection; |
collection = parser->device->collection + |
parser->device->maxcollection++; |
collection->type = type; |
collection->usage = usage; |
collection->level = parser->collection_stack_ptr - 1; |
if (type == HID_COLLECTION_APPLICATION) |
parser->device->maxapplication++; |
return 0; |
} |
/* |
* Close a collection. |
*/ |
static int close_collection(struct hid_parser *parser) |
{ |
if (!parser->collection_stack_ptr) { |
dbg("collection stack underflow"); |
return -1; |
} |
parser->collection_stack_ptr--; |
return 0; |
} |
/* |
* Climb up the stack, search for the specified collection type |
* and return the usage. |
*/ |
static unsigned hid_lookup_collection(struct hid_parser *parser, unsigned type) |
{ |
int n; |
for (n = parser->collection_stack_ptr - 1; n >= 0; n--) |
if (parser->device->collection[parser->collection_stack[n]].type == type) |
return parser->device->collection[parser->collection_stack[n]].usage; |
return 0; /* we know nothing about this usage type */ |
} |
/* |
* Add a usage to the temporary parser table. |
*/ |
static int hid_add_usage(struct hid_parser *parser, unsigned usage) |
{ |
if (parser->local.usage_index >= HID_MAX_USAGES) { |
dbg("usage index exceeded"); |
return -1; |
} |
parser->local.usage[parser->local.usage_index] = usage; |
parser->local.collection_index[parser->local.usage_index] = |
parser->collection_stack_ptr ? |
parser->collection_stack[parser->collection_stack_ptr - 1] : 0; |
parser->local.usage_index++; |
return 0; |
} |
/* |
* Register a new field for this report. |
*/ |
static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsigned flags) |
{ |
struct hid_report *report; |
struct hid_field *field; |
int usages; |
unsigned offset; |
int i; |
if (!(report = hid_register_report(parser->device, report_type, parser->global.report_id))) { |
dbg("hid_register_report failed"); |
return -1; |
} |
if (parser->global.logical_maximum < parser->global.logical_minimum) { |
dbg("logical range invalid %d %d", parser->global.logical_minimum, parser->global.logical_maximum); |
return -1; |
} |
usages = parser->local.usage_index; |
offset = report->size; |
report->size += parser->global.report_size * parser->global.report_count; |
if (usages == 0) |
return 0; /* ignore padding fields */ |
if ((field = hid_register_field(report, usages, parser->global.report_count)) == NULL) |
return 0; |
field->physical = hid_lookup_collection(parser, HID_COLLECTION_PHYSICAL); |
field->logical = hid_lookup_collection(parser, HID_COLLECTION_LOGICAL); |
field->application = hid_lookup_collection(parser, HID_COLLECTION_APPLICATION); |
for (i = 0; i < usages; i++) { |
field->usage[i].hid = parser->local.usage[i]; |
field->usage[i].collection_index = |
parser->local.collection_index[i]; |
} |
field->maxusage = usages; |
field->flags = flags; |
field->report_offset = offset; |
field->report_type = report_type; |
field->report_size = parser->global.report_size; |
field->report_count = parser->global.report_count; |
field->logical_minimum = parser->global.logical_minimum; |
field->logical_maximum = parser->global.logical_maximum; |
field->physical_minimum = parser->global.physical_minimum; |
field->physical_maximum = parser->global.physical_maximum; |
field->unit_exponent = parser->global.unit_exponent; |
field->unit = parser->global.unit; |
return 0; |
} |
/* |
* Read data value from item. |
*/ |
static __inline__ __u32 item_udata(struct hid_item *item) |
{ |
switch (item->size) { |
case 1: return item->data.u8; |
case 2: return item->data.u16; |
case 4: return item->data.u32; |
} |
return 0; |
} |
static __inline__ __s32 item_sdata(struct hid_item *item) |
{ |
switch (item->size) { |
case 1: return item->data.s8; |
case 2: return item->data.s16; |
case 4: return item->data.s32; |
} |
return 0; |
} |
/* |
* Process a global item. |
*/ |
static int hid_parser_global(struct hid_parser *parser, struct hid_item *item) |
{ |
switch (item->tag) { |
case HID_GLOBAL_ITEM_TAG_PUSH: |
if (parser->global_stack_ptr == HID_GLOBAL_STACK_SIZE) { |
dbg("global enviroment stack overflow"); |
return -1; |
} |
memcpy(parser->global_stack + parser->global_stack_ptr++, |
&parser->global, sizeof(struct hid_global)); |
return 0; |
case HID_GLOBAL_ITEM_TAG_POP: |
if (!parser->global_stack_ptr) { |
dbg("global enviroment stack underflow"); |
return -1; |
} |
memcpy(&parser->global, parser->global_stack + --parser->global_stack_ptr, |
sizeof(struct hid_global)); |
return 0; |
case HID_GLOBAL_ITEM_TAG_USAGE_PAGE: |
parser->global.usage_page = item_udata(item); |
return 0; |
case HID_GLOBAL_ITEM_TAG_LOGICAL_MINIMUM: |
parser->global.logical_minimum = item_sdata(item); |
return 0; |
case HID_GLOBAL_ITEM_TAG_LOGICAL_MAXIMUM: |
if (parser->global.logical_minimum < 0) |
parser->global.logical_maximum = item_sdata(item); |
else |
parser->global.logical_maximum = item_udata(item); |
return 0; |
case HID_GLOBAL_ITEM_TAG_PHYSICAL_MINIMUM: |
parser->global.physical_minimum = item_sdata(item); |
return 0; |
case HID_GLOBAL_ITEM_TAG_PHYSICAL_MAXIMUM: |
if (parser->global.physical_minimum < 0) |
parser->global.physical_maximum = item_sdata(item); |
else |
parser->global.physical_maximum = item_udata(item); |
return 0; |
case HID_GLOBAL_ITEM_TAG_UNIT_EXPONENT: |
parser->global.unit_exponent = item_sdata(item); |
return 0; |
case HID_GLOBAL_ITEM_TAG_UNIT: |
parser->global.unit = item_udata(item); |
return 0; |
case HID_GLOBAL_ITEM_TAG_REPORT_SIZE: |
if ((parser->global.report_size = item_udata(item)) > 32) { |
dbg("invalid report_size %d", parser->global.report_size); |
return -1; |
} |
return 0; |
case HID_GLOBAL_ITEM_TAG_REPORT_COUNT: |
if ((parser->global.report_count = item_udata(item)) > HID_MAX_USAGES) { |
dbg("invalid report_count %d", parser->global.report_count); |
return -1; |
} |
return 0; |
case HID_GLOBAL_ITEM_TAG_REPORT_ID: |
if ((parser->global.report_id = item_udata(item)) == 0) { |
dbg("report_id 0 is invalid"); |
return -1; |
} |
return 0; |
default: |
dbg("unknown global tag 0x%x", item->tag); |
return -1; |
} |
} |
/* |
* Process a local item. |
*/ |
static int hid_parser_local(struct hid_parser *parser, struct hid_item *item) |
{ |
__u32 data; |
unsigned n; |
if (item->size == 0) { |
dbg("item data expected for local item"); |
return -1; |
} |
data = item_udata(item); |
switch (item->tag) { |
case HID_LOCAL_ITEM_TAG_DELIMITER: |
if (data) { |
/* |
* We treat items before the first delimiter |
* as global to all usage sets (branch 0). |
* In the moment we process only these global |
* items and the first delimiter set. |
*/ |
if (parser->local.delimiter_depth != 0) { |
dbg("nested delimiters"); |
return -1; |
} |
parser->local.delimiter_depth++; |
parser->local.delimiter_branch++; |
} else { |
if (parser->local.delimiter_depth < 1) { |
dbg("bogus close delimiter"); |
return -1; |
} |
parser->local.delimiter_depth--; |
} |
return 1; |
case HID_LOCAL_ITEM_TAG_USAGE: |
if (parser->local.delimiter_branch > 1) { |
dbg("alternative usage ignored"); |
return 0; |
} |
if (item->size <= 2) |
data = (parser->global.usage_page << 16) + data; |
return hid_add_usage(parser, data); |
case HID_LOCAL_ITEM_TAG_USAGE_MINIMUM: |
if (parser->local.delimiter_branch > 1) { |
dbg("alternative usage ignored"); |
return 0; |
} |
if (item->size <= 2) |
data = (parser->global.usage_page << 16) + data; |
parser->local.usage_minimum = data; |
return 0; |
case HID_LOCAL_ITEM_TAG_USAGE_MAXIMUM: |
if (parser->local.delimiter_branch > 1) { |
dbg("alternative usage ignored"); |
return 0; |
} |
if (item->size <= 2) |
data = (parser->global.usage_page << 16) + data; |
for (n = parser->local.usage_minimum; n <= data; n++) |
if (hid_add_usage(parser, n)) { |
dbg("hid_add_usage failed\n"); |
return -1; |
} |
return 0; |
default: |
dbg("unknown local item tag 0x%x", item->tag); |
return 0; |
} |
return 0; |
} |
/* |
* Process a main item. |
*/ |
static int hid_parser_main(struct hid_parser *parser, struct hid_item *item) |
{ |
__u32 data; |
int ret; |
data = item_udata(item); |
switch (item->tag) { |
case HID_MAIN_ITEM_TAG_BEGIN_COLLECTION: |
ret = open_collection(parser, data & 0xff); |
break; |
case HID_MAIN_ITEM_TAG_END_COLLECTION: |
ret = close_collection(parser); |
break; |
case HID_MAIN_ITEM_TAG_INPUT: |
ret = hid_add_field(parser, HID_INPUT_REPORT, data); |
break; |
case HID_MAIN_ITEM_TAG_OUTPUT: |
ret = hid_add_field(parser, HID_OUTPUT_REPORT, data); |
break; |
case HID_MAIN_ITEM_TAG_FEATURE: |
ret = hid_add_field(parser, HID_FEATURE_REPORT, data); |
break; |
default: |
dbg("unknown main item tag 0x%x", item->tag); |
ret = 0; |
} |
memset(&parser->local, 0, sizeof(parser->local)); /* Reset the local parser environment */ |
return ret; |
} |
/* |
* Process a reserved item. |
*/ |
static int hid_parser_reserved(struct hid_parser *parser, struct hid_item *item) |
{ |
dbg("reserved item type, tag 0x%x", item->tag); |
return 0; |
} |
/* |
* Free a report and all registered fields. The field->usage and |
* field->value table's are allocated behind the field, so we need |
* only to free(field) itself. |
*/ |
static void hid_free_report(struct hid_report *report) |
{ |
unsigned n; |
for (n = 0; n < report->maxfield; n++) |
kfree(report->field[n]); |
kfree(report); |
} |
/* |
* Free a device structure, all reports, and all fields. |
*/ |
static void hid_free_device(struct hid_device *device) |
{ |
unsigned i,j; |
hid_ff_exit(device); |
for (i = 0; i < HID_REPORT_TYPES; i++) { |
struct hid_report_enum *report_enum = device->report_enum + i; |
for (j = 0; j < 256; j++) { |
struct hid_report *report = report_enum->report_id_hash[j]; |
if (report) |
hid_free_report(report); |
} |
} |
if (device->rdesc) |
kfree(device->rdesc); |
kfree(device); |
} |
/* |
* Fetch a report description item from the data stream. We support long |
* items, though they are not used yet. |
*/ |
static u8 *fetch_item(__u8 *start, __u8 *end, struct hid_item *item) |
{ |
u8 b; |
if ((end - start) <= 0) |
return NULL; |
b = *start++; |
item->type = (b >> 2) & 3; |
item->tag = (b >> 4) & 15; |
if (item->tag == HID_ITEM_TAG_LONG) { |
item->format = HID_ITEM_FORMAT_LONG; |
if ((end - start) < 2) |
return NULL; |
item->size = *start++; |
item->tag = *start++; |
if ((end - start) < item->size) |
return NULL; |
item->data.longdata = start; |
start += item->size; |
return start; |
} |
item->format = HID_ITEM_FORMAT_SHORT; |
item->size = b & 3; |
switch (item->size) { |
case 0: |
return start; |
case 1: |
if ((end - start) < 1) |
return NULL; |
item->data.u8 = *start++; |
return start; |
case 2: |
if ((end - start) < 2) |
return NULL; |
item->data.u16 = le16_to_cpu(get_unaligned(((__u16*)start)++)); |
return start; |
case 3: |
item->size++; |
if ((end - start) < 4) |
return NULL; |
item->data.u32 = le32_to_cpu(get_unaligned(((__u32*)start)++)); |
return start; |
} |
return NULL; |
} |
/* |
* Parse a report description into a hid_device structure. Reports are |
* enumerated, fields are attached to these reports. |
*/ |
static struct hid_device *hid_parse_report(__u8 *start, unsigned size) |
{ |
struct hid_device *device; |
struct hid_parser *parser; |
struct hid_item item; |
__u8 *end; |
unsigned i; |
static int (*dispatch_type[])(struct hid_parser *parser, |
struct hid_item *item) = { |
hid_parser_main, |
hid_parser_global, |
hid_parser_local, |
hid_parser_reserved |
}; |
if (!(device = kmalloc(sizeof(struct hid_device), GFP_KERNEL))) |
return NULL; |
memset(device, 0, sizeof(struct hid_device)); |
if (!(device->collection =kmalloc(sizeof(struct hid_collection) * |
HID_DEFAULT_NUM_COLLECTIONS, GFP_KERNEL))) { |
kfree(device); |
return NULL; |
} |
memset(device->collection, 0, sizeof(struct hid_collection) * |
HID_DEFAULT_NUM_COLLECTIONS); |
device->collection_size = HID_DEFAULT_NUM_COLLECTIONS; |
for (i = 0; i < HID_REPORT_TYPES; i++) |
INIT_LIST_HEAD(&device->report_enum[i].report_list); |
if (!(device->rdesc = (__u8 *)kmalloc(size, GFP_KERNEL))) { |
kfree(device->collection); |
kfree(device); |
return NULL; |
} |
memcpy(device->rdesc, start, size); |
device->rsize = size; |
if (!(parser = kmalloc(sizeof(struct hid_parser), GFP_KERNEL))) { |
kfree(device->rdesc); |
kfree(device->collection); |
kfree(device); |
return NULL; |
} |
memset(parser, 0, sizeof(struct hid_parser)); |
parser->device = device; |
end = start + size; |
while ((start = fetch_item(start, end, &item)) != 0) { |
if (item.format != HID_ITEM_FORMAT_SHORT) { |
dbg("unexpected long global item"); |
kfree(device->collection); |
hid_free_device(device); |
kfree(parser); |
return NULL; |
} |
if (dispatch_type[item.type](parser, &item)) { |
dbg("item %u %u %u %u parsing failed\n", |
item.format, (unsigned)item.size, (unsigned)item.type, (unsigned)item.tag); |
kfree(device->collection); |
hid_free_device(device); |
kfree(parser); |
return NULL; |
} |
if (start == end) { |
if (parser->collection_stack_ptr) { |
dbg("unbalanced collection at end of report description"); |
kfree(device->collection); |
hid_free_device(device); |
kfree(parser); |
return NULL; |
} |
if (parser->local.delimiter_depth) { |
dbg("unbalanced delimiter at end of report description"); |
kfree(device->collection); |
hid_free_device(device); |
kfree(parser); |
return NULL; |
} |
kfree(parser); |
return device; |
} |
} |
dbg("item fetching failed at offset %d\n", (int)(end - start)); |
kfree(device->collection); |
hid_free_device(device); |
kfree(parser); |
return NULL; |
} |
/* |
* Convert a signed n-bit integer to signed 32-bit integer. Common |
* cases are done through the compiler, the screwed things has to be |
* done by hand. |
*/ |
static __inline__ __s32 snto32(__u32 value, unsigned n) |
{ |
switch (n) { |
case 8: return ((__s8)value); |
case 16: return ((__s16)value); |
case 32: return ((__s32)value); |
} |
return value & (1 << (n - 1)) ? value | (-1 << n) : value; |
} |
/* |
* Convert a signed 32-bit integer to a signed n-bit integer. |
*/ |
static __inline__ __u32 s32ton(__s32 value, unsigned n) |
{ |
__s32 a = value >> (n - 1); |
if (a && a != -1) |
return value < 0 ? 1 << (n - 1) : (1 << (n - 1)) - 1; |
return value & ((1 << n) - 1); |
} |
/* |
* Extract/implement a data field from/to a report. |
*/ |
static __inline__ __u32 extract(__u8 *report, unsigned offset, unsigned n) |
{ |
report += (offset >> 5) << 2; offset &= 31; |
return (le64_to_cpu(get_unaligned((__u64*)report)) >> offset) & ((1 << n) - 1); |
} |
static __inline__ void implement(__u8 *report, unsigned offset, unsigned n, __u32 value) |
{ |
report += (offset >> 5) << 2; offset &= 31; |
put_unaligned((get_unaligned((__u64*)report) |
& cpu_to_le64(~((((__u64) 1 << n) - 1) << offset))) |
| cpu_to_le64((__u64)value << offset), (__u64*)report); |
} |
/* |
* Search an array for a value. |
*/ |
static __inline__ int search(__s32 *array, __s32 value, unsigned n) |
{ |
while (n--) { |
if (*array++ == value) |
return 0; |
} |
return -1; |
} |
static void hid_process_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value, struct pt_regs *regs) |
{ |
hid_dump_input(usage, value); |
if (hid->claimed & HID_CLAIMED_INPUT) |
hidinput_hid_event(hid, field, usage, value, regs); |
if (hid->claimed & HID_CLAIMED_HIDDEV) |
hiddev_hid_event(hid, field, usage, value, regs); |
} |
/* |
* Analyse a received field, and fetch the data from it. The field |
* content is stored for next report processing (we do differential |
* reporting to the layer). |
*/ |
static void hid_input_field(struct hid_device *hid, struct hid_field *field, __u8 *data, struct pt_regs *regs) |
{ |
unsigned n; |
unsigned count = field->report_count; |
unsigned offset = field->report_offset; |
unsigned size = field->report_size; |
__s32 min = field->logical_minimum; |
__s32 max = field->logical_maximum; |
__s32 value[count]; /* WARNING: gcc specific */ |
for (n = 0; n < count; n++) { |
value[n] = min < 0 ? snto32(extract(data, offset + n * size, size), size) : |
extract(data, offset + n * size, size); |
if (!(field->flags & HID_MAIN_ITEM_VARIABLE) /* Ignore report if ErrorRollOver */ |
&& value[n] >= min && value[n] <= max |
&& field->usage[value[n] - min].hid == HID_UP_KEYBOARD + 1) |
return; |
} |
for (n = 0; n < count; n++) { |
if (HID_MAIN_ITEM_VARIABLE & field->flags) { |
if (field->flags & HID_MAIN_ITEM_RELATIVE) { |
if (!value[n]) |
continue; |
} else { |
if (value[n] == field->value[n]) |
continue; |
} |
hid_process_event(hid, field, &field->usage[n], value[n], regs); |
continue; |
} |
if (field->value[n] >= min && field->value[n] <= max |
&& field->usage[field->value[n] - min].hid |
&& search(value, field->value[n], count)) |
hid_process_event(hid, field, &field->usage[field->value[n] - min], 0, regs); |
if (value[n] >= min && value[n] <= max |
&& field->usage[value[n] - min].hid |
&& search(field->value, value[n], count)) |
hid_process_event(hid, field, &field->usage[value[n] - min], 1, regs); |
} |
memcpy(field->value, value, count * sizeof(__s32)); |
} |
static int hid_input_report(int type, struct urb *urb, struct pt_regs *regs) |
{ |
struct hid_device *hid = urb->context; |
struct hid_report_enum *report_enum = hid->report_enum + type; |
u8 *data = urb->transfer_buffer; |
int len = urb->actual_length; |
struct hid_report *report; |
int n, size; |
if (!len) { |
dbg("empty report"); |
return -1; |
} |
#ifdef DEBUG_DATA |
printk(KERN_DEBUG __FILE__ ": report (size %u) (%snumbered)\n", len, report_enum->numbered ? "" : "un"); |
#endif |
n = 0; /* Normally report number is 0 */ |
if (report_enum->numbered) { /* Device uses numbered reports, data[0] is report number */ |
n = *data++; |
len--; |
} |
#ifdef DEBUG_DATA |
{ |
int i; |
printk(KERN_DEBUG __FILE__ ": report %d (size %u) = ", n, len); |
for (i = 0; i < len; i++) |
printk(" %02x", data[i]); |
printk("\n"); |
} |
#endif |
if (!(report = report_enum->report_id_hash[n])) { |
dbg("undefined report_id %d received", n); |
return -1; |
} |
size = ((report->size - 1) >> 3) + 1; |
if (len < size) { |
dbg("report %d is too short, (%d < %d)", report->id, len, size); |
return -1; |
} |
if (hid->claimed & HID_CLAIMED_HIDDEV) |
hiddev_report_event(hid, report); |
for (n = 0; n < report->maxfield; n++) |
hid_input_field(hid, report->field[n], data, regs); |
if (hid->claimed & HID_CLAIMED_INPUT) |
hidinput_report_event(hid, report); |
return 0; |
} |
/* |
* Input interrupt completion handler. |
*/ |
static void hid_irq_in(struct urb *urb, struct pt_regs *regs) |
{ |
struct hid_device *hid = urb->context; |
int status; |
switch (urb->status) { |
case 0: /* success */ |
hid_input_report(HID_INPUT_REPORT, urb, regs); |
break; |
case -ECONNRESET: /* unlink */ |
case -ENOENT: |
case -ESHUTDOWN: |
return; |
default: /* error */ |
dbg("nonzero status in input irq %d", urb->status); |
} |
status = usb_submit_urb (urb, SLAB_ATOMIC); |
if (status) |
err ("can't resubmit intr, %s-%s/input%d, status %d", |
hid->dev->bus->bus_name, hid->dev->devpath, |
hid->ifnum, status); |
} |
/* |
* Output the field into the report. |
*/ |
static void hid_output_field(struct hid_field *field, __u8 *data) |
{ |
unsigned count = field->report_count; |
unsigned offset = field->report_offset; |
unsigned size = field->report_size; |
unsigned n; |
for (n = 0; n < count; n++) { |
if (field->logical_minimum < 0) /* signed values */ |
implement(data, offset + n * size, size, s32ton(field->value[n], size)); |
else /* unsigned values */ |
implement(data, offset + n * size, size, field->value[n]); |
} |
} |
/* |
* Create a report. |
*/ |
void hid_output_report(struct hid_report *report, __u8 *data) |
{ |
unsigned n; |
if (report->id > 0) |
*data++ = report->id; |
for (n = 0; n < report->maxfield; n++) |
hid_output_field(report->field[n], data); |
} |
/* |
* Set a field value. The report this field belongs to has to be |
* created and transferred to the device, to set this value in the |
* device. |
*/ |
int hid_set_field(struct hid_field *field, unsigned offset, __s32 value) |
{ |
unsigned size = field->report_size; |
hid_dump_input(field->usage + offset, value); |
if (offset >= field->report_count) { |
dbg("offset (%d) exceeds report_count (%d)", offset, field->report_count); |
hid_dump_field(field, 8); |
return -1; |
} |
if (field->logical_minimum < 0) { |
if (value != snto32(s32ton(value, size), size)) { |
dbg("value %d is out of range", value); |
return -1; |
} |
} |
field->value[offset] = value; |
return 0; |
} |
int hid_find_field(struct hid_device *hid, unsigned int type, unsigned int code, struct hid_field **field) |
{ |
struct hid_report_enum *report_enum = hid->report_enum + HID_OUTPUT_REPORT; |
struct list_head *list = report_enum->report_list.next; |
int i, j; |
while (list != &report_enum->report_list) { |
struct hid_report *report = (struct hid_report *) list; |
list = list->next; |
for (i = 0; i < report->maxfield; i++) { |
*field = report->field[i]; |
for (j = 0; j < (*field)->maxusage; j++) |
if ((*field)->usage[j].type == type && (*field)->usage[j].code == code) |
return j; |
} |
} |
return -1; |
} |
/* |
* Find a report with a specified HID usage. |
*/ |
int hid_find_report_by_usage(struct hid_device *hid, __u32 wanted_usage, struct hid_report **report, int type) |
{ |
struct hid_report_enum *report_enum = hid->report_enum + type; |
struct list_head *list = report_enum->report_list.next; |
int i, j; |
while (list != &report_enum->report_list) { |
*report = (struct hid_report *) list; |
list = list->next; |
for (i = 0; i < (*report)->maxfield; i++) { |
struct hid_field *field = (*report)->field[i]; |
for (j = 0; j < field->maxusage; j++) |
if (field->logical == wanted_usage) |
return j; |
} |
} |
return -1; |
} |
int hid_find_field_in_report(struct hid_report *report, __u32 wanted_usage, struct hid_field **field) |
{ |
int i, j; |
for (i = 0; i < report->maxfield; i++) { |
*field = report->field[i]; |
for (j = 0; j < (*field)->maxusage; j++) |
if ((*field)->usage[j].hid == wanted_usage) |
return j; |
} |
return -1; |
} |
static int hid_submit_out(struct hid_device *hid) |
{ |
struct hid_report *report; |
report = hid->out[hid->outtail]; |
hid_output_report(report, hid->outbuf); |
hid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0); |
hid->urbout->dev = hid->dev; |
dbg("submitting out urb"); |
if (usb_submit_urb(hid->urbout, GFP_ATOMIC)) { |
err("usb_submit_urb(out) failed"); |
return -1; |
} |
return 0; |
} |
static int hid_submit_ctrl(struct hid_device *hid) |
{ |
struct hid_report *report; |
unsigned char dir; |
report = hid->ctrl[hid->ctrltail].report; |
dir = hid->ctrl[hid->ctrltail].dir; |
if (dir == USB_DIR_OUT) |
hid_output_report(report, hid->ctrlbuf); |
hid->urbctrl->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0); |
hid->urbctrl->pipe = (dir == USB_DIR_OUT) ? usb_sndctrlpipe(hid->dev, 0) : usb_rcvctrlpipe(hid->dev, 0); |
hid->urbctrl->dev = hid->dev; |
hid->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | dir; |
hid->cr->bRequest = (dir == USB_DIR_OUT) ? HID_REQ_SET_REPORT : HID_REQ_GET_REPORT; |
hid->cr->wValue = cpu_to_le16(((report->type + 1) << 8) | report->id); |
hid->cr->wIndex = cpu_to_le16(hid->ifnum); |
hid->cr->wLength = cpu_to_le16(hid->urbctrl->transfer_buffer_length); |
dbg("submitting ctrl urb"); |
if (usb_submit_urb(hid->urbctrl, GFP_ATOMIC)) { |
err("usb_submit_urb(ctrl) failed"); |
return -1; |
} |
return 0; |
} |
/* |
* Output interrupt completion handler. |
*/ |
static void hid_irq_out(struct urb *urb, struct pt_regs *regs) |
{ |
struct hid_device *hid = urb->context; |
unsigned long flags; |
if (urb->status) |
warn("output irq status %d received", urb->status); |
spin_lock_irqsave(&hid->outlock, flags); |
hid->outtail = (hid->outtail + 1) & (HID_OUTPUT_FIFO_SIZE - 1); |
if (hid->outhead != hid->outtail) { |
hid_submit_out(hid); |
spin_unlock_irqrestore(&hid->outlock, flags); |
return; |
} |
clear_bit(HID_OUT_RUNNING, &hid->iofl); |
spin_unlock_irqrestore(&hid->outlock, flags); |
wake_up(&hid->wait); |
} |
/* |
* Control pipe completion handler. |
*/ |
static void hid_ctrl(struct urb *urb, struct pt_regs *regs) |
{ |
struct hid_device *hid = urb->context; |
unsigned long flags; |
if (urb->status) |
warn("ctrl urb status %d received", urb->status); |
spin_lock_irqsave(&hid->ctrllock, flags); |
if (hid->ctrl[hid->ctrltail].dir == USB_DIR_IN) |
hid_input_report(hid->ctrl[hid->ctrltail].report->type, urb, regs); |
hid->ctrltail = (hid->ctrltail + 1) & (HID_CONTROL_FIFO_SIZE - 1); |
if (hid->ctrlhead != hid->ctrltail) { |
hid_submit_ctrl(hid); |
spin_unlock_irqrestore(&hid->ctrllock, flags); |
return; |
} |
clear_bit(HID_CTRL_RUNNING, &hid->iofl); |
spin_unlock_irqrestore(&hid->ctrllock, flags); |
wake_up(&hid->wait); |
} |
void hid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir) |
{ |
int head; |
unsigned long flags; |
if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN) |
return; |
if (hid->urbout && dir == USB_DIR_OUT && report->type == HID_OUTPUT_REPORT) { |
spin_lock_irqsave(&hid->outlock, flags); |
if ((head = (hid->outhead + 1) & (HID_OUTPUT_FIFO_SIZE - 1)) == hid->outtail) { |
spin_unlock_irqrestore(&hid->outlock, flags); |
warn("output queue full"); |
return; |
} |
hid->out[hid->outhead] = report; |
hid->outhead = head; |
if (!test_and_set_bit(HID_OUT_RUNNING, &hid->iofl)) |
hid_submit_out(hid); |
spin_unlock_irqrestore(&hid->outlock, flags); |
return; |
} |
spin_lock_irqsave(&hid->ctrllock, flags); |
if ((head = (hid->ctrlhead + 1) & (HID_CONTROL_FIFO_SIZE - 1)) == hid->ctrltail) { |
spin_unlock_irqrestore(&hid->ctrllock, flags); |
warn("control queue full"); |
return; |
} |
hid->ctrl[hid->ctrlhead].report = report; |
hid->ctrl[hid->ctrlhead].dir = dir; |
hid->ctrlhead = head; |
if (!test_and_set_bit(HID_CTRL_RUNNING, &hid->iofl)) |
hid_submit_ctrl(hid); |
spin_unlock_irqrestore(&hid->ctrllock, flags); |
} |
int hid_wait_io(struct hid_device *hid) |
{ |
DECLARE_WAITQUEUE(wait, current); |
int timeout = 1*HZ; |
set_current_state(TASK_UNINTERRUPTIBLE); |
add_wait_queue(&hid->wait, &wait); |
while (timeout && (test_bit(HID_CTRL_RUNNING, &hid->iofl) || |
test_bit(HID_OUT_RUNNING, &hid->iofl))) |
timeout = schedule_timeout(timeout); |
set_current_state(TASK_RUNNING); |
remove_wait_queue(&hid->wait, &wait); |
/* if (!timeout) { |
dbg("timeout waiting for ctrl or out queue to clear"); |
return -1; |
} */ |
return 0; |
} |
static int hid_get_class_descriptor(struct usb_device *dev, int ifnum, |
unsigned char type, void *buf, int size) |
{ |
return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), |
USB_REQ_GET_DESCRIPTOR, USB_RECIP_INTERFACE | USB_DIR_IN, |
(type << 8), ifnum, buf, size, HZ * USB_CTRL_GET_TIMEOUT); |
} |
int hid_open(struct hid_device *hid) |
{ |
if (hid->open++) |
return 0; |
hid->urbin->dev = hid->dev; |
if (usb_submit_urb(hid->urbin, GFP_KERNEL)) |
return -EIO; |
return 0; |
} |
void hid_close(struct hid_device *hid) |
{ |
if (!--hid->open) |
usb_unlink_urb(hid->urbin); |
} |
/* |
* Initialize all reports |
*/ |
void hid_init_reports(struct hid_device *hid) |
{ |
struct hid_report_enum *report_enum; |
struct hid_report *report; |
struct list_head *list; |
int len; |
int err, ret; |
report_enum = hid->report_enum + HID_INPUT_REPORT; |
list = report_enum->report_list.next; |
while (list != &report_enum->report_list) { |
report = (struct hid_report *) list; |
hid_submit_report(hid, report, USB_DIR_IN); |
list = list->next; |
} |
report_enum = hid->report_enum + HID_FEATURE_REPORT; |
list = report_enum->report_list.next; |
while (list != &report_enum->report_list) { |
report = (struct hid_report *) list; |
hid_submit_report(hid, report, USB_DIR_IN); |
list = list->next; |
} |
err = 0; |
while ((ret = hid_wait_io(hid))) { |
err |= ret; |
if (test_bit(HID_CTRL_RUNNING, &hid->iofl)) |
usb_unlink_urb(hid->urbctrl); |
if (test_bit(HID_OUT_RUNNING, &hid->iofl)) |
usb_unlink_urb(hid->urbout); |
} |
if (err) |
warn("timeout initializing reports\n"); |
report_enum = hid->report_enum + HID_INPUT_REPORT; |
list = report_enum->report_list.next; |
while (list != &report_enum->report_list) { |
report = (struct hid_report *) list; |
len = ((report->size - 1) >> 3) + 1 + report_enum->numbered; |
if (len > hid->urbin->transfer_buffer_length) |
hid->urbin->transfer_buffer_length = len < HID_BUFFER_SIZE ? len : HID_BUFFER_SIZE; |
usb_control_msg(hid->dev, usb_sndctrlpipe(hid->dev, 0), |
0x0a, USB_TYPE_CLASS | USB_RECIP_INTERFACE, report->id, |
hid->ifnum, NULL, 0, HZ * USB_CTRL_SET_TIMEOUT); |
list = list->next; |
} |
} |
#define USB_VENDOR_ID_WACOM 0x056a |
#define USB_DEVICE_ID_WACOM_PENPARTNER 0x0000 |
#define USB_DEVICE_ID_WACOM_GRAPHIRE 0x0010 |
#define USB_DEVICE_ID_WACOM_INTUOS 0x0020 |
#define USB_DEVICE_ID_WACOM_PL 0x0030 |
#define USB_DEVICE_ID_WACOM_INTUOS2 0x0040 |
#define USB_VENDOR_ID_KBGEAR 0x084e |
#define USB_DEVICE_ID_KBGEAR_JAMSTUDIO 0x1001 |
#define USB_VENDOR_ID_AIPTEK 0x08ca |
#define USB_DEVICE_ID_AIPTEK_6000 0x0020 |
#define USB_VENDOR_ID_GRIFFIN 0x077d |
#define USB_DEVICE_ID_POWERMATE 0x0410 |
#define USB_DEVICE_ID_SOUNDKNOB 0x04AA |
#define USB_VENDOR_ID_ATEN 0x0557 |
#define USB_DEVICE_ID_ATEN_UC100KM 0x2004 |
#define USB_DEVICE_ID_ATEN_CS124U 0x2202 |
#define USB_DEVICE_ID_ATEN_2PORTKVM 0x2204 |
#define USB_DEVICE_ID_ATEN_4PORTKVM 0x2205 |
#define USB_DEVICE_ID_ATEN_4PORTKVMC 0x2208 |
#define USB_VENDOR_ID_TOPMAX 0x0663 |
#define USB_DEVICE_ID_TOPMAX_COBRAPAD 0x0103 |
#define USB_VENDOR_ID_HAPP 0x078b |
#define USB_DEVICE_ID_UGCI_DRIVING 0x0010 |
#define USB_DEVICE_ID_UGCI_FLYING 0x0020 |
#define USB_DEVICE_ID_UGCI_FIGHTING 0x0030 |
#define USB_VENDOR_ID_MGE 0x0463 |
#define USB_DEVICE_ID_MGE_UPS 0xffff |
#define USB_DEVICE_ID_MGE_UPS1 0x0001 |
#define USB_VENDOR_ID_ONTRAK 0x0a07 |
#define USB_DEVICE_ID_ONTRAK_ADU100 0x0064 |
#define USB_VENDOR_ID_TANGTOP 0x0d3d |
#define USB_DEVICE_ID_TANGTOP_USBPS2 0x0001 |
#define USB_VENDOR_ID_ESSENTIAL_REALITY 0x0d7f |
#define USB_DEVICE_ID_ESSENTIAL_REALITY_P5 0x0100 |
#define USB_VENDOR_ID_A4TECH 0x09DA |
#define USB_DEVICE_ID_A4TECH_WCP32PU 0x0006 |
struct hid_blacklist { |
__u16 idVendor; |
__u16 idProduct; |
unsigned quirks; |
} hid_blacklist[] = { |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_PENPARTNER, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_GRAPHIRE, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_GRAPHIRE + 1, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_GRAPHIRE + 2, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS + 1, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS + 2, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS + 3, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS + 4, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_PL, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_PL + 1, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_PL + 2, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_PL + 3, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_PL + 4, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_PL + 5, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS2, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS2 + 1, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS2 + 2, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS2 + 3, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS2 + 4, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_KBGEAR, USB_DEVICE_ID_KBGEAR_JAMSTUDIO, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_6000, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_GRIFFIN, USB_DEVICE_ID_POWERMATE, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_GRIFFIN, USB_DEVICE_ID_SOUNDKNOB, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_UC100KM, HID_QUIRK_NOGET }, |
{ USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS124U, HID_QUIRK_NOGET }, |
{ USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_2PORTKVM, HID_QUIRK_NOGET }, |
{ USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVM, HID_QUIRK_NOGET }, |
{ USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVMC, HID_QUIRK_NOGET }, |
{ USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_HIDDEV }, |
{ USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS1, HID_QUIRK_HIDDEV }, |
{ USB_VENDOR_ID_TOPMAX, USB_DEVICE_ID_TOPMAX_COBRAPAD, HID_QUIRK_BADPAD }, |
{ USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING, HID_QUIRK_BADPAD|HID_QUIRK_MULTI_INPUT }, |
{ USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FLYING, HID_QUIRK_BADPAD|HID_QUIRK_MULTI_INPUT }, |
{ USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FIGHTING, HID_QUIRK_BADPAD|HID_QUIRK_MULTI_INPUT }, |
{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 100, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 200, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 300, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 400, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 500, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_TANGTOP, USB_DEVICE_ID_TANGTOP_USBPS2, HID_QUIRK_NOGET }, |
{ USB_VENDOR_ID_ESSENTIAL_REALITY, USB_DEVICE_ID_ESSENTIAL_REALITY_P5, HID_QUIRK_IGNORE }, |
{ USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU, HID_QUIRK_2WHEEL_MOUSE_HACK }, |
{ 0, 0 } |
}; |
static int hid_alloc_buffers(struct usb_device *dev, struct hid_device *hid) |
{ |
if (!(hid->inbuf = usb_buffer_alloc(dev, HID_BUFFER_SIZE, SLAB_ATOMIC, &hid->inbuf_dma))) |
return -1; |
if (!(hid->outbuf = usb_buffer_alloc(dev, HID_BUFFER_SIZE, SLAB_ATOMIC, &hid->outbuf_dma))) |
return -1; |
if (!(hid->cr = usb_buffer_alloc(dev, sizeof(*(hid->cr)), SLAB_ATOMIC, &hid->cr_dma))) |
return -1; |
if (!(hid->ctrlbuf = usb_buffer_alloc(dev, HID_BUFFER_SIZE, SLAB_ATOMIC, &hid->ctrlbuf_dma))) |
return -1; |
return 0; |
} |
static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid) |
{ |
if (hid->inbuf) |
usb_buffer_free(dev, HID_BUFFER_SIZE, hid->inbuf, hid->inbuf_dma); |
if (hid->outbuf) |
usb_buffer_free(dev, HID_BUFFER_SIZE, hid->outbuf, hid->outbuf_dma); |
if (hid->cr) |
usb_buffer_free(dev, sizeof(*(hid->cr)), hid->cr, hid->cr_dma); |
if (hid->ctrlbuf) |
usb_buffer_free(dev, HID_BUFFER_SIZE, hid->ctrlbuf, hid->ctrlbuf_dma); |
} |
static struct hid_device *usb_hid_configure(struct usb_interface *intf) |
{ |
struct usb_host_interface *interface = intf->altsetting + intf->act_altsetting; |
struct usb_device *dev = interface_to_usbdev (intf); |
struct hid_descriptor *hdesc; |
struct hid_device *hid; |
unsigned quirks = 0, rsize = 0; |
char *buf, *rdesc; |
int n; |
for (n = 0; hid_blacklist[n].idVendor; n++) |
if ((hid_blacklist[n].idVendor == dev->descriptor.idVendor) && |
(hid_blacklist[n].idProduct == dev->descriptor.idProduct)) |
quirks = hid_blacklist[n].quirks; |
if (quirks & HID_QUIRK_IGNORE) |
return NULL; |
if (usb_get_extra_descriptor(interface, HID_DT_HID, &hdesc) && ((!interface->desc.bNumEndpoints) || |
usb_get_extra_descriptor(&interface->endpoint[0], HID_DT_HID, &hdesc))) { |
dbg("class descriptor not present\n"); |
return NULL; |
} |
for (n = 0; n < hdesc->bNumDescriptors; n++) |
if (hdesc->desc[n].bDescriptorType == HID_DT_REPORT) |
rsize = le16_to_cpu(hdesc->desc[n].wDescriptorLength); |
if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) { |
dbg("weird size of report descriptor (%u)", rsize); |
return NULL; |
} |
if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) { |
dbg("couldn't allocate rdesc memory"); |
return NULL; |
} |
if ((n = hid_get_class_descriptor(dev, interface->desc.bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) { |
dbg("reading report descriptor failed"); |
kfree(rdesc); |
return NULL; |
} |
#ifdef DEBUG_DATA |
printk(KERN_DEBUG __FILE__ ": report descriptor (size %u, read %d) = ", rsize, n); |
for (n = 0; n < rsize; n++) |
printk(" %02x", (unsigned char) rdesc[n]); |
printk("\n"); |
#endif |
if (!(hid = hid_parse_report(rdesc, rsize))) { |
dbg("parsing report descriptor failed"); |
kfree(rdesc); |
return NULL; |
} |
kfree(rdesc); |
hid->quirks = quirks; |
if (hid_alloc_buffers(dev, hid)) { |
hid_free_buffers(dev, hid); |
goto fail; |
} |
for (n = 0; n < interface->desc.bNumEndpoints; n++) { |
struct usb_endpoint_descriptor *endpoint; |
int pipe; |
endpoint = &interface->endpoint[n].desc; |
if ((endpoint->bmAttributes & 3) != 3) /* Not an interrupt endpoint */ |
continue; |
if (endpoint->bEndpointAddress & USB_DIR_IN) { |
if (hid->urbin) |
continue; |
if (!(hid->urbin = usb_alloc_urb(0, GFP_KERNEL))) |
goto fail; |
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); |
usb_fill_int_urb(hid->urbin, dev, pipe, hid->inbuf, 0, |
hid_irq_in, hid, endpoint->bInterval); |
hid->urbin->transfer_dma = hid->inbuf_dma; |
hid->urbin->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; |
} else { |
if (hid->urbout) |
continue; |
if (!(hid->urbout = usb_alloc_urb(0, GFP_KERNEL))) |
goto fail; |
pipe = usb_sndbulkpipe(dev, endpoint->bEndpointAddress); |
usb_fill_bulk_urb(hid->urbout, dev, pipe, hid->outbuf, 0, |
hid_irq_out, hid); |
hid->urbout->transfer_dma = hid->outbuf_dma; |
hid->urbout->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; |
} |
} |
if (!hid->urbin) { |
err("couldn't find an input interrupt endpoint"); |
goto fail; |
} |
init_waitqueue_head(&hid->wait); |
hid->outlock = SPIN_LOCK_UNLOCKED; |
hid->ctrllock = SPIN_LOCK_UNLOCKED; |
hid->version = le16_to_cpu(hdesc->bcdHID); |
hid->country = hdesc->bCountryCode; |
hid->dev = dev; |
hid->ifnum = interface->desc.bInterfaceNumber; |
hid->name[0] = 0; |
if (!(buf = kmalloc(64, GFP_KERNEL))) |
goto fail; |
if (usb_string(dev, dev->descriptor.iManufacturer, buf, 64) > 0) { |
strcat(hid->name, buf); |
if (usb_string(dev, dev->descriptor.iProduct, buf, 64) > 0) |
snprintf26(hid->name, 64, "%s %s", hid->name, buf); |
} else if (usb_string(dev, dev->descriptor.iProduct, buf, 128) > 0) { |
snprintf26(hid->name, 128, "%s", buf); |
} else |
snprintf26(hid->name, 128, "%04x:%04x", dev->descriptor.idVendor, dev->descriptor.idProduct); |
usb_make_path(dev, buf, 64); |
snprintf26(hid->phys, 64, "%s/input%d", buf, |
intf->altsetting[0].desc.bInterfaceNumber); |
if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0) |
hid->uniq[0] = 0; |
kfree(buf); |
hid->urbctrl = usb_alloc_urb(0, GFP_KERNEL); |
if (!hid->urbctrl) |
goto fail; |
usb_fill_control_urb(hid->urbctrl, dev, 0, (void *) hid->cr, |
hid->ctrlbuf, 1, hid_ctrl, hid); |
hid->urbctrl->setup_dma = hid->cr_dma; |
hid->urbctrl->transfer_dma = hid->ctrlbuf_dma; |
hid->urbctrl->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP |
| URB_NO_SETUP_DMA_MAP); |
return hid; |
fail: |
if (hid->urbin) |
usb_free_urb(hid->urbin); |
if (hid->urbout) |
usb_free_urb(hid->urbout); |
if (hid->urbctrl) |
usb_free_urb(hid->urbctrl); |
hid_free_buffers(dev, hid); |
hid_free_device(hid); |
return NULL; |
} |
static void hid_disconnect(struct usb_interface *intf) |
{ |
struct hid_device *hid = usb_get_intfdata (intf); |
if (!hid) |
return; |
usb_set_intfdata(intf, NULL); |
usb_unlink_urb(hid->urbin); |
usb_unlink_urb(hid->urbout); |
usb_unlink_urb(hid->urbctrl); |
if (hid->claimed & HID_CLAIMED_INPUT) |
hidinput_disconnect(hid); |
if (hid->claimed & HID_CLAIMED_HIDDEV) |
hiddev_disconnect(hid); |
usb_free_urb(hid->urbin); |
usb_free_urb(hid->urbctrl); |
if (hid->urbout) |
usb_free_urb(hid->urbout); |
hid_free_buffers(hid->dev, hid); |
hid_free_device(hid); |
} |
static int hid_probe (struct usb_interface *intf, const struct usb_device_id *id) |
{ |
struct hid_device *hid; |
char path[64]; |
int i; |
char *c; |
dbg("HID probe called for ifnum %d", |
intf->altsetting->desc.bInterfaceNumber); |
if (!(hid = usb_hid_configure(intf))) |
return -EIO; |
hid_init_reports(hid); |
hid_dump_device(hid); |
if (!hidinput_connect(hid)) |
hid->claimed |= HID_CLAIMED_INPUT; |
if (!hiddev_connect(hid)) |
hid->claimed |= HID_CLAIMED_HIDDEV; |
usb_set_intfdata(intf, hid); |
if (!hid->claimed) { |
printk ("HID device not claimed by input or hiddev\n"); |
hid_disconnect(intf); |
return -EIO; |
} |
printk(KERN_INFO); |
if (hid->claimed & HID_CLAIMED_INPUT) |
printk("input"); |
if (hid->claimed == (HID_CLAIMED_INPUT | HID_CLAIMED_HIDDEV)) |
printk(","); |
if (hid->claimed & HID_CLAIMED_HIDDEV) |
printk("hiddev%d", hid->minor); |
c = "Device"; |
for (i = 0; i < hid->maxcollection; i++) { |
if (hid->collection[i].type == HID_COLLECTION_APPLICATION && |
(hid->collection[i].usage & HID_USAGE_PAGE) == HID_UP_GENDESK && |
(hid->collection[i].usage & 0xffff) < ARRAY_SIZE(hid_types)) { |
c = hid_types[hid->collection[i].usage & 0xffff]; |
break; |
} |
} |
usb_make_path(interface_to_usbdev(intf), path, 63); |
printk(": USB HID v%x.%02x %s [%s] on %s\n", |
hid->version >> 8, hid->version & 0xff, c, hid->name, path); |
return 0; |
} |
static struct usb_device_id hid_usb_ids [] = { |
{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, |
.bInterfaceClass = USB_INTERFACE_CLASS_HID }, |
{ } /* Terminating entry */ |
}; |
MODULE_DEVICE_TABLE (usb, hid_usb_ids); |
static struct usb_driver hid_driver = { |
.owner = THIS_MODULE, |
.name = "hid", |
.probe = hid_probe, |
.disconnect = hid_disconnect, |
.id_table = hid_usb_ids, |
}; |
/*static*/ int __init hid_init(void) |
{ |
int retval; |
retval = hiddev_init(); |
if (retval) |
goto hiddev_init_fail; |
retval = usb_register(&hid_driver); |
if (retval) |
goto usb_register_fail; |
info(DRIVER_VERSION ":" DRIVER_DESC); |
return 0; |
usb_register_fail: |
hiddev_exit(); |
hiddev_init_fail: |
return retval; |
} |
/*static*/ void __exit hid_exit(void) |
{ |
hiddev_exit(); |
usb_deregister(&hid_driver); |
} |
module_init(hid_init); |
module_exit(hid_exit); |
MODULE_AUTHOR(DRIVER_AUTHOR); |
MODULE_DESCRIPTION(DRIVER_DESC); |
MODULE_LICENSE(DRIVER_LICENSE); |
/shark/trunk/drivers/usb/media/pwc.h |
---|
0,0 → 1,269 |
/* (C) 1999-2003 Nemosoft Unv. (webcam@smcc.demon.nl) |
This program is free software; you can redistribute it and/or modify |
it under the terms of the GNU General Public License as published by |
the Free Software Foundation; either version 2 of the License, or |
(at your option) any later version. |
This program is distributed in the hope that it will be useful, |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
GNU General Public License for more details. |
You should have received a copy of the GNU General Public License |
along with this program; if not, write to the Free Software |
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
*/ |
#ifndef PWC_H |
#define PWC_H |
#include <linux/version.h> |
#include <linux/config.h> |
#include <linux/module.h> |
#include <linux/usb.h> |
#include <linux/spinlock.h> |
#include <linux/videodev.h> |
#include <linux/wait.h> |
#include <linux/smp_lock.h> |
#include <asm/semaphore.h> |
#include <asm/errno.h> |
#include "pwc-ioctl.h" |
/* Defines and structures for the Philips webcam */ |
/* Used for checking memory corruption/pointer validation */ |
#define PWC_MAGIC 0x89DC10ABUL |
#undef PWC_MAGIC |
/* Turn some debugging options on/off */ |
#define PWC_DEBUG 0 |
/* Trace certain actions in the driver */ |
#define TRACE_MODULE 0x0001 |
#define TRACE_PROBE 0x0002 |
#define TRACE_OPEN 0x0004 |
#define TRACE_READ 0x0008 |
#define TRACE_MEMORY 0x0010 |
#define TRACE_FLOW 0x0020 |
#define TRACE_SIZE 0x0040 |
#define TRACE_PWCX 0x0080 |
#define TRACE_SEQUENCE 0x1000 |
#define Trace(R, A...) if (pwc_trace & R) printk(KERN_DEBUG PWC_NAME " " A) |
#define Debug(A...) printk(KERN_DEBUG PWC_NAME " " A) |
#define Info(A...) printk(KERN_INFO PWC_NAME " " A) |
#define Err(A...) printk(KERN_ERR PWC_NAME " " A) |
/* Defines for ToUCam cameras */ |
#define TOUCAM_HEADER_SIZE 8 |
#define TOUCAM_TRAILER_SIZE 4 |
#define FEATURE_MOTOR_PANTILT 0x0001 |
/* Version block */ |
#define PWC_MAJOR 9 |
#define PWC_MINOR 0 |
#define PWC_VERSION "9.0-BETA-1" |
#define PWC_NAME "pwc" |
/* Turn certain features on/off */ |
#define PWC_INT_PIPE 0 |
/* Ignore errors in the first N frames, to allow for startup delays */ |
#define FRAME_LOWMARK 5 |
/* Size and number of buffers for the ISO pipe. */ |
#define MAX_ISO_BUFS 2 |
#define ISO_FRAMES_PER_DESC 10 |
#define ISO_MAX_FRAME_SIZE 960 |
#define ISO_BUFFER_SIZE (ISO_FRAMES_PER_DESC * ISO_MAX_FRAME_SIZE) |
/* Frame buffers: contains compressed or uncompressed video data. */ |
#define MAX_FRAMES 5 |
/* Maximum size after decompression is 640x480 YUV data, 1.5 * 640 * 480 */ |
#define PWC_FRAME_SIZE (460800 + TOUCAM_HEADER_SIZE + TOUCAM_TRAILER_SIZE) |
/* Absolute maximum number of buffers available for mmap() */ |
#define MAX_IMAGES 10 |
/* The following structures were based on cpia.h. Why reinvent the wheel? :-) */ |
struct pwc_iso_buf |
{ |
void *data; |
int length; |
int read; |
struct urb *urb; |
}; |
/* intermediate buffers with raw data from the USB cam */ |
struct pwc_frame_buf |
{ |
void *data; |
volatile int filled; /* number of bytes filled */ |
struct pwc_frame_buf *next; /* list */ |
#if PWC_DEBUG |
int sequence; /* Sequence number */ |
#endif |
}; |
struct pwc_device |
{ |
struct video_device vdev; |
#ifdef PWC_MAGIC |
int magic; |
#endif |
/* Pointer to our usb_device */ |
struct usb_device *udev; |
int type; /* type of cam (645, 646, 675, 680, 690, 720, 730, 740, 750) */ |
int release; /* release number */ |
int features; /* feature bits */ |
int error_status; /* set when something goes wrong with the cam (unplugged, USB errors) */ |
int usb_init; /* set when the cam has been initialized over USB */ |
/*** Video data ***/ |
int vopen; /* flag */ |
int vendpoint; /* video isoc endpoint */ |
int vcinterface; /* video control interface */ |
int valternate; /* alternate interface needed */ |
int vframes, vsize; /* frames-per-second & size (see PSZ_*) */ |
int vpalette; /* palette: 420P, RAW or RGBBAYER */ |
int vframe_count; /* received frames */ |
int vframes_dumped; /* counter for dumped frames */ |
int vframes_error; /* frames received in error */ |
int vmax_packet_size; /* USB maxpacket size */ |
int vlast_packet_size; /* for frame synchronisation */ |
int visoc_errors; /* number of contiguous ISOC errors */ |
int vcompression; /* desired compression factor */ |
int vbandlength; /* compressed band length; 0 is uncompressed */ |
char vsnapshot; /* snapshot mode */ |
char vsync; /* used by isoc handler */ |
char vmirror; /* for ToUCaM series */ |
int cmd_len; |
unsigned char cmd_buf[13]; |
/* The image acquisition requires 3 to 4 steps: |
1. data is gathered in short packets from the USB controller |
2. data is synchronized and packed into a frame buffer |
3a. in case data is compressed, decompress it directly into image buffer |
3b. in case data is uncompressed, copy into image buffer with viewport |
4. data is transferred to the user process |
Note that MAX_ISO_BUFS != MAX_FRAMES != MAX_IMAGES.... |
We have in effect a back-to-back-double-buffer system. |
*/ |
/* 1: isoc */ |
struct pwc_iso_buf sbuf[MAX_ISO_BUFS]; |
char iso_init; |
/* 2: frame */ |
struct pwc_frame_buf *fbuf; /* all frames */ |
struct pwc_frame_buf *empty_frames, *empty_frames_tail; /* all empty frames */ |
struct pwc_frame_buf *full_frames, *full_frames_tail; /* all filled frames */ |
struct pwc_frame_buf *fill_frame; /* frame currently being filled */ |
struct pwc_frame_buf *read_frame; /* frame currently read by user process */ |
int frame_header_size, frame_trailer_size; |
int frame_size; |
int frame_total_size; /* including header & trailer */ |
int drop_frames; |
#if PWC_DEBUG |
int sequence; /* Debugging aid */ |
#endif |
/* 3: decompression */ |
struct pwc_decompressor *decompressor; /* function block with decompression routines */ |
void *decompress_data; /* private data for decompression engine */ |
/* 4: image */ |
/* We have an 'image' and a 'view', where 'image' is the fixed-size image |
as delivered by the camera, and 'view' is the size requested by the |
program. The camera image is centered in this viewport, laced with |
a gray or black border. view_min <= image <= view <= view_max; |
*/ |
int image_mask; /* bitmask of supported sizes */ |
struct pwc_coord view_min, view_max; /* minimum and maximum viewable sizes */ |
struct pwc_coord abs_max; /* maximum supported size with compression */ |
struct pwc_coord image, view; /* image and viewport size */ |
struct pwc_coord offset; /* offset within the viewport */ |
void *image_data; /* total buffer, which is subdivided into ... */ |
void *image_ptr[MAX_IMAGES]; /* ...several images... */ |
int fill_image; /* ...which are rotated. */ |
int len_per_image; /* length per image */ |
int image_read_pos; /* In case we read data in pieces, keep track of were we are in the imagebuffer */ |
int image_used[MAX_IMAGES]; /* For MCAPTURE and SYNC */ |
struct semaphore modlock; /* to prevent races in video_open(), etc */ |
spinlock_t ptrlock; /* for manipulating the buffer pointers */ |
/*** motorized pan/tilt feature */ |
struct pwc_mpt_range angle_range; |
int pan_angle; /* in degrees * 100 */ |
int tilt_angle; /* absolute angle; 0,0 is home position */ |
/*** Misc. data ***/ |
wait_queue_head_t frameq; /* When waiting for a frame to finish... */ |
#if PWC_INT_PIPE |
void *usb_int_handler; /* for the interrupt endpoint */ |
#endif |
}; |
#ifdef __cplusplus |
extern "C" { |
#endif |
/* Global variables */ |
extern int pwc_trace; |
extern int pwc_preferred_compression; |
/** functions in pwc-if.c */ |
int pwc_try_video_mode(struct pwc_device *pdev, int width, int height, int new_fps, int new_compression, int new_snapshot); |
/** Functions in pwc-misc.c */ |
/* sizes in pixels */ |
extern struct pwc_coord pwc_image_sizes[PSZ_MAX]; |
int pwc_decode_size(struct pwc_device *pdev, int width, int height); |
void pwc_construct(struct pwc_device *pdev); |
/** Functions in pwc-ctrl.c */ |
/* Request a certain video mode. Returns < 0 if not possible */ |
extern int pwc_set_video_mode(struct pwc_device *pdev, int width, int height, int frames, int compression, int snapshot); |
/* Calculate the number of bytes per image (not frame) */ |
extern void pwc_set_image_buffer_size(struct pwc_device *pdev); |
/* Various controls; should be obvious. Value 0..65535, or < 0 on error */ |
extern int pwc_get_brightness(struct pwc_device *pdev); |
extern int pwc_set_brightness(struct pwc_device *pdev, int value); |
extern int pwc_get_contrast(struct pwc_device *pdev); |
extern int pwc_set_contrast(struct pwc_device *pdev, int value); |
extern int pwc_get_gamma(struct pwc_device *pdev); |
extern int pwc_set_gamma(struct pwc_device *pdev, int value); |
extern int pwc_get_saturation(struct pwc_device *pdev); |
extern int pwc_set_saturation(struct pwc_device *pdev, int value); |
extern int pwc_set_leds(struct pwc_device *pdev, int on_value, int off_value); |
extern int pwc_get_leds(struct pwc_device *pdev, int *on_value, int *off_value); |
extern int pwc_get_cmos_sensor(struct pwc_device *pdev); |
/* Power down or up the camera; not supported by all models */ |
extern int pwc_camera_power(struct pwc_device *pdev, int power); |
/* Private ioctl()s; see pwc-ioctl.h */ |
extern int pwc_ioctl(struct pwc_device *pdev, unsigned int cmd, void *arg); |
/** pwc-uncompress.c */ |
/* Expand frame to image, possibly including decompression. Uses read_frame and fill_image */ |
extern int pwc_decompress(struct pwc_device *pdev); |
#ifdef __cplusplus |
} |
#endif |
#endif |
/shark/trunk/drivers/usb/media/pwc-misc.c |
---|
0,0 → 1,122 |
/* Linux driver for Philips webcam |
Various miscellaneous functions and tables. |
(C) 1999-2003 Nemosoft Unv. (webcam@smcc.demon.nl) |
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/slab.h> |
#include "pwc.h" |
struct pwc_coord pwc_image_sizes[PSZ_MAX] = |
{ |
{ 128, 96, 0 }, |
{ 160, 120, 0 }, |
{ 176, 144, 0 }, |
{ 320, 240, 0 }, |
{ 352, 288, 0 }, |
{ 640, 480, 0 }, |
}; |
/* x,y -> PSZ_ */ |
int pwc_decode_size(struct pwc_device *pdev, int width, int height) |
{ |
int i, find; |
/* Make sure we don't go beyond our max size */ |
if (width > pdev->view_max.x || height > pdev->view_max.y) |
return -1; |
/* Find the largest size supported by the camera that fits into the |
requested size. |
*/ |
find = -1; |
for (i = 0; i < PSZ_MAX; i++) { |
if (pdev->image_mask & (1 << i)) { |
if (pwc_image_sizes[i].x <= width && pwc_image_sizes[i].y <= height) |
find = i; |
} |
} |
return find; |
} |
/* initialize variables depending on type and decompressor*/ |
void pwc_construct(struct pwc_device *pdev) |
{ |
switch(pdev->type) { |
case 645: |
case 646: |
pdev->view_min.x = 128; |
pdev->view_min.y = 96; |
pdev->view_max.x = 352; |
pdev->view_max.y = 288; |
pdev->image_mask = 1 << PSZ_SQCIF | 1 << PSZ_QCIF | 1 << PSZ_CIF; |
pdev->vcinterface = 2; |
pdev->vendpoint = 4; |
pdev->frame_header_size = 0; |
pdev->frame_trailer_size = 0; |
break; |
case 675: |
case 680: |
case 690: |
pdev->view_min.x = 128; |
pdev->view_min.y = 96; |
/* Anthill bug #38: PWC always reports max size, even without PWCX */ |
if (pdev->decompressor != NULL) { |
pdev->view_max.x = 640; |
pdev->view_max.y = 480; |
pdev->image_mask = 1 << PSZ_SQCIF | 1 << PSZ_QSIF | 1 << PSZ_QCIF | 1 << PSZ_SIF | 1 << PSZ_CIF | 1 << PSZ_VGA; |
} |
else { |
pdev->view_max.x = 352; |
pdev->view_max.y = 288; |
pdev->image_mask = 1 << PSZ_SQCIF | 1 << PSZ_QSIF | 1 << PSZ_QCIF | 1 << PSZ_SIF | 1 << PSZ_CIF; |
} |
pdev->vcinterface = 3; |
pdev->vendpoint = 4; |
pdev->frame_header_size = 0; |
pdev->frame_trailer_size = 0; |
break; |
case 720: |
case 730: |
case 740: |
case 750: |
pdev->view_min.x = 160; |
pdev->view_min.y = 120; |
/* Anthill bug #38: PWC always reports max size, even without PWCX */ |
if (pdev->decompressor != NULL) { |
pdev->view_max.x = 640; |
pdev->view_max.y = 480; |
pdev->image_mask = 1 << PSZ_QSIF | 1 << PSZ_SIF | 1 << PSZ_VGA; |
} |
else { |
/* Tell CIF, even though SIF really is the maximum, but some tools really need CIF */ |
pdev->view_max.x = 352; |
pdev->view_max.y = 288; |
pdev->image_mask = 1 << PSZ_QSIF | 1 << PSZ_SIF; |
} |
pdev->vcinterface = 3; |
pdev->vendpoint = 5; |
pdev->frame_header_size = TOUCAM_HEADER_SIZE; |
pdev->frame_trailer_size = TOUCAM_TRAILER_SIZE; |
break; |
} |
pdev->view_min.size = pdev->view_min.x * pdev->view_min.y; |
pdev->view_max.size = pdev->view_max.x * pdev->view_max.y; |
/* length of image, in YUV format */ |
pdev->len_per_image = (pdev->view_max.size * 3) / 2; |
} |
/shark/trunk/drivers/usb/media/pwc_timon.h |
---|
0,0 → 1,270 |
/* SQCIF */ |
{ |
/* 5 fps */ |
{ |
{1, 140, 0, {0x05, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x8C, 0xFC, 0x80, 0x02}}, |
{1, 140, 0, {0x05, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x8C, 0xFC, 0x80, 0x02}}, |
{1, 140, 0, {0x05, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x8C, 0xFC, 0x80, 0x02}}, |
{1, 140, 0, {0x05, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x8C, 0xFC, 0x80, 0x02}}, |
}, |
/* 10 fps */ |
{ |
{2, 280, 0, {0x04, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x18, 0xA9, 0x80, 0x02}}, |
{2, 280, 0, {0x04, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x18, 0xA9, 0x80, 0x02}}, |
{2, 280, 0, {0x04, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x18, 0xA9, 0x80, 0x02}}, |
{2, 280, 0, {0x04, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x18, 0xA9, 0x80, 0x02}}, |
}, |
/* 15 fps */ |
{ |
{3, 410, 0, {0x03, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x9A, 0x71, 0x80, 0x02}}, |
{3, 410, 0, {0x03, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x9A, 0x71, 0x80, 0x02}}, |
{3, 410, 0, {0x03, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x9A, 0x71, 0x80, 0x02}}, |
{3, 410, 0, {0x03, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x9A, 0x71, 0x80, 0x02}}, |
}, |
/* 20 fps */ |
{ |
{4, 559, 0, {0x02, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x2F, 0x56, 0x80, 0x02}}, |
{4, 559, 0, {0x02, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x2F, 0x56, 0x80, 0x02}}, |
{4, 559, 0, {0x02, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x2F, 0x56, 0x80, 0x02}}, |
{4, 559, 0, {0x02, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x2F, 0x56, 0x80, 0x02}}, |
}, |
/* 25 fps */ |
{ |
{5, 659, 0, {0x01, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x93, 0x46, 0x80, 0x02}}, |
{5, 659, 0, {0x01, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x93, 0x46, 0x80, 0x02}}, |
{5, 659, 0, {0x01, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x93, 0x46, 0x80, 0x02}}, |
{5, 659, 0, {0x01, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x93, 0x46, 0x80, 0x02}}, |
}, |
/* 30 fps */ |
{ |
{7, 838, 0, {0x00, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x46, 0x3B, 0x80, 0x02}}, |
{7, 838, 0, {0x00, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x46, 0x3B, 0x80, 0x02}}, |
{7, 838, 0, {0x00, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x46, 0x3B, 0x80, 0x02}}, |
{7, 838, 0, {0x00, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x46, 0x3B, 0x80, 0x02}}, |
}, |
}, |
/* QSIF */ |
{ |
/* 5 fps */ |
{ |
{1, 146, 0, {0x2D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0xFC, 0xC0, 0x02}}, |
{1, 146, 0, {0x2D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0xFC, 0xC0, 0x02}}, |
{1, 146, 0, {0x2D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0xFC, 0xC0, 0x02}}, |
{1, 146, 0, {0x2D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0xFC, 0xC0, 0x02}}, |
}, |
/* 10 fps */ |
{ |
{2, 291, 0, {0x2C, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x23, 0xA1, 0xC0, 0x02}}, |
{1, 191, 630, {0x2C, 0xF4, 0x05, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xBF, 0xF4, 0xC0, 0x02}}, |
{1, 191, 630, {0x2C, 0xF4, 0x05, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xBF, 0xF4, 0xC0, 0x02}}, |
{1, 191, 630, {0x2C, 0xF4, 0x05, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xBF, 0xF4, 0xC0, 0x02}}, |
}, |
/* 15 fps */ |
{ |
{3, 437, 0, {0x2B, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xB5, 0x6D, 0xC0, 0x02}}, |
{2, 291, 640, {0x2B, 0xF4, 0x05, 0x13, 0xF7, 0x13, 0x2F, 0x13, 0x08, 0x23, 0xA1, 0xC0, 0x02}}, |
{2, 291, 640, {0x2B, 0xF4, 0x05, 0x13, 0xF7, 0x13, 0x2F, 0x13, 0x08, 0x23, 0xA1, 0xC0, 0x02}}, |
{1, 191, 420, {0x2B, 0xF4, 0x0D, 0x0D, 0x1B, 0x0C, 0x53, 0x1E, 0x08, 0xBF, 0xF4, 0xC0, 0x02}}, |
}, |
/* 20 fps */ |
{ |
{4, 588, 0, {0x2A, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x4C, 0x52, 0xC0, 0x02}}, |
{3, 447, 730, {0x2A, 0xF4, 0x05, 0x16, 0xC9, 0x16, 0x01, 0x0E, 0x18, 0xBF, 0x69, 0xC0, 0x02}}, |
{2, 292, 476, {0x2A, 0xF4, 0x0D, 0x0E, 0xD8, 0x0E, 0x10, 0x19, 0x18, 0x24, 0xA1, 0xC0, 0x02}}, |
{1, 192, 312, {0x2A, 0xF4, 0x1D, 0x09, 0xB3, 0x08, 0xEB, 0x1E, 0x18, 0xC0, 0xF4, 0xC0, 0x02}}, |
}, |
/* 25 fps */ |
{ |
{5, 703, 0, {0x29, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xBF, 0x42, 0xC0, 0x02}}, |
{3, 447, 610, {0x29, 0xF4, 0x05, 0x13, 0x0B, 0x12, 0x43, 0x14, 0x18, 0xBF, 0x69, 0xC0, 0x02}}, |
{2, 292, 398, {0x29, 0xF4, 0x0D, 0x0C, 0x6C, 0x0B, 0xA4, 0x1E, 0x18, 0x24, 0xA1, 0xC0, 0x02}}, |
{1, 192, 262, {0x29, 0xF4, 0x25, 0x08, 0x23, 0x07, 0x5B, 0x1E, 0x18, 0xC0, 0xF4, 0xC0, 0x02}}, |
}, |
/* 30 fps */ |
{ |
{8, 873, 0, {0x28, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x69, 0x37, 0xC0, 0x02}}, |
{5, 704, 774, {0x28, 0xF4, 0x05, 0x18, 0x21, 0x17, 0x59, 0x0F, 0x18, 0xC0, 0x42, 0xC0, 0x02}}, |
{3, 448, 492, {0x28, 0xF4, 0x05, 0x0F, 0x5D, 0x0E, 0x95, 0x15, 0x18, 0xC0, 0x69, 0xC0, 0x02}}, |
{2, 291, 320, {0x28, 0xF4, 0x1D, 0x09, 0xFB, 0x09, 0x33, 0x1E, 0x18, 0x23, 0xA1, 0xC0, 0x02}}, |
}, |
}, |
/* QCIF */ |
{ |
/* 5 fps */ |
{ |
{1, 193, 0, {0x0D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xC1, 0xF4, 0xC0, 0x02}}, |
{1, 193, 0, {0x0D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xC1, 0xF4, 0xC0, 0x02}}, |
{1, 193, 0, {0x0D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xC1, 0xF4, 0xC0, 0x02}}, |
{1, 193, 0, {0x0D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xC1, 0xF4, 0xC0, 0x02}}, |
}, |
/* 10 fps */ |
{ |
{3, 385, 0, {0x0C, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x81, 0x79, 0xC0, 0x02}}, |
{2, 291, 800, {0x0C, 0xF4, 0x05, 0x18, 0xF4, 0x18, 0x18, 0x11, 0x08, 0x23, 0xA1, 0xC0, 0x02}}, |
{2, 291, 800, {0x0C, 0xF4, 0x05, 0x18, 0xF4, 0x18, 0x18, 0x11, 0x08, 0x23, 0xA1, 0xC0, 0x02}}, |
{1, 194, 532, {0x0C, 0xF4, 0x05, 0x10, 0x9A, 0x0F, 0xBE, 0x1B, 0x08, 0xC2, 0xF0, 0xC0, 0x02}}, |
}, |
/* 15 fps */ |
{ |
{4, 577, 0, {0x0B, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x41, 0x52, 0xC0, 0x02}}, |
{3, 447, 818, {0x0B, 0xF4, 0x05, 0x19, 0x89, 0x18, 0xAD, 0x0F, 0x10, 0xBF, 0x69, 0xC0, 0x02}}, |
{2, 292, 534, {0x0B, 0xF4, 0x05, 0x10, 0xA3, 0x0F, 0xC7, 0x19, 0x10, 0x24, 0xA1, 0xC0, 0x02}}, |
{1, 195, 356, {0x0B, 0xF4, 0x15, 0x0B, 0x11, 0x0A, 0x35, 0x1E, 0x10, 0xC3, 0xF0, 0xC0, 0x02}}, |
}, |
/* 20 fps */ |
{ |
{6, 776, 0, {0x0A, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x08, 0x3F, 0xC0, 0x02}}, |
{4, 591, 804, {0x0A, 0xF4, 0x05, 0x19, 0x1E, 0x18, 0x42, 0x0F, 0x18, 0x4F, 0x4E, 0xC0, 0x02}}, |
{3, 447, 608, {0x0A, 0xF4, 0x05, 0x12, 0xFD, 0x12, 0x21, 0x15, 0x18, 0xBF, 0x69, 0xC0, 0x02}}, |
{2, 291, 396, {0x0A, 0xF4, 0x15, 0x0C, 0x5E, 0x0B, 0x82, 0x1E, 0x18, 0x23, 0xA1, 0xC0, 0x02}}, |
}, |
/* 25 fps */ |
{ |
{9, 928, 0, {0x09, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xA0, 0x33, 0xC0, 0x02}}, |
{5, 703, 800, {0x09, 0xF4, 0x05, 0x18, 0xF4, 0x18, 0x18, 0x10, 0x18, 0xBF, 0x42, 0xC0, 0x02}}, |
{3, 447, 508, {0x09, 0xF4, 0x0D, 0x0F, 0xD2, 0x0E, 0xF6, 0x1B, 0x18, 0xBF, 0x69, 0xC0, 0x02}}, |
{2, 292, 332, {0x09, 0xF4, 0x1D, 0x0A, 0x5A, 0x09, 0x7E, 0x1E, 0x18, 0x24, 0xA1, 0xC0, 0x02}}, |
}, |
/* 30 fps */ |
{ |
{0, }, |
{9, 956, 876, {0x08, 0xF4, 0x05, 0x1B, 0x58, 0x1A, 0x7C, 0x0E, 0x20, 0xBC, 0x33, 0x10, 0x02}}, |
{4, 592, 542, {0x08, 0xF4, 0x05, 0x10, 0xE4, 0x10, 0x08, 0x17, 0x20, 0x50, 0x4E, 0x10, 0x02}}, |
{2, 291, 266, {0x08, 0xF4, 0x25, 0x08, 0x48, 0x07, 0x6C, 0x1E, 0x20, 0x23, 0xA1, 0x10, 0x02}}, |
}, |
}, |
/* SIF */ |
{ |
/* 5 fps */ |
{ |
{4, 582, 0, {0x35, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x46, 0x52, 0x60, 0x02}}, |
{3, 387, 1276, {0x35, 0xF4, 0x05, 0x27, 0xD8, 0x26, 0x48, 0x03, 0x10, 0x83, 0x79, 0x60, 0x02}}, |
{2, 291, 960, {0x35, 0xF4, 0x0D, 0x1D, 0xF2, 0x1C, 0x62, 0x04, 0x10, 0x23, 0xA1, 0x60, 0x02}}, |
{1, 191, 630, {0x35, 0xF4, 0x1D, 0x13, 0xA9, 0x12, 0x19, 0x05, 0x08, 0xBF, 0xF4, 0x60, 0x02}}, |
}, |
/* 10 fps */ |
{ |
{0, }, |
{6, 775, 1278, {0x34, 0xF4, 0x05, 0x27, 0xE8, 0x26, 0x58, 0x05, 0x30, 0x07, 0x3F, 0x10, 0x02}}, |
{3, 447, 736, {0x34, 0xF4, 0x15, 0x16, 0xFB, 0x15, 0x6B, 0x05, 0x18, 0xBF, 0x69, 0x10, 0x02}}, |
{2, 291, 480, {0x34, 0xF4, 0x2D, 0x0E, 0xF9, 0x0D, 0x69, 0x09, 0x18, 0x23, 0xA1, 0x10, 0x02}}, |
}, |
/* 15 fps */ |
{ |
{0, }, |
{9, 955, 1050, {0x33, 0xF4, 0x05, 0x20, 0xCF, 0x1F, 0x3F, 0x06, 0x48, 0xBB, 0x33, 0x10, 0x02}}, |
{4, 591, 650, {0x33, 0xF4, 0x15, 0x14, 0x44, 0x12, 0xB4, 0x08, 0x30, 0x4F, 0x4E, 0x10, 0x02}}, |
{3, 448, 492, {0x33, 0xF4, 0x25, 0x0F, 0x52, 0x0D, 0xC2, 0x09, 0x28, 0xC0, 0x69, 0x10, 0x02}}, |
}, |
/* 20 fps */ |
{ |
{0, }, |
{9, 958, 782, {0x32, 0xF4, 0x0D, 0x18, 0x6A, 0x16, 0xDA, 0x0B, 0x58, 0xBE, 0x33, 0xD0, 0x02}}, |
{5, 703, 574, {0x32, 0xF4, 0x1D, 0x11, 0xE7, 0x10, 0x57, 0x0B, 0x40, 0xBF, 0x42, 0xD0, 0x02}}, |
{3, 446, 364, {0x32, 0xF4, 0x3D, 0x0B, 0x5C, 0x09, 0xCC, 0x0E, 0x30, 0xBE, 0x69, 0xD0, 0x02}}, |
}, |
/* 25 fps */ |
{ |
{0, }, |
{9, 958, 654, {0x31, 0xF4, 0x15, 0x14, 0x66, 0x12, 0xD6, 0x0B, 0x50, 0xBE, 0x33, 0x90, 0x02}}, |
{6, 776, 530, {0x31, 0xF4, 0x25, 0x10, 0x8C, 0x0E, 0xFC, 0x0C, 0x48, 0x08, 0x3F, 0x90, 0x02}}, |
{4, 592, 404, {0x31, 0xF4, 0x35, 0x0C, 0x96, 0x0B, 0x06, 0x0B, 0x38, 0x50, 0x4E, 0x90, 0x02}}, |
}, |
/* 30 fps */ |
{ |
{0, }, |
{9, 957, 526, {0x30, 0xF4, 0x25, 0x10, 0x68, 0x0E, 0xD8, 0x0D, 0x58, 0xBD, 0x33, 0x60, 0x02}}, |
{6, 775, 426, {0x30, 0xF4, 0x35, 0x0D, 0x48, 0x0B, 0xB8, 0x0F, 0x50, 0x07, 0x3F, 0x60, 0x02}}, |
{4, 590, 324, {0x30, 0x7A, 0x4B, 0x0A, 0x1C, 0x08, 0xB4, 0x0E, 0x40, 0x4E, 0x52, 0x60, 0x02}}, |
}, |
}, |
/* CIF */ |
{ |
/* 5 fps */ |
{ |
{6, 771, 0, {0x15, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x3F, 0x80, 0x02}}, |
{4, 465, 1278, {0x15, 0xF4, 0x05, 0x27, 0xEE, 0x26, 0x36, 0x03, 0x18, 0xD1, 0x65, 0x80, 0x02}}, |
{2, 291, 800, {0x15, 0xF4, 0x15, 0x18, 0xF4, 0x17, 0x3C, 0x05, 0x18, 0x23, 0xA1, 0x80, 0x02}}, |
{1, 193, 528, {0x15, 0xF4, 0x2D, 0x10, 0x7E, 0x0E, 0xC6, 0x0A, 0x18, 0xC1, 0xF4, 0x80, 0x02}}, |
}, |
/* 10 fps */ |
{ |
{0, }, |
{9, 932, 1278, {0x14, 0xF4, 0x05, 0x27, 0xEE, 0x26, 0x36, 0x04, 0x30, 0xA4, 0x33, 0x10, 0x02}}, |
{4, 591, 812, {0x14, 0xF4, 0x15, 0x19, 0x56, 0x17, 0x9E, 0x06, 0x28, 0x4F, 0x4E, 0x10, 0x02}}, |
{2, 291, 400, {0x14, 0xF4, 0x3D, 0x0C, 0x7A, 0x0A, 0xC2, 0x0E, 0x28, 0x23, 0xA1, 0x10, 0x02}}, |
}, |
/* 15 fps */ |
{ |
{0, }, |
{9, 956, 876, {0x13, 0xF4, 0x0D, 0x1B, 0x58, 0x19, 0xA0, 0x05, 0x38, 0xBC, 0x33, 0x60, 0x02}}, |
{5, 703, 644, {0x13, 0xF4, 0x1D, 0x14, 0x1C, 0x12, 0x64, 0x08, 0x38, 0xBF, 0x42, 0x60, 0x02}}, |
{3, 448, 410, {0x13, 0xF4, 0x3D, 0x0C, 0xC4, 0x0B, 0x0C, 0x0E, 0x38, 0xC0, 0x69, 0x60, 0x02}}, |
}, |
/* 20 fps */ |
{ |
{0, }, |
{9, 956, 650, {0x12, 0xF4, 0x1D, 0x14, 0x4A, 0x12, 0x92, 0x09, 0x48, 0xBC, 0x33, 0x10, 0x03}}, |
{6, 776, 528, {0x12, 0xF4, 0x2D, 0x10, 0x7E, 0x0E, 0xC6, 0x0A, 0x40, 0x08, 0x3F, 0x10, 0x03}}, |
{4, 591, 402, {0x12, 0xF4, 0x3D, 0x0C, 0x8F, 0x0A, 0xD7, 0x0E, 0x40, 0x4F, 0x4E, 0x10, 0x03}}, |
}, |
/* 25 fps */ |
{ |
{0, }, |
{9, 956, 544, {0x11, 0xF4, 0x25, 0x10, 0xF4, 0x0F, 0x3C, 0x0A, 0x48, 0xBC, 0x33, 0xC0, 0x02}}, |
{7, 840, 478, {0x11, 0xF4, 0x2D, 0x0E, 0xEB, 0x0D, 0x33, 0x0B, 0x48, 0x48, 0x3B, 0xC0, 0x02}}, |
{5, 703, 400, {0x11, 0xF4, 0x3D, 0x0C, 0x7A, 0x0A, 0xC2, 0x0E, 0x48, 0xBF, 0x42, 0xC0, 0x02}}, |
}, |
/* 30 fps */ |
{ |
{0, }, |
{9, 956, 438, {0x10, 0xF4, 0x35, 0x0D, 0xAC, 0x0B, 0xF4, 0x0D, 0x50, 0xBC, 0x33, 0x10, 0x02}}, |
{7, 838, 384, {0x10, 0xF4, 0x45, 0x0B, 0xFD, 0x0A, 0x45, 0x0F, 0x50, 0x46, 0x3B, 0x10, 0x02}}, |
{6, 773, 354, {0x10, 0x7A, 0x4B, 0x0B, 0x0C, 0x09, 0x80, 0x10, 0x50, 0x05, 0x3F, 0x10, 0x02}}, |
}, |
}, |
/* VGA */ |
{ |
/* 5 fps */ |
{ |
{0, }, |
{6, 773, 1272, {0x1D, 0xF4, 0x15, 0x27, 0xB6, 0x24, 0x96, 0x02, 0x30, 0x05, 0x3F, 0x10, 0x02}}, |
{4, 592, 976, {0x1D, 0xF4, 0x25, 0x1E, 0x78, 0x1B, 0x58, 0x03, 0x30, 0x50, 0x4E, 0x10, 0x02}}, |
{3, 448, 738, {0x1D, 0xF4, 0x3D, 0x17, 0x0C, 0x13, 0xEC, 0x04, 0x30, 0xC0, 0x69, 0x10, 0x02}}, |
}, |
/* 10 fps */ |
{ |
{0, }, |
{9, 956, 788, {0x1C, 0xF4, 0x35, 0x18, 0x9C, 0x15, 0x7C, 0x03, 0x48, 0xBC, 0x33, 0x10, 0x02}}, |
{6, 776, 640, {0x1C, 0x7A, 0x53, 0x13, 0xFC, 0x11, 0x2C, 0x04, 0x48, 0x08, 0x3F, 0x10, 0x02}}, |
{4, 592, 488, {0x1C, 0x7A, 0x6B, 0x0F, 0x3C, 0x0C, 0x6C, 0x06, 0x48, 0x50, 0x4E, 0x10, 0x02}}, |
}, |
/* 15 fps */ |
{ |
{0, }, |
{9, 957, 526, {0x1B, 0x7A, 0x63, 0x10, 0x68, 0x0D, 0x98, 0x06, 0x58, 0xBD, 0x33, 0x80, 0x02}}, |
{9, 957, 526, {0x1B, 0x7A, 0x63, 0x10, 0x68, 0x0D, 0x98, 0x06, 0x58, 0xBD, 0x33, 0x80, 0x02}}, |
{8, 895, 492, {0x1B, 0x7A, 0x6B, 0x0F, 0x5D, 0x0C, 0x8D, 0x06, 0x58, 0x7F, 0x37, 0x80, 0x02}}, |
}, |
/* 20 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 25 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 30 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
}, |
/shark/trunk/drivers/usb/media/pwc_kiara.h |
---|
0,0 → 1,270 |
/* SQCIF */ |
{ |
/* 5 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 10 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 15 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 20 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 25 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 30 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
}, |
/* QSIF */ |
{ |
/* 5 fps */ |
{ |
{1, 146, 0, {0x1D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0x00, 0x80}}, |
{1, 146, 0, {0x1D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0x00, 0x80}}, |
{1, 146, 0, {0x1D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0x00, 0x80}}, |
{1, 146, 0, {0x1D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0x00, 0x80}}, |
}, |
/* 10 fps */ |
{ |
{2, 291, 0, {0x1C, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x23, 0x01, 0x80}}, |
{1, 192, 630, {0x14, 0xF4, 0x30, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xC0, 0x00, 0x80}}, |
{1, 192, 630, {0x14, 0xF4, 0x30, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xC0, 0x00, 0x80}}, |
{1, 192, 630, {0x14, 0xF4, 0x30, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xC0, 0x00, 0x80}}, |
}, |
/* 15 fps */ |
{ |
{3, 437, 0, {0x1B, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xB5, 0x01, 0x80}}, |
{2, 292, 640, {0x13, 0xF4, 0x30, 0x13, 0xF7, 0x13, 0x2F, 0x13, 0x20, 0x24, 0x01, 0x80}}, |
{2, 292, 640, {0x13, 0xF4, 0x30, 0x13, 0xF7, 0x13, 0x2F, 0x13, 0x20, 0x24, 0x01, 0x80}}, |
{1, 192, 420, {0x13, 0xF4, 0x30, 0x0D, 0x1B, 0x0C, 0x53, 0x1E, 0x18, 0xC0, 0x00, 0x80}}, |
}, |
/* 20 fps */ |
{ |
{4, 589, 0, {0x1A, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x4D, 0x02, 0x80}}, |
{3, 448, 730, {0x12, 0xF4, 0x30, 0x16, 0xC9, 0x16, 0x01, 0x0E, 0x18, 0xC0, 0x01, 0x80}}, |
{2, 292, 476, {0x12, 0xF4, 0x30, 0x0E, 0xD8, 0x0E, 0x10, 0x19, 0x18, 0x24, 0x01, 0x80}}, |
{1, 192, 312, {0x12, 0xF4, 0x50, 0x09, 0xB3, 0x08, 0xEB, 0x1E, 0x18, 0xC0, 0x00, 0x80}}, |
}, |
/* 25 fps */ |
{ |
{5, 703, 0, {0x19, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xBF, 0x02, 0x80}}, |
{3, 447, 610, {0x11, 0xF4, 0x30, 0x13, 0x0B, 0x12, 0x43, 0x14, 0x28, 0xBF, 0x01, 0x80}}, |
{2, 292, 398, {0x11, 0xF4, 0x50, 0x0C, 0x6C, 0x0B, 0xA4, 0x1E, 0x28, 0x24, 0x01, 0x80}}, |
{1, 193, 262, {0x11, 0xF4, 0x50, 0x08, 0x23, 0x07, 0x5B, 0x1E, 0x28, 0xC1, 0x00, 0x80}}, |
}, |
/* 30 fps */ |
{ |
{8, 874, 0, {0x18, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x6A, 0x03, 0x80}}, |
{5, 704, 730, {0x10, 0xF4, 0x30, 0x16, 0xC9, 0x16, 0x01, 0x0E, 0x28, 0xC0, 0x02, 0x80}}, |
{3, 448, 492, {0x10, 0xF4, 0x30, 0x0F, 0x5D, 0x0E, 0x95, 0x15, 0x28, 0xC0, 0x01, 0x80}}, |
{2, 292, 320, {0x10, 0xF4, 0x50, 0x09, 0xFB, 0x09, 0x33, 0x1E, 0x28, 0x24, 0x01, 0x80}}, |
}, |
}, |
/* QCIF */ |
{ |
/* 5 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 10 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 15 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 20 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 25 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 30 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
}, |
/* SIF */ |
{ |
/* 5 fps */ |
{ |
{4, 582, 0, {0x0D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x46, 0x02, 0x80}}, |
{3, 387, 1276, {0x05, 0xF4, 0x30, 0x27, 0xD8, 0x26, 0x48, 0x03, 0x10, 0x83, 0x01, 0x80}}, |
{2, 291, 960, {0x05, 0xF4, 0x30, 0x1D, 0xF2, 0x1C, 0x62, 0x04, 0x10, 0x23, 0x01, 0x80}}, |
{1, 191, 630, {0x05, 0xF4, 0x50, 0x13, 0xA9, 0x12, 0x19, 0x05, 0x18, 0xBF, 0x00, 0x80}}, |
}, |
/* 10 fps */ |
{ |
{0, }, |
{6, 775, 1278, {0x04, 0xF4, 0x30, 0x27, 0xE8, 0x26, 0x58, 0x05, 0x30, 0x07, 0x03, 0x80}}, |
{3, 447, 736, {0x04, 0xF4, 0x30, 0x16, 0xFB, 0x15, 0x6B, 0x05, 0x28, 0xBF, 0x01, 0x80}}, |
{2, 292, 480, {0x04, 0xF4, 0x70, 0x0E, 0xF9, 0x0D, 0x69, 0x09, 0x28, 0x24, 0x01, 0x80}}, |
}, |
/* 15 fps */ |
{ |
{0, }, |
{9, 955, 1050, {0x03, 0xF4, 0x30, 0x20, 0xCF, 0x1F, 0x3F, 0x06, 0x48, 0xBB, 0x03, 0x80}}, |
{4, 592, 650, {0x03, 0xF4, 0x30, 0x14, 0x44, 0x12, 0xB4, 0x08, 0x30, 0x50, 0x02, 0x80}}, |
{3, 448, 492, {0x03, 0xF4, 0x50, 0x0F, 0x52, 0x0D, 0xC2, 0x09, 0x38, 0xC0, 0x01, 0x80}}, |
}, |
/* 20 fps */ |
{ |
{0, }, |
{9, 958, 782, {0x02, 0xF4, 0x30, 0x18, 0x6A, 0x16, 0xDA, 0x0B, 0x58, 0xBE, 0x03, 0x80}}, |
{5, 703, 574, {0x02, 0xF4, 0x50, 0x11, 0xE7, 0x10, 0x57, 0x0B, 0x40, 0xBF, 0x02, 0x80}}, |
{3, 446, 364, {0x02, 0xF4, 0x90, 0x0B, 0x5C, 0x09, 0xCC, 0x0E, 0x38, 0xBE, 0x01, 0x80}}, |
}, |
/* 25 fps */ |
{ |
{0, }, |
{9, 958, 654, {0x01, 0xF4, 0x30, 0x14, 0x66, 0x12, 0xD6, 0x0B, 0x50, 0xBE, 0x03, 0x80}}, |
{6, 776, 530, {0x01, 0xF4, 0x50, 0x10, 0x8C, 0x0E, 0xFC, 0x0C, 0x48, 0x08, 0x03, 0x80}}, |
{4, 592, 404, {0x01, 0xF4, 0x70, 0x0C, 0x96, 0x0B, 0x06, 0x0B, 0x48, 0x50, 0x02, 0x80}}, |
}, |
/* 30 fps */ |
{ |
{0, }, |
{9, 957, 526, {0x00, 0xF4, 0x50, 0x10, 0x68, 0x0E, 0xD8, 0x0D, 0x58, 0xBD, 0x03, 0x80}}, |
{6, 775, 426, {0x00, 0xF4, 0x70, 0x0D, 0x48, 0x0B, 0xB8, 0x0F, 0x50, 0x07, 0x03, 0x80}}, |
{4, 590, 324, {0x00, 0x7A, 0x88, 0x0A, 0x1C, 0x08, 0xB4, 0x0E, 0x50, 0x4E, 0x02, 0x80}}, |
}, |
}, |
/* CIF */ |
{ |
/* 5 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 10 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 15 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 20 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 25 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 30 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
}, |
/* VGA */ |
{ |
/* 5 fps */ |
{ |
{0, }, |
{6, 773, 1272, {0x25, 0xF4, 0x30, 0x27, 0xB6, 0x24, 0x96, 0x02, 0x30, 0x05, 0x03, 0x80}}, |
{4, 592, 976, {0x25, 0xF4, 0x50, 0x1E, 0x78, 0x1B, 0x58, 0x03, 0x30, 0x50, 0x02, 0x80}}, |
{3, 448, 738, {0x25, 0xF4, 0x90, 0x17, 0x0C, 0x13, 0xEC, 0x04, 0x30, 0xC0, 0x01, 0x80}}, |
}, |
/* 10 fps */ |
{ |
{0, }, |
{9, 956, 788, {0x24, 0xF4, 0x70, 0x18, 0x9C, 0x15, 0x7C, 0x03, 0x48, 0xBC, 0x03, 0x80}}, |
{6, 776, 640, {0x24, 0xF4, 0xB0, 0x13, 0xFC, 0x11, 0x2C, 0x04, 0x48, 0x08, 0x03, 0x80}}, |
{4, 592, 488, {0x24, 0x7A, 0xE8, 0x0F, 0x3C, 0x0C, 0x6C, 0x06, 0x48, 0x50, 0x02, 0x80}}, |
}, |
/* 15 fps */ |
{ |
{0, }, |
{9, 957, 526, {0x23, 0x7A, 0xE8, 0x10, 0x68, 0x0D, 0x98, 0x06, 0x58, 0xBD, 0x03, 0x80}}, |
{9, 957, 526, {0x23, 0x7A, 0xE8, 0x10, 0x68, 0x0D, 0x98, 0x06, 0x58, 0xBD, 0x03, 0x80}}, |
{8, 895, 492, {0x23, 0x7A, 0xE8, 0x0F, 0x5D, 0x0C, 0x8D, 0x06, 0x58, 0x7F, 0x03, 0x80}}, |
}, |
/* 20 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 25 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
/* 30 fps */ |
{ |
{0, }, |
{0, }, |
{0, }, |
{0, }, |
}, |
}, |
/shark/trunk/drivers/usb/media/pwc-if.c |
---|
0,0 → 1,2202 |
/* Linux driver for Philips webcam |
USB and Video4Linux interface part. |
(C) 1999-2003 Nemosoft Unv. |
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 |
*/ |
/* |
This code forms the interface between the USB layers and the Philips |
specific stuff. Some adanved stuff of the driver falls under an |
NDA, signed between me and Philips B.V., Eindhoven, the Netherlands, and |
is thus not distributed in source form. The binary pwcx.o module |
contains the code that falls under the NDA. |
In case you're wondering: 'pwc' stands for "Philips WebCam", but |
I really didn't want to type 'philips_web_cam' every time (I'm lazy as |
any Linux kernel hacker, but I don't like uncomprehensible abbreviations |
without explanation). |
Oh yes, convention: to disctinguish between all the various pointers to |
device-structures, I use these names for the pointer variables: |
udev: struct usb_device * |
vdev: struct video_device * |
pdev: struct pwc_devive * |
*/ |
/* Contributors: |
- Alvarado: adding whitebalance code |
- Alistar Moire: QuickCam 3000 Pro device/product ID |
- Tony Hoyle: Creative Labs Webcam 5 device/product ID |
- Mark Burazin: solving hang in VIDIOCSYNC when camera gets unplugged |
- Jk Fang: SOTEC Afina Eye ID |
- Xavier Roche: QuickCam Pro 4000 ID |
- Jens Knudsen: QuickCam Zoom ID |
- J. Debert: QuickCam for Notebooks ID |
*/ |
#include <linuxcomp.h> |
#include <linux/errno.h> |
#include <linux/init.h> |
#include <linux/mm.h> |
#include <linux/module.h> |
#include <linux/poll.h> |
#include <linux/slab.h> |
#include <linux/vmalloc.h> |
#include <asm/io.h> |
#include "pwc.h" |
#include "pwc-ioctl.h" |
#include "pwc-uncompress.h" |
/* Function prototypes and driver templates */ |
/* hotplug device table support */ |
static struct usb_device_id pwc_device_table [] = { |
{ USB_DEVICE(0x0471, 0x0302) }, /* Philips models */ |
{ USB_DEVICE(0x0471, 0x0303) }, |
{ USB_DEVICE(0x0471, 0x0304) }, |
{ USB_DEVICE(0x0471, 0x0307) }, |
{ USB_DEVICE(0x0471, 0x0308) }, |
{ USB_DEVICE(0x0471, 0x030C) }, |
{ USB_DEVICE(0x0471, 0x0310) }, |
{ USB_DEVICE(0x0471, 0x0311) }, |
{ USB_DEVICE(0x0471, 0x0312) }, |
{ USB_DEVICE(0x0471, 0x0313) }, /* the 'new' 720K */ |
{ USB_DEVICE(0x069A, 0x0001) }, /* Askey */ |
{ USB_DEVICE(0x046D, 0x08B0) }, /* Logitech QuickCam Pro 3000 */ |
{ USB_DEVICE(0x046D, 0x08B1) }, /* Logitech QuickCam Notebook Pro */ |
{ USB_DEVICE(0x046D, 0x08B2) }, /* Logitech QuickCam Pro 4000 */ |
{ USB_DEVICE(0x046D, 0x08B3) }, /* Logitech QuickCam Zoom (old model) */ |
{ USB_DEVICE(0x046D, 0x08B4) }, /* Logitech QuickCam Zoom (new model) */ |
{ USB_DEVICE(0x046D, 0x08B5) }, /* Logitech QuickCam Orbit/Sphere */ |
{ USB_DEVICE(0x046D, 0x08B6) }, /* Logitech (reserved) */ |
{ USB_DEVICE(0x046D, 0x08B7) }, /* Logitech (reserved) */ |
{ USB_DEVICE(0x046D, 0x08B8) }, /* Logitech (reserved) */ |
{ USB_DEVICE(0x055D, 0x9000) }, /* Samsung */ |
{ USB_DEVICE(0x055D, 0x9001) }, |
{ USB_DEVICE(0x041E, 0x400C) }, /* Creative Webcam 5 */ |
{ USB_DEVICE(0x041E, 0x4011) }, /* Creative Webcam Pro Ex */ |
{ USB_DEVICE(0x04CC, 0x8116) }, /* Afina Eye */ |
{ USB_DEVICE(0x06BE, 0x8116) }, /* AME CU-001 */ |
{ USB_DEVICE(0x0d81, 0x1910) }, /* Visionite */ |
{ USB_DEVICE(0x0d81, 0x1900) }, |
{ } |
}; |
MODULE_DEVICE_TABLE(usb, pwc_device_table); |
static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id *id); |
static void usb_pwc_disconnect(struct usb_interface *intf); |
static struct usb_driver pwc_driver = { |
.owner = THIS_MODULE, |
.name = "Philips webcam", /* name */ |
.id_table = pwc_device_table, |
.probe = usb_pwc_probe, /* probe() */ |
.disconnect = usb_pwc_disconnect, /* disconnect() */ |
}; |
#define MAX_DEV_HINTS 20 |
#define MAX_ISOC_ERRORS 20 |
static int default_size = PSZ_QCIF; |
static int default_fps = 10; |
static int default_fbufs = 3; /* Default number of frame buffers */ |
static int default_mbufs = 2; /* Default number of mmap() buffers */ |
int pwc_trace = TRACE_MODULE | TRACE_FLOW | TRACE_PWCX; |
static int power_save = 0; |
static int led_on = 100, led_off = 0; /* defaults to LED that is on while in use */ |
int pwc_preferred_compression = 2; /* 0..3 = uncompressed..high */ |
static struct { |
int type; |
char serial_number[30]; |
int device_node; |
struct pwc_device *pdev; |
} device_hint[MAX_DEV_HINTS]; |
/***/ |
/*static*/ int pwc_video_open(struct inode *inode, struct file *file); |
static int pwc_video_close(struct inode *inode, struct file *file); |
static void pwc_video_release(struct video_device *); |
/*static*/ ssize_t pwc_video_read(struct file *file, char *buf, |
size_t count, loff_t *ppos); |
static unsigned int pwc_video_poll(struct file *file, poll_table *wait); |
/*static*/ int pwc_video_ioctl(struct inode *inode, struct file *file, |
unsigned int ioctlnr, unsigned long arg); |
static int pwc_video_mmap(struct file *file, struct vm_area_struct *vma); |
static struct file_operations pwc_fops = { |
.owner = THIS_MODULE, |
.open = pwc_video_open, |
.release = pwc_video_close, |
.read = pwc_video_read, |
.poll = pwc_video_poll, |
.mmap = pwc_video_mmap, |
.ioctl = pwc_video_ioctl, |
.llseek = no_llseek, |
}; |
static struct video_device pwc_template = { |
.owner = THIS_MODULE, |
.name = "Philips Webcam", /* Filled in later */ |
.type = VID_TYPE_CAPTURE, |
.hardware = VID_HARDWARE_PWC, |
.fops = &pwc_fops, |
}; |
/***************************************************************************/ |
/* Okay, this is some magic that I worked out and the reasoning behind it... |
The biggest problem with any USB device is of course: "what to do |
when the user unplugs the device while it is in use by an application?" |
We have several options: |
1) Curse them with the 7 plagues when they do (requires divine intervention) |
2) Tell them not to (won't work: they'll do it anyway) |
3) Oops the kernel (this will have a negative effect on a user's uptime) |
4) Do something sensible. |
Of course, we go for option 4. |
It happens that this device will be linked to two times, once from |
usb_device and once from the video_device in their respective 'private' |
pointers. This is done when the device is probed() and all initialization |
succeeded. The pwc_device struct links back to both structures. |
When a device is unplugged while in use it will be removed from the |
list of known USB devices; I also de-register it as a V4L device, but |
unfortunately I can't free the memory since the struct is still in use |
by the file descriptor. This free-ing is then deferend until the first |
opportunity. Crude, but it works. |
A small 'advantage' is that if a user unplugs the cam and plugs it back |
in, it should get assigned the same video device minor, but unfortunately |
it's non-trivial to re-link the cam back to the video device... (that |
would surely be magic! :)) |
*/ |
/***************************************************************************/ |
/* Private functions */ |
/* Here we want the physical address of the memory. |
* This is used when initializing the contents of the area. |
*/ |
static inline unsigned long kvirt_to_pa(unsigned long adr) |
{ |
// unsigned long kva, ret; |
// kva = (unsigned long) page_address(vmalloc_to_page((void *)adr)); |
// kva |= adr & (PAGE_SIZE-1); /* restore the offset */ |
// ret = __pa(kva); */ |
// return ret; |
return adr; |
} |
static void * rvmalloc(unsigned long size) |
{ |
void * mem; |
unsigned long adr; |
// size=PAGE_ALIGN(size); |
// mem=vmalloc_32(size); |
mem=malloc(size); |
if (mem) |
{ |
memset(mem, 0, size); /* Clear the ram out, no junk to the user */ |
// adr=(unsigned long) mem; |
// while (size > 0) |
// { |
// SetPageReserved(vmalloc_to_page((void *)adr)); |
// adr+=PAGE_SIZE; |
// size-=PAGE_SIZE; |
// } |
} |
return mem; |
} |
static void rvfree(void * mem, unsigned long size) |
{ |
unsigned long adr; |
// if (mem) |
// { |
// adr=(unsigned long) mem; |
// while ((long) size > 0) |
// { |
// ClearPageReserved(vmalloc_to_page((void *)adr)); |
// adr+=PAGE_SIZE; |
// size-=PAGE_SIZE; |
// } |
// vfree(mem); |
// } |
} |
static int pwc_allocate_buffers(struct pwc_device *pdev) |
{ |
int i; |
void *kbuf; |
Trace(TRACE_MEMORY, ">> pwc_allocate_buffers(pdev = 0x%p)\n", pdev); |
if (pdev == NULL) |
return -ENXIO; |
#ifdef PWC_MAGIC |
if (pdev->magic != PWC_MAGIC) { |
Err("allocate_buffers(): magic failed.\n"); |
return -ENXIO; |
} |
#endif |
/* Allocate Isochronous pipe buffers */ |
for (i = 0; i < MAX_ISO_BUFS; i++) { |
if (pdev->sbuf[i].data == NULL) { |
kbuf = kmalloc(ISO_BUFFER_SIZE, GFP_KERNEL); |
if (kbuf == NULL) { |
Err("Failed to allocate iso buffer %d.\n", i); |
return -ENOMEM; |
} |
Trace(TRACE_MEMORY, "Allocated iso buffer at %p.\n", kbuf); |
pdev->sbuf[i].data = kbuf; |
memset(kbuf, 0, ISO_BUFFER_SIZE); |
} |
} |
/* Allocate frame buffer structure */ |
if (pdev->fbuf == NULL) { |
kbuf = kmalloc(default_fbufs * sizeof(struct pwc_frame_buf), GFP_KERNEL); |
if (kbuf == NULL) { |
Err("Failed to allocate frame buffer structure.\n"); |
return -ENOMEM; |
} |
Trace(TRACE_MEMORY, "Allocated frame buffer structure at %p.\n", kbuf); |
pdev->fbuf = kbuf; |
memset(kbuf, 0, default_fbufs * sizeof(struct pwc_frame_buf)); |
} |
/* create frame buffers, and make circular ring */ |
for (i = 0; i < default_fbufs; i++) { |
if (pdev->fbuf[i].data == NULL) { |
kbuf = vmalloc(PWC_FRAME_SIZE); /* need vmalloc since frame buffer > 128K */ |
if (kbuf == NULL) { |
Err("Failed to allocate frame buffer %d.\n", i); |
return -ENOMEM; |
} |
Trace(TRACE_MEMORY, "Allocated frame buffer %d at %p.\n", i, kbuf); |
pdev->fbuf[i].data = kbuf; |
memset(kbuf, 128, PWC_FRAME_SIZE); |
} |
} |
/* Allocate decompressor table space */ |
kbuf = NULL; |
if (pdev->decompressor != NULL) { |
kbuf = kmalloc(pdev->decompressor->table_size, GFP_KERNEL); |
if (kbuf == NULL) { |
Err("Failed to allocate decompress table.\n"); |
return -ENOMEM; |
} |
Trace(TRACE_MEMORY, "Allocated decompress table %p.\n", kbuf); |
} |
pdev->decompress_data = kbuf; |
/* Allocate image buffer; double buffer for mmap() */ |
kbuf = rvmalloc(default_mbufs * pdev->len_per_image); |
if (kbuf == NULL) { |
Err("Failed to allocate image buffer(s).\n"); |
return -ENOMEM; |
} |
Trace(TRACE_MEMORY, "Allocated image buffer at %p.\n", kbuf); |
pdev->image_data = kbuf; |
for (i = 0; i < default_mbufs; i++) |
pdev->image_ptr[i] = kbuf + i * pdev->len_per_image; |
for (; i < MAX_IMAGES; i++) |
pdev->image_ptr[i] = NULL; |
kbuf = NULL; |
Trace(TRACE_MEMORY, "<< pwc_allocate_buffers()\n"); |
return 0; |
} |
static void pwc_free_buffers(struct pwc_device *pdev) |
{ |
int i; |
Trace(TRACE_MEMORY, "Entering free_buffers(%p).\n", pdev); |
if (pdev == NULL) |
return; |
#ifdef PWC_MAGIC |
if (pdev->magic != PWC_MAGIC) { |
Err("free_buffers(): magic failed.\n"); |
return; |
} |
#endif |
/* Release Iso-pipe buffers */ |
for (i = 0; i < MAX_ISO_BUFS; i++) |
if (pdev->sbuf[i].data != NULL) { |
Trace(TRACE_MEMORY, "Freeing ISO buffer at %p.\n", pdev->sbuf[i].data); |
kfree(pdev->sbuf[i].data); |
pdev->sbuf[i].data = NULL; |
} |
/* The same for frame buffers */ |
if (pdev->fbuf != NULL) { |
for (i = 0; i < default_fbufs; i++) { |
if (pdev->fbuf[i].data != NULL) { |
Trace(TRACE_MEMORY, "Freeing frame buffer %d at %p.\n", i, pdev->fbuf[i].data); |
vfree(pdev->fbuf[i].data); |
pdev->fbuf[i].data = NULL; |
} |
} |
kfree(pdev->fbuf); |
pdev->fbuf = NULL; |
} |
/* Intermediate decompression buffer & tables */ |
if (pdev->decompress_data != NULL) { |
Trace(TRACE_MEMORY, "Freeing decompression buffer at %p.\n", pdev->decompress_data); |
kfree(pdev->decompress_data); |
pdev->decompress_data = NULL; |
} |
pdev->decompressor = NULL; |
/* Release image buffers */ |
if (pdev->image_data != NULL) { |
Trace(TRACE_MEMORY, "Freeing image buffer at %p.\n", pdev->image_data); |
rvfree(pdev->image_data, default_mbufs * pdev->len_per_image); |
} |
pdev->image_data = NULL; |
Trace(TRACE_MEMORY, "Leaving free_buffers().\n"); |
} |
/* The frame & image buffer mess. |
Yes, this is a mess. Well, it used to be simple, but alas... In this |
module, 3 buffers schemes are used to get the data from the USB bus to |
the user program. The first scheme involves the ISO buffers (called thus |
since they transport ISO data from the USB controller), and not really |
interesting. Suffices to say the data from this buffer is quickly |
gathered in an interrupt handler (pwc_isoc_handler) and placed into the |
frame buffer. |
The frame buffer is the second scheme, and is the central element here. |
It collects the data from a single frame from the camera (hence, the |
name). Frames are delimited by the USB camera with a short USB packet, |
so that's easy to detect. The frame buffers form a list that is filled |
by the camera+USB controller and drained by the user process through |
either read() or mmap(). |
The image buffer is the third scheme, in which frames are decompressed |
and converted into planar format. For mmap() there is more than |
one image buffer available. |
The frame buffers provide the image buffering. In case the user process |
is a bit slow, this introduces lag and some undesired side-effects. |
The problem arises when the frame buffer is full. I used to drop the last |
frame, which makes the data in the queue stale very quickly. But dropping |
the frame at the head of the queue proved to be a litte bit more difficult. |
I tried a circular linked scheme, but this introduced more problems than |
it solved. |
Because filling and draining are completely asynchronous processes, this |
requires some fiddling with pointers and mutexes. |
Eventually, I came up with a system with 2 lists: an 'empty' frame list |
and a 'full' frame list: |
* Initially, all frame buffers but one are on the 'empty' list; the one |
remaining buffer is our initial fill frame. |
* If a frame is needed for filling, we try to take it from the 'empty' |
list, unless that list is empty, in which case we take the buffer at |
the head of the 'full' list. |
* When our fill buffer has been filled, it is appended to the 'full' |
list. |
* If a frame is needed by read() or mmap(), it is taken from the head of |
the 'full' list, handled, and then appended to the 'empty' list. If no |
buffer is present on the 'full' list, we wait. |
The advantage is that the buffer that is currently being decompressed/ |
converted, is on neither list, and thus not in our way (any other scheme |
I tried had the problem of old data lingering in the queue). |
Whatever strategy you choose, it always remains a tradeoff: with more |
frame buffers the chances of a missed frame are reduced. On the other |
hand, on slower machines it introduces lag because the queue will |
always be full. |
*/ |
/** |
\brief Find next frame buffer to fill. Take from empty or full list, whichever comes first. |
*/ |
static inline int pwc_next_fill_frame(struct pwc_device *pdev) |
{ |
int ret; |
unsigned long flags; |
ret = 0; |
spin_lock_irqsave(&pdev->ptrlock, flags); |
if (pdev->fill_frame != NULL) { |
/* append to 'full' list */ |
if (pdev->full_frames == NULL) { |
pdev->full_frames = pdev->fill_frame; |
pdev->full_frames_tail = pdev->full_frames; |
} |
else { |
pdev->full_frames_tail->next = pdev->fill_frame; |
pdev->full_frames_tail = pdev->fill_frame; |
} |
} |
if (pdev->empty_frames != NULL) { |
/* We have empty frames available. That's easy */ |
pdev->fill_frame = pdev->empty_frames; |
pdev->empty_frames = pdev->empty_frames->next; |
} |
else { |
/* Hmm. Take it from the full list */ |
#if PWC_DEBUG |
/* sanity check */ |
if (pdev->full_frames == NULL) { |
Err("Neither empty or full frames available!\n"); |
spin_unlock_irqrestore(&pdev->ptrlock, flags); |
return -EINVAL; |
} |
#endif |
pdev->fill_frame = pdev->full_frames; |
pdev->full_frames = pdev->full_frames->next; |
ret = 1; |
} |
pdev->fill_frame->next = NULL; |
#if PWC_DEBUG |
Trace(TRACE_SEQUENCE, "Assigning sequence number %d.\n", pdev->sequence); |
pdev->fill_frame->sequence = pdev->sequence++; |
#endif |
spin_unlock_irqrestore(&pdev->ptrlock, flags); |
return ret; |
} |
/** |
\brief Reset all buffers, pointers and lists, except for the image_used[] buffer. |
If the image_used[] buffer is cleared too, mmap()/VIDIOCSYNC will run into trouble. |
*/ |
static void pwc_reset_buffers(struct pwc_device *pdev) |
{ |
int i; |
unsigned long flags; |
spin_lock_irqsave(&pdev->ptrlock, flags); |
pdev->full_frames = NULL; |
pdev->full_frames_tail = NULL; |
for (i = 0; i < default_fbufs; i++) { |
pdev->fbuf[i].filled = 0; |
if (i > 0) |
pdev->fbuf[i].next = &pdev->fbuf[i - 1]; |
else |
pdev->fbuf->next = NULL; |
} |
pdev->empty_frames = &pdev->fbuf[default_fbufs - 1]; |
pdev->empty_frames_tail = pdev->fbuf; |
pdev->read_frame = NULL; |
pdev->fill_frame = pdev->empty_frames; |
pdev->empty_frames = pdev->empty_frames->next; |
pdev->image_read_pos = 0; |
pdev->fill_image = 0; |
spin_unlock_irqrestore(&pdev->ptrlock, flags); |
} |
/** |
\brief Do all the handling for getting one frame: get pointer, decompress, advance pointers. |
*/ |
static int pwc_handle_frame(struct pwc_device *pdev) |
{ |
int ret = 0; |
unsigned long flags; |
spin_lock_irqsave(&pdev->ptrlock, flags); |
/* First grab our read_frame; this is removed from all lists, so |
we can release the lock after this without problems */ |
if (pdev->read_frame != NULL) { |
/* This can't theoretically happen */ |
Err("Huh? Read frame still in use?\n"); |
} |
else { |
if (pdev->full_frames == NULL) { |
Err("Woops. No frames ready.\n"); |
} |
else { |
pdev->read_frame = pdev->full_frames; |
pdev->full_frames = pdev->full_frames->next; |
pdev->read_frame->next = NULL; |
} |
if (pdev->read_frame != NULL) { |
#if PWC_DEBUG |
Trace(TRACE_SEQUENCE, "Decompressing frame %d\n", pdev->read_frame->sequence); |
#endif |
/* Decompression is a lenghty process, so it's outside of the lock. |
This gives the isoc_handler the opportunity to fill more frames |
in the mean time. |
*/ |
spin_unlock_irqrestore(&pdev->ptrlock, flags); |
ret = pwc_decompress(pdev); |
spin_lock_irqsave(&pdev->ptrlock, flags); |
/* We're done with read_buffer, tack it to the end of the empty buffer list */ |
if (pdev->empty_frames == NULL) { |
pdev->empty_frames = pdev->read_frame; |
pdev->empty_frames_tail = pdev->empty_frames; |
} |
else { |
pdev->empty_frames_tail->next = pdev->read_frame; |
pdev->empty_frames_tail = pdev->read_frame; |
} |
pdev->read_frame = NULL; |
} |
} |
spin_unlock_irqrestore(&pdev->ptrlock, flags); |
return ret; |
} |
/** |
\brief Advance pointers of image buffer (after each user request) |
*/ |
static inline void pwc_next_image(struct pwc_device *pdev) |
{ |
pdev->image_used[pdev->fill_image] = 0; |
pdev->fill_image = (pdev->fill_image + 1) % default_mbufs; |
} |
/* This gets called for the Isochronous pipe (video). This is done in |
* interrupt time, so it has to be fast, not crash, and not stall. Neat. |
*/ |
static void pwc_isoc_handler(struct urb *urb, struct pt_regs *regs) |
{ |
struct pwc_device *pdev; |
int i, fst, flen; |
int awake; |
struct pwc_frame_buf *fbuf; |
unsigned char *fillptr = 0, *iso_buf = 0; |
awake = 0; |
pdev = (struct pwc_device *)urb->context; |
if (pdev == NULL) { |
Err("isoc_handler() called with NULL device?!\n"); |
return; |
} |
#ifdef PWC_MAGIC |
if (pdev->magic != PWC_MAGIC) { |
Err("isoc_handler() called with bad magic!\n"); |
return; |
} |
#endif |
if (urb->status == -ENOENT || urb->status == -ECONNRESET) { |
Trace(TRACE_OPEN, "pwc_isoc_handler(): URB (%p) unlinked %ssynchronuously.\n", urb, urb->status == -ENOENT ? "" : "a"); |
return; |
} |
if (urb->status != -EINPROGRESS && urb->status != 0) { |
const char *errmsg; |
errmsg = "Unknown"; |
switch(urb->status) { |
case -ENOSR: errmsg = "Buffer error (overrun)"; break; |
case -EPIPE: errmsg = "Stalled (device not responding)"; break; |
case -EOVERFLOW: errmsg = "Babble (bad cable?)"; break; |
case -EPROTO: errmsg = "Bit-stuff error (bad cable?)"; break; |
case -EILSEQ: errmsg = "CRC/Timeout (could be anything)"; break; |
case -ETIMEDOUT: errmsg = "NAK (device does not respond)"; break; |
} |
Trace(TRACE_FLOW, "pwc_isoc_handler() called with status %d [%s].\n", urb->status, errmsg); |
/* Give up after a number of contiguous errors on the USB bus. |
Appearantly something is wrong so we simulate an unplug event. |
*/ |
if (++pdev->visoc_errors > MAX_ISOC_ERRORS) |
{ |
Info("Too many ISOC errors, bailing out.\n"); |
pdev->error_status = EIO; |
awake = 1; |
wake_up_interruptible(&pdev->frameq); |
} |
goto handler_end; // ugly, but practical |
} |
fbuf = pdev->fill_frame; |
if (fbuf == NULL) { |
Err("pwc_isoc_handler without valid fill frame.\n"); |
awake = 1; |
goto handler_end; |
} |
else { |
fillptr = fbuf->data + fbuf->filled; |
} |
/* Reset ISOC error counter. We did get here, after all. */ |
pdev->visoc_errors = 0; |
/* vsync: 0 = don't copy data |
1 = sync-hunt |
2 = synched |
*/ |
/* Compact data */ |
for (i = 0; i < urb->number_of_packets; i++) { |
fst = urb->iso_frame_desc[i].status; |
flen = urb->iso_frame_desc[i].actual_length; |
iso_buf = urb->transfer_buffer + urb->iso_frame_desc[i].offset; |
if (fst == 0) { |
if (flen > 0) { /* if valid data... */ |
if (pdev->vsync > 0) { /* ...and we are not sync-hunting... */ |
pdev->vsync = 2; |
/* ...copy data to frame buffer, if possible */ |
if (flen + fbuf->filled > pdev->frame_total_size) { |
Trace(TRACE_FLOW, "Frame buffer overflow (flen = %d, frame_total_size = %d).\n", flen, pdev->frame_total_size); |
pdev->vsync = 0; /* Hmm, let's wait for an EOF (end-of-frame) */ |
pdev->vframes_error++; |
} |
else { |
memmove(fillptr, iso_buf, flen); |
fillptr += flen; |
} |
} |
fbuf->filled += flen; |
} /* ..flen > 0 */ |
if (flen < pdev->vlast_packet_size) { |
/* Shorter packet... We probably have the end of an image-frame; |
wake up read() process and let select()/poll() do something. |
Decompression is done in user time over there. |
*/ |
if (pdev->vsync == 2) { |
/* The ToUCam Fun CMOS sensor causes the firmware to send 2 or 3 bogus |
frames on the USB wire after an exposure change. This conditition is |
however detected in the cam and a bit is set in the header. |
*/ |
if (pdev->type == 730) { |
unsigned char *ptr = (unsigned char *)fbuf->data; |
if (ptr[1] == 1 && ptr[0] & 0x10) { |
#if PWC_DEBUG |
Debug("Hyundai CMOS sensor bug. Dropping frame %d.\n", fbuf->sequence); |
#endif |
pdev->drop_frames += 2; |
pdev->vframes_error++; |
} |
if ((ptr[0] ^ pdev->vmirror) & 0x01) { |
if (ptr[0] & 0x01) |
Info("Snapshot button pressed.\n"); |
else |
Info("Snapshot button released.\n"); |
} |
if ((ptr[0] ^ pdev->vmirror) & 0x02) { |
if (ptr[0] & 0x02) |
Info("Image is mirrored.\n"); |
else |
Info("Image is normal.\n"); |
} |
pdev->vmirror = ptr[0] & 0x03; |
/* Sometimes the trailer of the 730 is still sent as a 4 byte packet |
after a short frame; this condition is filtered out specifically. A 4 byte |
frame doesn't make sense anyway. |
So we get either this sequence: |
drop_bit set -> 4 byte frame -> short frame -> good frame |
Or this one: |
drop_bit set -> short frame -> good frame |
So we drop either 3 or 2 frames in all! |
*/ |
if (fbuf->filled == 4) |
pdev->drop_frames++; |
} |
/* In case we were instructed to drop the frame, do so silently. |
The buffer pointers are not updated either (but the counters are reset below). |
*/ |
if (pdev->drop_frames > 0) |
pdev->drop_frames--; |
else { |
/* Check for underflow first */ |
if (fbuf->filled < pdev->frame_total_size) { |
Trace(TRACE_FLOW, "Frame buffer underflow (%d bytes); discarded.\n", fbuf->filled); |
pdev->vframes_error++; |
} |
else { |
/* Send only once per EOF */ |
awake = 1; /* delay wake_ups */ |
/* Find our next frame to fill. This will always succeed, since we |
* nick a frame from either empty or full list, but if we had to |
* take it from the full list, it means a frame got dropped. |
*/ |
if (pwc_next_fill_frame(pdev)) { |
pdev->vframes_dumped++; |
if ((pdev->vframe_count > FRAME_LOWMARK) && (pwc_trace & TRACE_FLOW)) { |
if (pdev->vframes_dumped < 20) |
Trace(TRACE_FLOW, "Dumping frame %d.\n", pdev->vframe_count); |
if (pdev->vframes_dumped == 20) |
Trace(TRACE_FLOW, "Dumping frame %d (last message).\n", pdev->vframe_count); |
} |
} |
fbuf = pdev->fill_frame; |
} |
} /* !drop_frames */ |
pdev->vframe_count++; |
} |
fbuf->filled = 0; |
fillptr = fbuf->data; |
pdev->vsync = 1; |
} /* .. flen < last_packet_size */ |
pdev->vlast_packet_size = flen; |
} /* ..status == 0 */ |
#if PWC_DEBUG |
/* This is normally not interesting to the user, unless you are really debugging something */ |
else { |
static int iso_error = 0; |
iso_error++; |
if (iso_error < 20) |
Trace(TRACE_FLOW, "Iso frame %d of USB has error %d\n", i, fst); |
} |
#endif |
} |
handler_end: |
if (awake) |
wake_up_interruptible(&pdev->frameq); |
urb->dev = pdev->udev; |
i = usb_submit_urb(urb, GFP_ATOMIC); |
if (i != 0) |
Err("Error (%d) re-submitting urb in pwc_isoc_handler.\n", i); |
} |
static int pwc_isoc_init(struct pwc_device *pdev) |
{ |
struct usb_device *udev; |
struct urb *urb; |
int i, j, ret; |
struct usb_host_interface *idesc; |
if (pdev == NULL) |
return -EFAULT; |
if (pdev->iso_init) |
return 0; |
pdev->vsync = 0; |
udev = pdev->udev; |
/* Get the current alternate interface, adjust packet size */ |
if (!udev->actconfig) |
return -EFAULT; |
idesc = &udev->actconfig->interface[0]->altsetting[pdev->valternate]; |
if (!idesc) |
return -EFAULT; |
/* Search video endpoint */ |
pdev->vmax_packet_size = -1; |
for (i = 0; i < idesc->desc.bNumEndpoints; i++) |
if ((idesc->endpoint[i].desc.bEndpointAddress & 0xF) == pdev->vendpoint) { |
pdev->vmax_packet_size = idesc->endpoint[i].desc.wMaxPacketSize; |
break; |
} |
if (pdev->vmax_packet_size < 0 || pdev->vmax_packet_size > ISO_MAX_FRAME_SIZE) { |
Err("Failed to find packet size for video endpoint in current alternate setting.\n"); |
return -ENFILE; /* Odd error, that should be noticeable */ |
} |
/* Set alternate interface */ |
ret = 0; |
Trace(TRACE_OPEN, "Setting alternate interface %d\n", pdev->valternate); |
ret = usb_set_interface(pdev->udev, 0, pdev->valternate); |
if (ret < 0) |
return ret; |
for (i = 0; i < MAX_ISO_BUFS; i++) { |
urb = usb_alloc_urb(ISO_FRAMES_PER_DESC, GFP_KERNEL); |
if (urb == NULL) { |
Err("Failed to allocate urb %d\n", i); |
ret = -ENOMEM; |
break; |
} |
pdev->sbuf[i].urb = urb; |
Trace(TRACE_MEMORY, "Allocated URB at 0x%p\n", urb); |
} |
if (ret) { |
/* De-allocate in reverse order */ |
while (i >= 0) { |
if (pdev->sbuf[i].urb != NULL) |
usb_free_urb(pdev->sbuf[i].urb); |
pdev->sbuf[i].urb = NULL; |
i--; |
} |
return ret; |
} |
/* init URB structure */ |
for (i = 0; i < MAX_ISO_BUFS; i++) { |
urb = pdev->sbuf[i].urb; |
urb->interval = 1; // devik |
urb->dev = udev; |
urb->pipe = usb_rcvisocpipe(udev, pdev->vendpoint); |
urb->transfer_flags = URB_ISO_ASAP; |
urb->transfer_buffer = pdev->sbuf[i].data; |
urb->transfer_buffer_length = ISO_BUFFER_SIZE; |
urb->complete = pwc_isoc_handler; |
urb->context = pdev; |
urb->start_frame = 0; |
urb->number_of_packets = ISO_FRAMES_PER_DESC; |
for (j = 0; j < ISO_FRAMES_PER_DESC; j++) { |
urb->iso_frame_desc[j].offset = j * ISO_MAX_FRAME_SIZE; |
urb->iso_frame_desc[j].length = pdev->vmax_packet_size; |
} |
} |
/* link */ |
for (i = 0; i < MAX_ISO_BUFS; i++) { |
ret = usb_submit_urb(pdev->sbuf[i].urb, GFP_KERNEL); |
if (ret) |
Err("isoc_init() submit_urb %d failed with error %d\n", i, ret); |
else |
Trace(TRACE_MEMORY, "URB 0x%p submitted.\n", pdev->sbuf[i].urb); |
} |
/* All is done... */ |
pdev->iso_init = 1; |
Trace(TRACE_OPEN, "<< pwc_isoc_init()\n"); |
return 0; |
} |
static void pwc_isoc_cleanup(struct pwc_device *pdev) |
{ |
int i; |
Trace(TRACE_OPEN, ">> pwc_isoc_cleanup()\n"); |
if (pdev == NULL) |
return; |
/* Unlinking ISOC buffers one by one */ |
for (i = 0; i < MAX_ISO_BUFS; i++) { |
struct urb *urb; |
urb = pdev->sbuf[i].urb; |
if (urb != 0) { |
if (pdev->iso_init) { |
Trace(TRACE_MEMORY, "Unlinking URB %p\n", urb); |
usb_unlink_urb(urb); |
} |
Trace(TRACE_MEMORY, "Freeing URB\n"); |
usb_free_urb(urb); |
pdev->sbuf[i].urb = NULL; |
} |
} |
/* Stop camera, but only if we are sure the camera is still there (unplug |
is signalled by EPIPE) |
*/ |
if (pdev->error_status && pdev->error_status != EPIPE) { |
Trace(TRACE_OPEN, "Setting alternate interface 0.\n"); |
usb_set_interface(pdev->udev, 0, 0); |
} |
pdev->iso_init = 0; |
Trace(TRACE_OPEN, "<< pwc_isoc_cleanup()\n"); |
} |
int pwc_try_video_mode(struct pwc_device *pdev, int width, int height, int new_fps, int new_compression, int new_snapshot) |
{ |
int ret, start; |
/* Stop isoc stuff */ |
pwc_isoc_cleanup(pdev); |
/* Reset parameters */ |
pwc_reset_buffers(pdev); |
/* Try to set video mode... */ |
start = ret = pwc_set_video_mode(pdev, width, height, new_fps, new_compression, new_snapshot); |
if (ret) { |
Trace(TRACE_FLOW, "pwc_set_video_mode attempt 1 failed.\n"); |
/* That failed... restore old mode (we know that worked) */ |
start = pwc_set_video_mode(pdev, pdev->view.x, pdev->view.y, pdev->vframes, pdev->vcompression, pdev->vsnapshot); |
if (start) { |
Trace(TRACE_FLOW, "pwc_set_video_mode attempt 2 failed.\n"); |
} |
} |
if (start == 0) |
{ |
if (pwc_isoc_init(pdev) < 0) |
{ |
Info("Failed to restart ISOC transfers in pwc_try_video_mode.\n"); |
ret = -EAGAIN; /* let's try again, who knows if it works a second time */ |
} |
} |
pdev->drop_frames++; /* try to avoid garbage during switch */ |
return ret; /* Return original error code */ |
} |
/***************************************************************************/ |
/* Video4Linux functions */ |
extern struct video_device *video_device[]; |
/*static*/ int pwc_video_open(struct inode *inode, struct file *file) |
{ |
int i; |
struct video_device *vdev = video_device[0]; //video_devdata(file); |
struct pwc_device *pdev; |
Trace(TRACE_OPEN, ">> video_open called(vdev = 0x%p).\n", vdev); |
pdev = (struct pwc_device *)vdev->priv; |
if (pdev == NULL) |
BUG(); |
if (pdev->vopen) |
return -EBUSY; |
down(&pdev->modlock); |
if (!pdev->usb_init) { |
Trace(TRACE_OPEN, "Doing first time initialization.\n"); |
pdev->usb_init = 1; |
if (pwc_trace & TRACE_OPEN) { |
/* Query sensor type */ |
const char *sensor_type = NULL; |
i = pwc_get_cmos_sensor(pdev); |
if (i > 0) |
{ |
switch(i) { |
case 0x00: sensor_type = "Hyundai CMOS sensor"; break; |
case 0x20: sensor_type = "Sony CCD sensor + TDA8787"; break; |
case 0x2E: sensor_type = "Sony CCD sensor + Exas 98L59"; break; |
case 0x2F: sensor_type = "Sony CCD sensor + ADI 9804"; break; |
case 0x30: sensor_type = "Sharp CCD sensor + TDA8787"; break; |
case 0x3E: sensor_type = "Sharp CCD sensor + Exas 98L59"; break; |
case 0x3F: sensor_type = "Sharp CCD sensor + ADI 9804"; break; |
case 0x40: sensor_type = "UPA 1021 sensor"; break; |
case 0x100: sensor_type = "VGA sensor"; break; |
case 0x101: sensor_type = "PAL MR sensor"; break; |
default: sensor_type = "unknown type of sensor"; break; |
} |
} |
if (sensor_type != NULL) |
Info("This %s camera is equipped with a %s (%d).\n", pdev->vdev.name, sensor_type, i); |
} |
} |
/* Turn on camera */ |
if (power_save) { |
i = pwc_camera_power(pdev, 1); |
if (i < 0) |
Info("Failed to restore power to the camera! (%d)\n", i); |
} |
/* Set LED on/off time */ |
if (pwc_set_leds(pdev, led_on, led_off) < 0) |
Info("Failed to set LED on/off time.\n"); |
/* Find our decompressor, if any */ |
pdev->decompressor = pwc_find_decompressor(pdev->type); |
#if PWC_DEBUG |
Debug("Found decompressor for %d at 0x%p\n", pdev->type, pdev->decompressor); |
#endif |
pwc_construct(pdev); /* set min/max sizes correct */ |
/* So far, so good. Allocate memory. */ |
i = pwc_allocate_buffers(pdev); |
if (i < 0) { |
Trace(TRACE_OPEN, "Failed to allocate buffer memory.\n"); |
up(&pdev->modlock); |
return i; |
} |
/* Reset buffers & parameters */ |
pwc_reset_buffers(pdev); |
for (i = 0; i < default_mbufs; i++) |
pdev->image_used[i] = 0; |
pdev->vframe_count = 0; |
pdev->vframes_dumped = 0; |
pdev->vframes_error = 0; |
pdev->visoc_errors = 0; |
pdev->error_status = 0; |
#if PWC_DEBUG |
pdev->sequence = 0; |
#endif |
pwc_construct(pdev); /* set min/max sizes correct */ |
/* Set some defaults */ |
pdev->vsnapshot = 0; |
/* Start iso pipe for video; first try the last used video size |
(or the default one); if that fails try QCIF/10 or QSIF/10; |
it that fails too, give up. |
*/ |
i = pwc_set_video_mode(pdev, pwc_image_sizes[pdev->vsize].x, pwc_image_sizes[pdev->vsize].y, pdev->vframes, pdev->vcompression, 0); |
if (i) { |
Trace(TRACE_OPEN, "First attempt at set_video_mode failed.\n"); |
if (pdev->type == 730 || pdev->type == 740 || pdev->type == 750) |
i = pwc_set_video_mode(pdev, pwc_image_sizes[PSZ_QSIF].x, pwc_image_sizes[PSZ_QSIF].y, 10, pdev->vcompression, 0); |
else |
i = pwc_set_video_mode(pdev, pwc_image_sizes[PSZ_QCIF].x, pwc_image_sizes[PSZ_QCIF].y, 10, pdev->vcompression, 0); |
} |
if (i) { |
Trace(TRACE_OPEN, "Second attempt at set_video_mode failed.\n"); |
up(&pdev->modlock); |
return i; |
} |
i = pwc_isoc_init(pdev); |
if (i) { |
Trace(TRACE_OPEN, "Failed to init ISOC stuff = %d.\n", i); |
up(&pdev->modlock); |
return i; |
} |
pdev->vopen++; |
//file->private_data = vdev; |
/* lock decompressor; this has a small race condition, since we |
could in theory unload pwcx.o between pwc_find_decompressor() |
above and this call. I doubt it's ever going to be a problem. |
*/ |
if (pdev->decompressor != NULL) |
pdev->decompressor->lock(); |
up(&pdev->modlock); |
Trace(TRACE_OPEN, "<< video_open() returns 0.\n"); |
return 0; |
} |
/* Note that all cleanup is done in the reverse order as in _open */ |
static int pwc_video_close(struct inode *inode, struct file *file) |
{ |
struct video_device *vdev = video_device[0]; //file->private_data; |
struct pwc_device *pdev; |
int i; |
Trace(TRACE_OPEN, ">> video_close called(vdev = 0x%p).\n", vdev); |
pdev = (struct pwc_device *)vdev->priv; |
if (pdev->vopen == 0) |
Info("video_close() called on closed device?\n"); |
/* Dump statistics, but only if a reasonable amount of frames were |
processed (to prevent endless log-entries in case of snap-shot |
programs) |
*/ |
if (pdev->vframe_count > 20) |
Info("Closing video device: %d frames received, dumped %d frames, %d frames with errors.\n", pdev->vframe_count, pdev->vframes_dumped, pdev->vframes_error); |
if (pdev->decompressor != NULL) { |
pdev->decompressor->exit(); |
pdev->decompressor->unlock(); |
pdev->decompressor = NULL; |
} |
pwc_isoc_cleanup(pdev); |
pwc_free_buffers(pdev); |
/* Turn off LEDS and power down camera, but only when not unplugged */ |
if (pdev->error_status != EPIPE) { |
/* Turn LEDs off */ |
if (pwc_set_leds(pdev, 0, 0) < 0) |
Info("Failed to set LED on/off time.\n"); |
if (power_save) { |
i = pwc_camera_power(pdev, 0); |
if (i < 0) |
Err("Failed to power down camera (%d)\n", i); |
} |
} |
pdev->vopen = 0; |
Trace(TRACE_OPEN, "<< video_close()\n"); |
return 0; |
} |
static void pwc_video_release(struct video_device *vfd) |
{ |
Trace(TRACE_OPEN, "pwc_video_release() called. Now what?\n"); |
} |
/* |
* FIXME: what about two parallel reads ???? |
* ANSWER: Not supported. You can't open the device more than once, |
despite what the V4L1 interface says. First, I don't see |
the need, second there's no mechanism of alerting the |
2nd/3rd/... process of events like changing image size. |
And I don't see the point of blocking that for the |
2nd/3rd/... process. |
In multi-threaded environments reading parallel from any |
device is tricky anyhow. |
*/ |
/*static*/ ssize_t pwc_video_read(struct file *file, char *buf, |
size_t count, loff_t *ppos) |
{ |
struct video_device *vdev = video_device[0]; //file->private_data; |
struct pwc_device *pdev; |
int noblock = 0; //file->f_flags & O_NONBLOCK; |
DECLARE_WAITQUEUE(wait, current); |
int bytes_to_read; |
//printk(KERN_INFO "@0\n"); |
//wait_ms(1000); |
// Trace(TRACE_READ, "video_read(0x%p, %p, %d) called.\n", vdev, buf, count); |
if (vdev == NULL) |
return -EFAULT; |
pdev = vdev->priv; |
if (pdev == NULL) |
return -EFAULT; |
if (pdev->error_status) |
return -pdev->error_status; /* Something happened, report what. */ |
//printk(KERN_INFO "@1\n"); |
//wait_ms(1000); |
/* In case we're doing partial reads, we don't have to wait for a frame */ |
if (pdev->image_read_pos == 0) { |
/* Do wait queueing according to the (doc)book */ |
add_wait_queue(&pdev->frameq, &wait); |
while (pdev->full_frames == NULL) { |
/* Check for unplugged/etc. here */ |
if (pdev->error_status) { |
remove_wait_queue(&pdev->frameq, &wait); |
set_current_state(TASK_RUNNING); |
return -pdev->error_status ; |
} |
if (noblock) { |
remove_wait_queue(&pdev->frameq, &wait); |
set_current_state(TASK_RUNNING); |
return -EWOULDBLOCK; |
} |
if (signal_pending(current)) { |
remove_wait_queue(&pdev->frameq, &wait); |
set_current_state(TASK_RUNNING); |
return -ERESTARTSYS; |
} |
schedule(); |
set_current_state(TASK_INTERRUPTIBLE); |
} |
remove_wait_queue(&pdev->frameq, &wait); |
set_current_state(TASK_RUNNING); |
//printk(KERN_INFO "@5\n"); |
//wait_ms(1000); |
/* Decompress and release frame */ |
if (pwc_handle_frame(pdev)) |
return -EFAULT; |
} |
//printk(KERN_INFO "@6\n"); |
//wait_ms(1000); |
// Trace(TRACE_READ, "Copying data to user space.\n"); |
if (pdev->vpalette == VIDEO_PALETTE_RAW) { |
//printk(KERN_INFO "@7\n"); |
//wait_ms(1000); |
bytes_to_read = pdev->frame_size; |
} else |
bytes_to_read = pdev->view.size; |
/* copy bytes to user space; we allow for partial reads */ |
//printk(KERN_INFO "@8\n"); |
//wait_ms(1000); |
if (count + pdev->image_read_pos > bytes_to_read) |
count = bytes_to_read - pdev->image_read_pos; |
if (copy_to_user(buf, pdev->image_ptr[pdev->fill_image] + pdev->image_read_pos, count)) |
return -EFAULT; |
pdev->image_read_pos += count; |
if (pdev->image_read_pos >= bytes_to_read) { /* All data has been read */ |
//printk(KERN_INFO "@9\n"); |
//wait_ms(1000); |
pdev->image_read_pos = 0; |
pwc_next_image(pdev); |
} |
return count; |
} |
static unsigned int pwc_video_poll(struct file *file, poll_table *wait) |
{ |
struct video_device *vdev = video_device[0]; //file->private_data; |
struct pwc_device *pdev; |
if (vdev == NULL) |
return -EFAULT; |
pdev = vdev->priv; |
if (pdev == NULL) |
return -EFAULT; |
poll_wait(file, &pdev->frameq, wait); |
if (pdev->error_status) |
return POLLERR; |
if (pdev->full_frames != NULL) /* we have frames waiting */ |
return (POLLIN | POLLRDNORM); |
return 0; |
} |
/*static*/ int pwc_video_do_ioctl(struct inode *inode, struct file *file, |
unsigned int cmd, void *arg) |
{ |
struct video_device *vdev = video_device[0]; //file->private_data; |
struct pwc_device *pdev; |
DECLARE_WAITQUEUE(wait, current); |
if (vdev == NULL) |
return -EFAULT; |
pdev = vdev->priv; |
if (pdev == NULL) |
return -EFAULT; |
switch (cmd) { |
/* Query cabapilities */ |
case VIDIOCGCAP: |
{ |
struct video_capability *caps = arg; |
strcpy(caps->name, vdev->name); |
caps->type = VID_TYPE_CAPTURE; |
caps->channels = 1; |
caps->audios = 1; |
caps->minwidth = pdev->view_min.x; |
caps->minheight = pdev->view_min.y; |
caps->maxwidth = pdev->view_max.x; |
caps->maxheight = pdev->view_max.y; |
break; |
} |
/* Channel functions (simulate 1 channel) */ |
case VIDIOCGCHAN: |
{ |
struct video_channel *v = arg; |
if (v->channel != 0) |
return -EINVAL; |
v->flags = 0; |
v->tuners = 0; |
v->type = VIDEO_TYPE_CAMERA; |
strcpy(v->name, "Webcam"); |
return 0; |
} |
case VIDIOCSCHAN: |
{ |
/* The spec says the argument is an integer, but |
the bttv driver uses a video_channel arg, which |
makes sense becasue it also has the norm flag. |
*/ |
struct video_channel *v = arg; |
if (v->channel != 0) |
return -EINVAL; |
return 0; |
} |
/* Picture functions; contrast etc. */ |
case VIDIOCGPICT: |
{ |
struct video_picture *p = arg; |
int val; |
val = pwc_get_brightness(pdev); |
if (val >= 0) |
p->brightness = val; |
else |
p->brightness = 0xffff; |
val = pwc_get_contrast(pdev); |
if (val >= 0) |
p->contrast = val; |
else |
p->contrast = 0xffff; |
/* Gamma, Whiteness, what's the difference? :) */ |
val = pwc_get_gamma(pdev); |
if (val >= 0) |
p->whiteness = val; |
else |
p->whiteness = 0xffff; |
val = pwc_get_saturation(pdev); |
if (val >= 0) |
p->colour = val; |
else |
p->colour = 0xffff; |
p->depth = 24; |
p->palette = pdev->vpalette; |
p->hue = 0xFFFF; /* N/A */ |
break; |
} |
case VIDIOCSPICT: |
{ |
struct video_picture *p = arg; |
/* |
* FIXME: Suppose we are mid read |
ANSWER: No problem: the firmware of the camera |
can handle brightness/contrast/etc |
changes at _any_ time, and the palette |
is used exactly once in the uncompress |
routine. |
*/ |
pwc_set_brightness(pdev, p->brightness); |
pwc_set_contrast(pdev, p->contrast); |
pwc_set_gamma(pdev, p->whiteness); |
pwc_set_saturation(pdev, p->colour); |
if (p->palette && p->palette != pdev->vpalette) { |
switch (p->palette) { |
case VIDEO_PALETTE_YUV420P: |
case VIDEO_PALETTE_RAW: |
pdev->vpalette = p->palette; |
return pwc_try_video_mode(pdev, pdev->image.x, pdev->image.y, pdev->vframes, pdev->vcompression, pdev->vsnapshot); |
break; |
default: |
return -EINVAL; |
break; |
} |
} |
break; |
} |
/* Window/size parameters */ |
case VIDIOCGWIN: |
{ |
struct video_window *vw = arg; |
vw->x = 0; |
vw->y = 0; |
vw->width = pdev->view.x; |
vw->height = pdev->view.y; |
vw->chromakey = 0; |
vw->flags = (pdev->vframes << PWC_FPS_SHIFT) | |
(pdev->vsnapshot ? PWC_FPS_SNAPSHOT : 0); |
break; |
} |
case VIDIOCSWIN: |
{ |
struct video_window *vw = arg; |
int fps, snapshot, ret; |
fps = (vw->flags & PWC_FPS_FRMASK) >> PWC_FPS_SHIFT; |
snapshot = vw->flags & PWC_FPS_SNAPSHOT; |
if (fps == 0) |
fps = pdev->vframes; |
if (pdev->view.x == vw->width && pdev->view.y && fps == pdev->vframes && snapshot == pdev->vsnapshot) |
return 0; |
ret = pwc_try_video_mode(pdev, vw->width, vw->height, fps, pdev->vcompression, snapshot); |
if (ret) |
return ret; |
break; |
} |
/* We don't have overlay support (yet) */ |
case VIDIOCGFBUF: |
{ |
struct video_buffer *vb = arg; |
memset(vb,0,sizeof(*vb)); |
break; |
} |
/* mmap() functions */ |
case VIDIOCGMBUF: |
{ |
/* Tell the user program how much memory is needed for a mmap() */ |
struct video_mbuf *vm = arg; |
int i; |
memset(vm, 0, sizeof(*vm)); |
vm->size = default_mbufs * pdev->len_per_image; |
vm->frames = default_mbufs; /* double buffering should be enough for most applications */ |
for (i = 0; i < default_mbufs; i++) |
vm->offsets[i] = i * pdev->len_per_image; |
break; |
} |
case VIDIOCMCAPTURE: |
{ |
/* Start capture into a given image buffer (called 'frame' in video_mmap structure) */ |
struct video_mmap *vm = arg; |
Trace(TRACE_READ, "VIDIOCMCAPTURE: %dx%d, frame %d, format %d\n", vm->width, vm->height, vm->frame, vm->format); |
if (vm->frame < 0 || vm->frame >= default_mbufs) |
return -EINVAL; |
/* xawtv is nasty. It probes the available palettes |
by setting a very small image size and trying |
various palettes... The driver doesn't support |
such small images, so I'm working around it. |
*/ |
if (vm->format) |
{ |
switch (vm->format) |
{ |
case VIDEO_PALETTE_YUV420P: |
case VIDEO_PALETTE_RAW: |
break; |
default: |
return -EINVAL; |
break; |
} |
} |
if ((vm->width != pdev->view.x || vm->height != pdev->view.y) && |
(vm->width >= pdev->view_min.x && vm->height >= pdev->view_min.y)) { |
int ret; |
Trace(TRACE_OPEN, "VIDIOCMCAPTURE: changing size to please xawtv :-(.\n"); |
ret = pwc_try_video_mode(pdev, vm->width, vm->height, pdev->vframes, pdev->vcompression, pdev->vsnapshot); |
if (ret) |
return ret; |
} /* ... size mismatch */ |
/* FIXME: should we lock here? */ |
if (pdev->image_used[vm->frame]) |
return -EBUSY; /* buffer wasn't available. Bummer */ |
pdev->image_used[vm->frame] = 1; |
/* Okay, we're done here. In the SYNC call we wait until a |
frame comes available, then expand image into the given |
buffer. |
In contrast to the CPiA cam the Philips cams deliver a |
constant stream, almost like a grabber card. Also, |
we have separate buffers for the rawdata and the image, |
meaning we can nearly always expand into the requested buffer. |
*/ |
Trace(TRACE_READ, "VIDIOCMCAPTURE done.\n"); |
break; |
} |
case VIDIOCSYNC: |
{ |
/* The doc says: "Whenever a buffer is used it should |
call VIDIOCSYNC to free this frame up and continue." |
The only odd thing about this whole procedure is |
that MCAPTURE flags the buffer as "in use", and |
SYNC immediately unmarks it, while it isn't |
after SYNC that you know that the buffer actually |
got filled! So you better not start a CAPTURE in |
the same frame immediately (use double buffering). |
This is not a problem for this cam, since it has |
extra intermediate buffers, but a hardware |
grabber card will then overwrite the buffer |
you're working on. |
*/ |
int *mbuf = arg; |
int ret; |
Trace(TRACE_READ, "VIDIOCSYNC called (%d).\n", *mbuf); |
/* bounds check */ |
if (*mbuf < 0 || *mbuf >= default_mbufs) |
return -EINVAL; |
/* check if this buffer was requested anyway */ |
if (pdev->image_used[*mbuf] == 0) |
return -EINVAL; |
/* Add ourselves to the frame wait-queue. |
FIXME: needs auditing for safety. |
QUESTION: In what respect? I think that using the |
frameq is safe now. |
*/ |
add_wait_queue(&pdev->frameq, &wait); |
while (pdev->full_frames == NULL) { |
if (pdev->error_status) { |
remove_wait_queue(&pdev->frameq, &wait); |
set_current_state(TASK_RUNNING); |
return -pdev->error_status; |
} |
if (signal_pending(current)) { |
remove_wait_queue(&pdev->frameq, &wait); |
set_current_state(TASK_RUNNING); |
return -ERESTARTSYS; |
} |
schedule(); |
set_current_state(TASK_INTERRUPTIBLE); |
} |
remove_wait_queue(&pdev->frameq, &wait); |
set_current_state(TASK_RUNNING); |
/* The frame is ready. Expand in the image buffer |
requested by the user. I don't care if you |
mmap() 5 buffers and request data in this order: |
buffer 4 2 3 0 1 2 3 0 4 3 1 . . . |
Grabber hardware may not be so forgiving. |
*/ |
Trace(TRACE_READ, "VIDIOCSYNC: frame ready.\n"); |
pdev->fill_image = *mbuf; /* tell in which buffer we want the image to be expanded */ |
/* Decompress, etc */ |
ret = pwc_handle_frame(pdev); |
pdev->image_used[*mbuf] = 0; |
if (ret) |
return -EFAULT; |
break; |
} |
case VIDIOCGAUDIO: |
{ |
struct video_audio *v = arg; |
strcpy(v->name, "Microphone"); |
v->audio = -1; /* unknown audio minor */ |
v->flags = 0; |
v->mode = VIDEO_SOUND_MONO; |
v->volume = 0; |
v->bass = 0; |
v->treble = 0; |
v->balance = 0x8000; |
v->step = 1; |
break; |
} |
case VIDIOCSAUDIO: |
{ |
/* Dummy: nothing can be set */ |
break; |
} |
case VIDIOCGUNIT: |
{ |
struct video_unit *vu = arg; |
vu->video = pdev->vdev.minor & 0x3F; |
vu->audio = -1; /* not known yet */ |
vu->vbi = -1; |
vu->radio = -1; |
vu->teletext = -1; |
break; |
} |
default: |
return pwc_ioctl(pdev, cmd, arg); |
} /* ..switch */ |
return 0; |
} |
/*static*/ int pwc_video_ioctl(struct inode *inode, struct file *file, |
unsigned int cmd, unsigned long arg) |
{ |
return video_usercopy(inode, file, cmd, arg, pwc_video_do_ioctl); |
} |
static int pwc_video_mmap(struct file *file, struct vm_area_struct *vma) |
{ |
struct video_device *vdev = video_device[0]; //file->private_data; |
struct pwc_device *pdev; |
unsigned long start = vma->vm_start; |
unsigned long size = vma->vm_end-vma->vm_start; |
unsigned long page, pos; |
Trace(TRACE_MEMORY, "mmap(0x%p, 0x%lx, %lu) called.\n", vdev, start, size); |
pdev = vdev->priv; |
pos = (unsigned long)pdev->image_data; |
while (size > 0) { |
page = kvirt_to_pa(pos); |
if (remap_page_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) |
return -EAGAIN; |
start += PAGE_SIZE; |
pos += PAGE_SIZE; |
if (size > PAGE_SIZE) |
size -= PAGE_SIZE; |
else |
size = 0; |
} |
return 0; |
} |
/***************************************************************************/ |
/* USB functions */ |
/* This function gets called when a new device is plugged in or the usb core |
* is loaded. |
*/ |
static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id *id) |
{ |
struct usb_device *udev = interface_to_usbdev(intf); |
struct pwc_device *pdev = NULL; |
int vendor_id, product_id, type_id; |
int i, hint; |
int features = 0; |
int video_nr = -1; /* default: use next available device */ |
char serial_number[30], *name; |
/* Check if we can handle this device */ |
Trace(TRACE_PROBE, "probe() called [%04X %04X], if %d\n", |
udev->descriptor.idVendor, udev->descriptor.idProduct, |
intf->altsetting->desc.bInterfaceNumber); |
/* the interfaces are probed one by one. We are only interested in the |
video interface (0) now. |
Interface 1 is the Audio Control, and interface 2 Audio itself. |
*/ |
if (intf->altsetting->desc.bInterfaceNumber > 0) |
return -ENODEV; |
vendor_id = udev->descriptor.idVendor; |
product_id = udev->descriptor.idProduct; |
if (vendor_id == 0x0471) { |
switch (product_id) { |
case 0x0302: |
Info("Philips PCA645VC USB webcam detected.\n"); |
name = "Philips 645 webcam"; |
type_id = 645; |
break; |
case 0x0303: |
Info("Philips PCA646VC USB webcam detected.\n"); |
name = "Philips 646 webcam"; |
type_id = 646; |
break; |
case 0x0304: |
Info("Askey VC010 type 2 USB webcam detected.\n"); |
name = "Askey VC010 webcam"; |
type_id = 646; |
break; |
case 0x0307: |
Info("Philips PCVC675K (Vesta) USB webcam detected.\n"); |
name = "Philips 675 webcam"; |
type_id = 675; |
break; |
case 0x0308: |
Info("Philips PCVC680K (Vesta Pro) USB webcam detected.\n"); |
name = "Philips 680 webcam"; |
type_id = 680; |
break; |
case 0x030C: |
Info("Philips PCVC690K (Vesta Pro Scan) USB webcam detected.\n"); |
name = "Philips 690 webcam"; |
type_id = 690; |
break; |
case 0x0310: |
Info("Philips PCVC730K (ToUCam Fun)/PCVC830 (ToUCam II) USB webcam detected.\n"); |
name = "Philips 730 webcam"; |
type_id = 730; |
break; |
case 0x0311: |
Info("Philips PCVC740K (ToUCam Pro)/PCVC840 (ToUCam II) USB webcam detected.\n"); |
name = "Philips 740 webcam"; |
type_id = 740; |
break; |
case 0x0312: |
Info("Philips PCVC750K (ToUCam Pro Scan) USB webcam detected.\n"); |
name = "Philips 750 webcam"; |
type_id = 750; |
break; |
case 0x0313: |
Info("Philips PCVC720K/40 (ToUCam XS) USB webcam detected.\n"); |
name = "Philips 720K/40 webcam"; |
type_id = 720; |
break; |
default: |
return -ENODEV; |
break; |
} |
} |
else if (vendor_id == 0x069A) { |
switch(product_id) { |
case 0x0001: |
Info("Askey VC010 type 1 USB webcam detected.\n"); |
name = "Askey VC010 webcam"; |
type_id = 645; |
break; |
default: |
return -ENODEV; |
break; |
} |
} |
else if (vendor_id == 0x046d) { |
switch(product_id) { |
case 0x08b0: |
Info("Logitech QuickCam Pro 3000 USB webcam detected.\n"); |
name = "Logitech QuickCam Pro 3000"; |
type_id = 740; /* CCD sensor */ |
break; |
case 0x08b1: |
Info("Logitech QuickCam Notebook Pro USB webcam detected.\n"); |
name = "Logitech QuickCam Notebook Pro"; |
type_id = 740; /* CCD sensor */ |
break; |
case 0x08b2: |
Info("Logitech QuickCam 4000 Pro USB webcam detected.\n"); |
name = "Logitech QuickCam Pro 4000"; |
type_id = 740; /* CCD sensor */ |
break; |
case 0x08b3: |
Info("Logitech QuickCam Zoom USB webcam detected.\n"); |
name = "Logitech QuickCam Zoom"; |
type_id = 740; /* CCD sensor */ |
break; |
case 0x08B4: |
Info("Logitech QuickCam Zoom (new model) USB webcam detected.\n"); |
name = "Logitech QuickCam Zoom"; |
type_id = 740; /* CCD sensor */ |
break; |
case 0x08b5: |
Info("Logitech QuickCam Orbit/Sphere USB webcam detected.\n"); |
name = "Logitech QuickCam Orbit"; |
type_id = 740; /* CCD sensor */ |
features |= FEATURE_MOTOR_PANTILT; |
break; |
case 0x08b6: |
case 0x08b7: |
case 0x08b8: |
Info("Logitech QuickCam detected (reserved ID).\n"); |
name = "Logitech QuickCam (res.)"; |
type_id = 730; /* Assuming CMOS */ |
break; |
default: |
return -ENODEV; |
break; |
} |
} |
else if (vendor_id == 0x055d) { |
/* I don't know the difference between the C10 and the C30; |
I suppose the difference is the sensor, but both cameras |
work equally well with a type_id of 675 |
*/ |
switch(product_id) { |
case 0x9000: |
Info("Samsung MPC-C10 USB webcam detected.\n"); |
name = "Samsung MPC-C10"; |
type_id = 675; |
break; |
case 0x9001: |
Info("Samsung MPC-C30 USB webcam detected.\n"); |
name = "Samsung MPC-C30"; |
type_id = 675; |
break; |
default: |
return -ENODEV; |
break; |
} |
} |
else if (vendor_id == 0x041e) { |
switch(product_id) { |
case 0x400c: |
Info("Creative Labs Webcam 5 detected.\n"); |
name = "Creative Labs Webcam 5"; |
type_id = 730; |
break; |
case 0x4011: |
Info("Creative Labs Webcam Pro Ex detected.\n"); |
name = "Creative Labs Webcam Pro Ex"; |
type_id = 740; |
break; |
default: |
return -ENODEV; |
break; |
} |
} |
else if (vendor_id == 0x04cc) { |
switch(product_id) { |
case 0x8116: |
Info("Sotec Afina Eye USB webcam detected.\n"); |
name = "Sotec Afina Eye"; |
type_id = 730; |
break; |
default: |
return -ENODEV; |
break; |
} |
} |
else if (vendor_id == 0x06B) { |
switch(product_id) { |
case 0x8116: |
/* Basicly the same as the Sotec Afina Eye */ |
Info("AME CU-001 USB webcam detected.\n"); |
name = "AME CU-001"; |
type_id = 730; |
break; |
default: |
return -ENODEV; |
break; |
} |
} |
else if (vendor_id == 0x06be) { |
switch(product_id) { |
case 0x8116: |
/* This is essentially the same cam as the Sotec Afina Eye */ |
Info("AME Co. Afina Eye USB webcam detected.\n"); |
name = "AME Co. Afina Eye"; |
type_id = 750; |
break; |
default: |
return -ENODEV; |
break; |
} |
} |
else if (vendor_id == 0x0d81) { |
switch(product_id) { |
case 0x1900: |
Info("Visionite VCS-UC300 USB webcam detected.\n"); |
name = "Visionite VCS-UC300"; |
type_id = 740; /* CCD sensor */ |
break; |
case 0x1910: |
Info("Visionite VCS-UM100 USB webcam detected.\n"); |
name = "Visionite VCS-UM100"; |
type_id = 730; /* CMOS sensor */ |
break; |
default: |
return -ENODEV; |
break; |
} |
} |
else |
return -ENODEV; /* Not any of the know types; but the list keeps growing. */ |
memset(serial_number, 0, 30); |
usb_string(udev, udev->descriptor.iSerialNumber, serial_number, 29); |
Trace(TRACE_PROBE, "Device serial number is %s\n", serial_number); |
if (udev->descriptor.bNumConfigurations > 1) |
Info("Warning: more than 1 configuration available.\n"); |
/* Allocate structure, initialize pointers, mutexes, etc. and link it to the usb_device */ |
pdev = kmalloc(sizeof(struct pwc_device), GFP_KERNEL); |
if (pdev == NULL) { |
Err("Oops, could not allocate memory for pwc_device.\n"); |
return -ENOMEM; |
} |
memset(pdev, 0, sizeof(struct pwc_device)); |
pdev->type = type_id; |
pdev->vsize = default_size; |
pdev->vframes = default_fps; |
pdev->features = features; |
if (vendor_id == 0x046D && product_id == 0x08B5) |
{ |
/* Logitech QuickCam Orbit |
The ranges have been determined experimentally; they may differ from cam to cam. |
Also, the exact ranges left-right and up-down are different for my cam |
*/ |
pdev->angle_range.pan_min = -7000; |
pdev->angle_range.pan_max = 7000; |
pdev->angle_range.tilt_min = -3000; |
pdev->angle_range.tilt_max = 2500; |
pdev->angle_range.zoom_min = -1; |
pdev->angle_range.zoom_max = -1; |
} |
init_MUTEX(&pdev->modlock); |
pdev->ptrlock = SPIN_LOCK_UNLOCKED; |
pdev->udev = udev; |
init_waitqueue_head(&pdev->frameq); |
pdev->vcompression = pwc_preferred_compression; |
memcpy(&pdev->vdev, &pwc_template, sizeof(pwc_template)); |
strcpy(pdev->vdev.name, name); |
pdev->vdev.owner = THIS_MODULE; |
pdev->vdev.priv = pdev; |
pdev->release = udev->descriptor.bcdDevice; |
Trace(TRACE_PROBE, "Release: %04x\n", pdev->release); |
/* Now search device_hint[] table for a match, so we can hint a node number. */ |
for (hint = 0; hint < MAX_DEV_HINTS; hint++) { |
if (((device_hint[hint].type == -1) || (device_hint[hint].type == pdev->type)) && |
(device_hint[hint].pdev == NULL)) { |
/* so far, so good... try serial number */ |
if ((device_hint[hint].serial_number[0] == '*') || !strcmp(device_hint[hint].serial_number, serial_number)) { |
/* match! */ |
video_nr = device_hint[hint].device_node; |
Trace(TRACE_PROBE, "Found hint, will try to register as /dev/video%d\n", video_nr); |
break; |
} |
} |
} |
pdev->vdev.release = pwc_video_release; |
i = video_register_device(&pdev->vdev, VFL_TYPE_GRABBER, video_nr); |
if (i < 0) { |
Err("Failed to register as video device (%d).\n", i); |
kfree(pdev); /* Oops, no memory leaks please */ |
return -EIO; |
} |
else { |
Info("Registered as /dev/video%d.\n", pdev->vdev.minor & 0x3F); |
} |
/* occupy slot */ |
if (hint < MAX_DEV_HINTS) |
device_hint[hint].pdev = pdev; |
Trace(TRACE_PROBE, "probe() function returning struct at 0x%p.\n", pdev); |
usb_set_intfdata (intf, pdev); |
return 0; |
} |
/* The user janked out the cable... */ |
static void usb_pwc_disconnect(struct usb_interface *intf) |
{ |
struct pwc_device *pdev; |
int hint; |
lock_kernel(); |
pdev = usb_get_intfdata (intf); |
usb_set_intfdata (intf, NULL); |
if (pdev == NULL) { |
Err("pwc_disconnect() Called without private pointer.\n"); |
goto disconnect_out; |
} |
if (pdev->udev == NULL) { |
Err("pwc_disconnect() already called for %p\n", pdev); |
goto disconnect_out; |
} |
if (pdev->udev != interface_to_usbdev(intf)) { |
Err("pwc_disconnect() Woops: pointer mismatch udev/pdev.\n"); |
goto disconnect_out; |
} |
#ifdef PWC_MAGIC |
if (pdev->magic != PWC_MAGIC) { |
Err("pwc_disconnect() Magic number failed. Consult your scrolls and try again.\n"); |
goto disconnect_out; |
} |
#endif |
/* We got unplugged; this is signalled by an EPIPE error code */ |
if (pdev->vopen) { |
Info("Disconnected while webcam is in use!\n"); |
pdev->error_status = EPIPE; |
} |
/* Alert waiting processes */ |
wake_up_interruptible(&pdev->frameq); |
/* Wait until device is closed */ |
while (pdev->vopen) |
schedule(); |
/* Device is now closed, so we can safely unregister it */ |
Trace(TRACE_PROBE, "Unregistering video device in disconnect().\n"); |
video_unregister_device(&pdev->vdev); |
/* Free memory (don't set pdev to 0 just yet) */ |
kfree(pdev); |
disconnect_out: |
/* search device_hint[] table if we occupy a slot, by any chance */ |
for (hint = 0; hint < MAX_DEV_HINTS; hint++) |
if (device_hint[hint].pdev == pdev) |
device_hint[hint].pdev = NULL; |
unlock_kernel(); |
} |
/* *grunt* We have to do atoi ourselves :-( */ |
static int pwc_atoi(const char *s) |
{ |
int k = 0; |
k = 0; |
while (*s != '\0' && *s >= '0' && *s <= '9') { |
k = 10 * k + (*s - '0'); |
s++; |
} |
return k; |
} |
/* |
* Initialization code & module stuff |
*/ |
static char *size = NULL; |
static int fps = 0; |
static int fbufs = 0; |
static int mbufs = 0; |
static int trace = -1; |
static int compression = -1; |
static int leds[2] = { -1, -1 }; |
static char *dev_hint[MAX_DEV_HINTS] = { }; |
MODULE_PARM(size, "s"); |
MODULE_PARM_DESC(size, "Initial image size. One of sqcif, qsif, qcif, sif, cif, vga"); |
MODULE_PARM(fps, "i"); |
MODULE_PARM_DESC(fps, "Initial frames per second. Varies with model, useful range 5-30"); |
MODULE_PARM(fbufs, "i"); |
MODULE_PARM_DESC(fbufs, "Number of internal frame buffers to reserve"); |
MODULE_PARM(mbufs, "i"); |
MODULE_PARM_DESC(mbufs, "Number of external (mmap()ed) image buffers"); |
MODULE_PARM(trace, "i"); |
MODULE_PARM_DESC(trace, "For debugging purposes"); |
MODULE_PARM(power_save, "i"); |
MODULE_PARM_DESC(power_save, "Turn power save feature in camera on or off"); |
MODULE_PARM(compression, "i"); |
MODULE_PARM_DESC(compression, "Preferred compression quality. Range 0 (uncompressed) to 3 (high compression)"); |
MODULE_PARM(leds, "2i"); |
MODULE_PARM_DESC(leds, "LED on,off time in milliseconds"); |
MODULE_PARM(dev_hint, "0-20s"); |
MODULE_PARM_DESC(dev_hint, "Device node hints"); |
MODULE_DESCRIPTION("Philips & OEM USB webcam driver"); |
MODULE_AUTHOR("Nemosoft Unv. <webcamt@smcc.demon.nl>"); |
MODULE_LICENSE("GPL"); |
/*static*/ int __init usb_pwc_init(void) |
{ |
int i, sz; |
char *sizenames[PSZ_MAX] = { "sqcif", "qsif", "qcif", "sif", "cif", "vga" }; |
Info("Philips webcam module version " PWC_VERSION " loaded.\n"); |
Info("Supports Philips PCA645/646, PCVC675/680/690, PCVC720[40]/730/740/750 & PCVC830/840.\n"); |
Info("Also supports the Askey VC010, various Logitech Quickcams, Samsung MPC-C10 and MPC-C30,\n"); |
Info("the Creative WebCam 5 & Pro Ex, SOTEC Afina Eye and Visionite VCS-UC300 and VCS-UM100.\n"); |
if (fps) { |
if (fps < 4 || fps > 30) { |
Err("Framerate out of bounds (4-30).\n"); |
return -EINVAL; |
} |
default_fps = fps; |
Info("Default framerate set to %d.\n", default_fps); |
} |
if (size) { |
/* string; try matching with array */ |
for (sz = 0; sz < PSZ_MAX; sz++) { |
if (!strcmp(sizenames[sz], size)) { /* Found! */ |
default_size = sz; |
break; |
} |
} |
if (sz == PSZ_MAX) { |
Err("Size not recognized; try size=[sqcif | qsif | qcif | sif | cif | vga].\n"); |
return -EINVAL; |
} |
Info("Default image size set to %s [%dx%d].\n", sizenames[default_size], pwc_image_sizes[default_size].x, pwc_image_sizes[default_size].y); |
} |
if (mbufs) { |
if (mbufs < 1 || mbufs > MAX_IMAGES) { |
Err("Illegal number of mmap() buffers; use a number between 1 and %d.\n", MAX_IMAGES); |
return -EINVAL; |
} |
default_mbufs = mbufs; |
Info("Number of image buffers set to %d.\n", default_mbufs); |
} |
if (fbufs) { |
if (fbufs < 2 || fbufs > MAX_FRAMES) { |
Err("Illegal number of frame buffers; use a number between 2 and %d.\n", MAX_FRAMES); |
return -EINVAL; |
} |
default_fbufs = fbufs; |
Info("Number of frame buffers set to %d.\n", default_fbufs); |
} |
if (trace >= 0) { |
Info("Trace options: 0x%04x\n", trace); |
pwc_trace = trace; |
} |
if (compression >= 0) { |
if (compression > 3) { |
Err("Invalid compression setting; use a number between 0 (uncompressed) and 3 (high).\n"); |
return -EINVAL; |
} |
pwc_preferred_compression = compression; |
Info("Preferred compression set to %d.\n", pwc_preferred_compression); |
} |
if (power_save) |
Info("Enabling power save on open/close.\n"); |
if (leds[0] >= 0) |
led_on = leds[0]; |
if (leds[1] >= 0) |
led_off = leds[1]; |
/* Big device node whoopla. Basically, it allows you to assign a |
device node (/dev/videoX) to a camera, based on its type |
& serial number. The format is [type[.serialnumber]:]node. |
Any camera that isn't matched by these rules gets the next |
available free device node. |
*/ |
for (i = 0; i < MAX_DEV_HINTS; i++) { |
char *s, *colon, *dot; |
/* This loop also initializes the array */ |
device_hint[i].pdev = NULL; |
s = dev_hint[i]; |
if (s != NULL && *s != '\0') { |
device_hint[i].type = -1; /* wildcard */ |
strcpy(device_hint[i].serial_number, "*"); |
/* parse string: chop at ':' & '/' */ |
colon = dot = s; |
while (*colon != '\0' && *colon != ':') |
colon++; |
while (*dot != '\0' && *dot != '.') |
dot++; |
/* Few sanity checks */ |
if (*dot != '\0' && dot > colon) { |
Err("Malformed camera hint: the colon must be after the dot.\n"); |
return -EINVAL; |
} |
if (*colon == '\0') { |
/* No colon */ |
if (*dot != '\0') { |
Err("Malformed camera hint: no colon + device node given.\n"); |
return -EINVAL; |
} |
else { |
/* No type or serial number specified, just a number. */ |
device_hint[i].device_node = pwc_atoi(s); |
} |
} |
else { |
/* There's a colon, so we have at least a type and a device node */ |
device_hint[i].type = pwc_atoi(s); |
device_hint[i].device_node = pwc_atoi(colon + 1); |
if (*dot != '\0') { |
/* There's a serial number as well */ |
int k; |
dot++; |
k = 0; |
while (*dot != ':' && k < 29) { |
device_hint[i].serial_number[k++] = *dot; |
dot++; |
} |
device_hint[i].serial_number[k] = '\0'; |
} |
} |
#if PWC_DEBUG |
Debug("device_hint[%d]:\n", i); |
Debug(" type : %d\n", device_hint[i].type); |
Debug(" serial# : %s\n", device_hint[i].serial_number); |
Debug(" node : %d\n", device_hint[i].device_node); |
#endif |
} |
else |
device_hint[i].type = 0; /* not filled */ |
} /* ..for MAX_DEV_HINTS */ |
Trace(TRACE_PROBE, "Registering driver at address 0x%p.\n", &pwc_driver); |
return usb_register(&pwc_driver); |
} |
static void __exit usb_pwc_exit(void) |
{ |
Trace(TRACE_MODULE, "Deregistering driver.\n"); |
usb_deregister(&pwc_driver); |
Info("Philips webcam module removed.\n"); |
} |
module_init(usb_pwc_init); |
module_exit(usb_pwc_exit); |
/shark/trunk/drivers/usb/media/pwc-uncompress.c |
---|
0,0 → 1,180 |
/* Linux driver for Philips webcam |
Decompression frontend. |
(C) 1999-2003 Nemosoft Unv. (webcam@smcc.demon.nl) |
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 |
*/ |
/* |
This is where the decompression routines register and unregister |
themselves. It also has a decompressor wrapper function. |
*/ |
#include <asm/current.h> |
#include <asm/types.h> |
// #include <linux/sched.h> |
#include "pwc.h" |
#include "pwc-uncompress.h" |
/* This contains a list of all registered decompressors */ |
static LIST_HEAD(pwc_decompressor_list); |
/* Should the pwc_decompress structure ever change, we increase the |
version number so that we don't get nasty surprises, or can |
dynamically adjust our structure. |
*/ |
const int pwc_decompressor_version = PWC_MAJOR; |
/* Add decompressor to list, ignoring duplicates */ |
void pwc_register_decompressor(struct pwc_decompressor *pwcd) |
{ |
if (pwc_find_decompressor(pwcd->type) == NULL) { |
Trace(TRACE_PWCX, "Adding decompressor for model %d.\n", pwcd->type); |
list_add_tail(&pwcd->pwcd_list, &pwc_decompressor_list); |
} |
} |
/* Remove decompressor from list */ |
void pwc_unregister_decompressor(int type) |
{ |
struct pwc_decompressor *find; |
find = pwc_find_decompressor(type); |
if (find != NULL) { |
Trace(TRACE_PWCX, "Removing decompressor for model %d.\n", type); |
list_del(&find->pwcd_list); |
} |
} |
/* Find decompressor in list */ |
struct pwc_decompressor *pwc_find_decompressor(int type) |
{ |
struct list_head *tmp; |
struct pwc_decompressor *pwcd; |
list_for_each(tmp, &pwc_decompressor_list) { |
pwcd = list_entry(tmp, struct pwc_decompressor, pwcd_list); |
if (pwcd->type == type) |
return pwcd; |
} |
return NULL; |
} |
int pwc_decompress(struct pwc_device *pdev) |
{ |
struct pwc_frame_buf *fbuf; |
int n, line, col, stride; |
void *yuv, *image; |
u16 *src; |
u16 *dsty, *dstu, *dstv; |
if (pdev == NULL) |
return -EFAULT; |
#if defined(__KERNEL__) && defined(PWC_MAGIC) |
if (pdev->magic != PWC_MAGIC) { |
Err("pwc_decompress(): magic failed.\n"); |
return -EFAULT; |
} |
#endif |
fbuf = pdev->read_frame; |
if (fbuf == NULL) |
return -EFAULT; |
image = pdev->image_ptr[pdev->fill_image]; |
if (!image) |
return -EFAULT; |
yuv = fbuf->data + pdev->frame_header_size; /* Skip header */ |
/* Raw format; that's easy... */ |
if (pdev->vpalette == VIDEO_PALETTE_RAW) |
{ |
memcpy(image, yuv, pdev->frame_size); |
return 0; |
} |
if (pdev->vbandlength == 0) { |
/* Uncompressed mode. We copy the data into the output buffer, |
using the viewport size (which may be larger than the image |
size). Unfortunately we have to do a bit of byte stuffing |
to get the desired output format/size. |
*/ |
/* |
* We do some byte shuffling here to go from the |
* native format to YUV420P. |
*/ |
src = (u16 *)yuv; |
n = pdev->view.x * pdev->view.y; |
/* offset in Y plane */ |
stride = pdev->view.x * pdev->offset.y + pdev->offset.x; |
dsty = (u16 *)(image + stride); |
/* offsets in U/V planes */ |
stride = pdev->view.x * pdev->offset.y / 4 + pdev->offset.x / 2; |
dstu = (u16 *)(image + n + stride); |
dstv = (u16 *)(image + n + n / 4 + stride); |
/* increment after each line */ |
stride = (pdev->view.x - pdev->image.x) / 2; /* u16 is 2 bytes */ |
for (line = 0; line < pdev->image.y; line++) { |
for (col = 0; col < pdev->image.x; col += 4) { |
*dsty++ = *src++; |
*dsty++ = *src++; |
if (line & 1) |
*dstv++ = *src++; |
else |
*dstu++ = *src++; |
} |
dsty += stride; |
if (line & 1) |
dstv += (stride >> 1); |
else |
dstu += (stride >> 1); |
} |
} |
else { |
/* Compressed; the decompressor routines will write the data |
in planar format immediately. |
*/ |
int flags; |
flags = PWCX_FLAG_PLANAR; |
if (pdev->vsize == PSZ_VGA && pdev->vframes == 5 && pdev->vsnapshot) |
flags |= PWCX_FLAG_BAYER; |
if (pdev->decompressor) |
pdev->decompressor->decompress( |
&pdev->image, &pdev->view, &pdev->offset, |
yuv, image, |
flags, |
pdev->decompress_data, pdev->vbandlength); |
else |
return -ENXIO; /* No such device or address: missing decompressor */ |
} |
return 0; |
} |
/* Make sure these functions are available for the decompressor plugin |
both when this code is compiled into the kernel or as as module. |
*/ |
EXPORT_SYMBOL_NOVERS(pwc_decompressor_version); |
EXPORT_SYMBOL(pwc_register_decompressor); |
EXPORT_SYMBOL(pwc_unregister_decompressor); |
/shark/trunk/drivers/usb/media/pwc-ioctl.h |
---|
0,0 → 1,276 |
#ifndef PWC_IOCTL_H |
#define PWC_IOCTL_H |
/* (C) 2001-2003 Nemosoft Unv. webcam@smcc.demon.nl |
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 |
*/ |
/* This is pwc-ioctl.h belonging to PWC 8.12.1 |
It contains structures and defines to communicate from user space |
directly to the driver. |
*/ |
/* |
Changes |
2001/08/03 Alvarado Added ioctl constants to access methods for |
changing white balance and red/blue gains |
2002/12/15 G. H. Fernandez-Toribio VIDIOCGREALSIZE |
2003/12/13 Nemosft Unv. Some modifications to make interfacing to |
PWCX easier |
*/ |
/* These are private ioctl() commands, specific for the Philips webcams. |
They contain functions not found in other webcams, and settings not |
specified in the Video4Linux API. |
The #define names are built up like follows: |
VIDIOC VIDeo IOCtl prefix |
PWC Philps WebCam |
G optional: Get |
S optional: Set |
... the function |
*/ |
/* Enumeration of image sizes */ |
#define PSZ_SQCIF 0x00 |
#define PSZ_QSIF 0x01 |
#define PSZ_QCIF 0x02 |
#define PSZ_SIF 0x03 |
#define PSZ_CIF 0x04 |
#define PSZ_VGA 0x05 |
#define PSZ_MAX 6 |
/* The frame rate is encoded in the video_window.flags parameter using |
the upper 16 bits, since some flags are defined nowadays. The following |
defines provide a mask and shift to filter out this value. |
In 'Snapshot' mode the camera freezes its automatic exposure and colour |
balance controls. |
*/ |
#define PWC_FPS_SHIFT 16 |
#define PWC_FPS_MASK 0x00FF0000 |
#define PWC_FPS_FRMASK 0x003F0000 |
#define PWC_FPS_SNAPSHOT 0x00400000 |
/* structure for transfering x & y coordinates */ |
struct pwc_coord |
{ |
int x, y; /* guess what */ |
int size; /* size, or offset */ |
}; |
/* Used with VIDIOCPWCPROBE */ |
struct pwc_probe |
{ |
char name[32]; |
int type; |
}; |
/* pwc_whitebalance.mode values */ |
#define PWC_WB_INDOOR 0 |
#define PWC_WB_OUTDOOR 1 |
#define PWC_WB_FL 2 |
#define PWC_WB_MANUAL 3 |
#define PWC_WB_AUTO 4 |
/* Used with VIDIOCPWC[SG]AWB (Auto White Balance). |
Set mode to one of the PWC_WB_* values above. |
*red and *blue are the respective gains of these colour components inside |
the camera; range 0..65535 |
When 'mode' == PWC_WB_MANUAL, 'manual_red' and 'manual_blue' are set or read; |
otherwise undefined. |
'read_red' and 'read_blue' are read-only. |
*/ |
struct pwc_whitebalance |
{ |
int mode; |
int manual_red, manual_blue; /* R/W */ |
int read_red, read_blue; /* R/O */ |
}; |
/* |
'control_speed' and 'control_delay' are used in automatic whitebalance mode, |
and tell the camera how fast it should react to changes in lighting, and |
with how much delay. Valid values are 0..65535. |
*/ |
struct pwc_wb_speed |
{ |
int control_speed; |
int control_delay; |
}; |
/* Used with VIDIOCPWC[SG]LED */ |
struct pwc_leds |
{ |
int led_on; /* Led on-time; range = 0..25000 */ |
int led_off; /* Led off-time; range = 0..25000 */ |
}; |
/* Image size (used with GREALSIZE) */ |
struct pwc_imagesize |
{ |
int width; |
int height; |
}; |
/* Defines and structures for Motorized Pan & Tilt */ |
#define PWC_MPT_PAN 0x01 |
#define PWC_MPT_TILT 0x02 |
#define PWC_MPT_TIMEOUT 0x04 /* for status */ |
/* Set angles; when absolute = 1, the angle is absolute and the |
driver calculates the relative offset for you. This can only |
be used with VIDIOCPWCSANGLE; VIDIOCPWCGANGLE always returns |
absolute angles. |
*/ |
struct pwc_mpt_angles |
{ |
int absolute; /* write-only */ |
int pan; /* degrees * 100 */ |
int tilt; /* degress * 100 */ |
int zoom; /* N/A, set to -1 */ |
}; |
/* Range of angles of the camera, both horizontally and vertically. |
The zoom is not used, maybe in the future... |
*/ |
struct pwc_mpt_range |
{ |
int pan_min, pan_max; /* degrees * 100 */ |
int tilt_min, tilt_max; |
int zoom_min, zoom_max; /* -1, -1 */ |
}; |
struct pwc_mpt_status |
{ |
int status; |
int time_pan; |
int time_tilt; |
}; |
/* This is used for out-of-kernel decompression. With it, you can get |
all the necessary information to initialize and use the decompressor |
routines in standalone applications. |
*/ |
struct pwc_video_command |
{ |
int type; /* camera type (645, 675, 730, etc.) */ |
int release; /* release number */ |
int size; /* one of PSZ_* */ |
int alternate; |
int command_len; /* length of USB video command */ |
unsigned char command_buf[13]; /* Actual USB video command */ |
int bandlength; /* >0 = compressed */ |
int frame_size; /* Size of one (un)compressed frame */ |
}; |
/* Flags for PWCX subroutines. Not all modules honour all flags. */ |
#define PWCX_FLAG_PLANAR 0x0001 |
#define PWCX_FLAG_BAYER 0x0008 |
/* IOCTL definitions */ |
/* Restore user settings */ |
#define VIDIOCPWCRUSER _IO('v', 192) |
/* Save user settings */ |
#define VIDIOCPWCSUSER _IO('v', 193) |
/* Restore factory settings */ |
#define VIDIOCPWCFACTORY _IO('v', 194) |
/* You can manipulate the compression factor. A compression preference of 0 |
means use uncompressed modes when available; 1 is low compression, 2 is |
medium and 3 is high compression preferred. Of course, the higher the |
compression, the lower the bandwidth used but more chance of artefacts |
in the image. The driver automatically chooses a higher compression when |
the preferred mode is not available. |
*/ |
/* Set preferred compression quality (0 = uncompressed, 3 = highest compression) */ |
#define VIDIOCPWCSCQUAL _IOW('v', 195, int) |
/* Get preferred compression quality */ |
#define VIDIOCPWCGCQUAL _IOR('v', 195, int) |
/* This is a probe function; since so many devices are supported, it |
becomes difficult to include all the names in programs that want to |
check for the enhanced Philips stuff. So in stead, try this PROBE; |
it returns a structure with the original name, and the corresponding |
Philips type. |
To use, fill the structure with zeroes, call PROBE and if that succeeds, |
compare the name with that returned from VIDIOCGCAP; they should be the |
same. If so, you can be assured it is a Philips (OEM) cam and the type |
is valid. |
*/ |
#define VIDIOCPWCPROBE _IOR('v', 199, struct pwc_probe) |
/* Set AGC (Automatic Gain Control); int < 0 = auto, 0..65535 = fixed */ |
#define VIDIOCPWCSAGC _IOW('v', 200, int) |
/* Get AGC; int < 0 = auto; >= 0 = fixed, range 0..65535 */ |
#define VIDIOCPWCGAGC _IOR('v', 200, int) |
/* Set shutter speed; int < 0 = auto; >= 0 = fixed, range 0..65535 */ |
#define VIDIOCPWCSSHUTTER _IOW('v', 201, int) |
/* Color compensation (Auto White Balance) */ |
#define VIDIOCPWCSAWB _IOW('v', 202, struct pwc_whitebalance) |
#define VIDIOCPWCGAWB _IOR('v', 202, struct pwc_whitebalance) |
/* Auto WB speed */ |
#define VIDIOCPWCSAWBSPEED _IOW('v', 203, struct pwc_wb_speed) |
#define VIDIOCPWCGAWBSPEED _IOR('v', 203, struct pwc_wb_speed) |
/* LEDs on/off/blink; int range 0..65535 */ |
#define VIDIOCPWCSLED _IOW('v', 205, struct pwc_leds) |
#define VIDIOCPWCGLED _IOR('v', 205, struct pwc_leds) |
/* Contour (sharpness); int < 0 = auto, 0..65536 = fixed */ |
#define VIDIOCPWCSCONTOUR _IOW('v', 206, int) |
#define VIDIOCPWCGCONTOUR _IOR('v', 206, int) |
/* Backlight compensation; 0 = off, otherwise on */ |
#define VIDIOCPWCSBACKLIGHT _IOW('v', 207, int) |
#define VIDIOCPWCGBACKLIGHT _IOR('v', 207, int) |
/* Flickerless mode; = 0 off, otherwise on */ |
#define VIDIOCPWCSFLICKER _IOW('v', 208, int) |
#define VIDIOCPWCGFLICKER _IOR('v', 208, int) |
/* Dynamic noise reduction; 0 off, 3 = high noise reduction */ |
#define VIDIOCPWCSDYNNOISE _IOW('v', 209, int) |
#define VIDIOCPWCGDYNNOISE _IOR('v', 209, int) |
/* Real image size as used by the camera; tells you whether or not there's a gray border around the image */ |
#define VIDIOCPWCGREALSIZE _IOR('v', 210, struct pwc_imagesize) |
/* Motorized pan & tilt functions */ |
#define VIDIOCPWCMPTRESET _IOW('v', 211, int) |
#define VIDIOCPWCMPTGRANGE _IOR('v', 211, struct pwc_mpt_range) |
#define VIDIOCPWCMPTSANGLE _IOW('v', 212, struct pwc_mpt_angles) |
#define VIDIOCPWCMPTGANGLE _IOR('v', 212, struct pwc_mpt_angles) |
#define VIDIOCPWCMPTSTATUS _IOR('v', 213, struct pwc_mpt_status) |
/* Get the USB set-video command; needed for initializing libpwcx */ |
#define VIDIOCPWCGVIDCMD _IOR('v', 215, struct pwc_video_command) |
#endif |
/shark/trunk/drivers/usb/media/pwc_nala.h |
---|
0,0 → 1,66 |
/* SQCIF */ |
{ |
{0, 0, {0x04, 0x01, 0x03}}, |
{8, 0, {0x05, 0x01, 0x03}}, |
{7, 0, {0x08, 0x01, 0x03}}, |
{7, 0, {0x0A, 0x01, 0x03}}, |
{6, 0, {0x0C, 0x01, 0x03}}, |
{5, 0, {0x0F, 0x01, 0x03}}, |
{4, 0, {0x14, 0x01, 0x03}}, |
{3, 0, {0x18, 0x01, 0x03}}, |
}, |
/* QSIF */ |
{ |
{0}, |
{0}, |
{0}, |
{0}, |
{0}, |
{0}, |
{0}, |
{0}, |
}, |
/* QCIF */ |
{ |
{0, 0, {0x04, 0x01, 0x02}}, |
{8, 0, {0x05, 0x01, 0x02}}, |
{7, 0, {0x08, 0x01, 0x02}}, |
{6, 0, {0x0A, 0x01, 0x02}}, |
{5, 0, {0x0C, 0x01, 0x02}}, |
{4, 0, {0x0F, 0x01, 0x02}}, |
{1, 0, {0x14, 0x01, 0x02}}, |
{1, 0, {0x18, 0x01, 0x02}}, |
}, |
/* SIF */ |
{ |
{0}, |
{0}, |
{0}, |
{0}, |
{0}, |
{0}, |
{0}, |
{0}, |
}, |
/* CIF */ |
{ |
{4, 0, {0x04, 0x01, 0x01}}, |
{7, 1, {0x05, 0x03, 0x01}}, |
{6, 1, {0x08, 0x03, 0x01}}, |
{4, 1, {0x0A, 0x03, 0x01}}, |
{3, 1, {0x0C, 0x03, 0x01}}, |
{2, 1, {0x0F, 0x03, 0x01}}, |
{0}, |
{0}, |
}, |
/* VGA */ |
{ |
{0}, |
{0}, |
{0}, |
{0}, |
{0}, |
{0}, |
{0}, |
{0}, |
}, |
/shark/trunk/drivers/usb/media/pwc-uncompress.h |
---|
0,0 → 1,82 |
/* (C) 1999-2003 Nemosoft Unv. (webcam@smcc.demon.nl) |
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 |
*/ |
/* This file is the bridge between the kernel module and the plugin; it |
describes the structures and datatypes used in both modules. Any |
significant change should be reflected by increasing the |
pwc_decompressor_version major number. |
*/ |
#ifndef PWC_UNCOMPRESS_H |
#define PWC_UNCOMPRESS_H |
#include <linux/config.h> |
#include <linux/list.h> |
#include "pwc.h" |
/* from pwc-dec.h */ |
#define PWCX_FLAG_PLANAR 0x0001 |
/* */ |
#ifdef __cplusplus |
extern "C" { |
#endif |
/* The decompressor structure. |
Every type of decompressor registers itself with the main module. |
When a device is opened, it looks up the correct compressor, and |
uses that when a compressed video mode is requested. |
*/ |
struct pwc_decompressor |
{ |
int type; /* type of camera (645, 680, etc) */ |
int table_size; /* memory needed */ |
void (* init)(int type, int release, void *buffer, void *table); /* Initialization routine; should be called after each set_video_mode */ |
void (* exit)(void); /* Cleanup routine */ |
void (* decompress)(struct pwc_coord *image, struct pwc_coord *view, struct pwc_coord *offset, |
void *src, void *dst, int flags, |
void *table, int bandlength); |
void (* lock)(void); /* make sure module cannot be unloaded */ |
void (* unlock)(void); /* release lock on module */ |
struct list_head pwcd_list; |
}; |
/* Our structure version number. Is set to the version number major */ |
extern const int pwc_decompressor_version; |
/* Adds decompressor to list, based on its 'type' field (which matches the 'type' field in pwc_device; ignores any double requests */ |
extern void pwc_register_decompressor(struct pwc_decompressor *pwcd); |
/* Removes decompressor, based on the type number */ |
extern void pwc_unregister_decompressor(int type); |
/* Returns pointer to decompressor struct, or NULL if it doesn't exist */ |
extern struct pwc_decompressor *pwc_find_decompressor(int type); |
#ifdef CONFIG_USB_PWCX |
/* If the decompressor is compiled in, we must call these manually */ |
extern int usb_pwcx_init(void); |
extern void usb_pwcx_exit(void); |
#endif |
#ifdef __cplusplus |
} |
#endif |
#endif |
/shark/trunk/drivers/usb/media/pwc-ctrl.c |
---|
0,0 → 1,1747 |
/* Driver for Philips webcam |
Functions that send various control messages to the webcam, including |
video modes. |
(C) 1999-2003 Nemosoft Unv. (webcam@smcc.demon.nl) |
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 |
*/ |
/* |
Changes |
2001/08/03 Alvarado Added methods for changing white balance and |
red/green gains |
*/ |
/* Control functions for the cam; brightness, contrast, video mode, etc. */ |
#ifdef __KERNEL__ |
#include <asm/uaccess.h> |
#endif |
#include <asm/errno.h> |
#include "pwc.h" |
#include "pwc-ioctl.h" |
#include "pwc-uncompress.h" |
/* Request types: video */ |
#define SET_LUM_CTL 0x01 |
#define GET_LUM_CTL 0x02 |
#define SET_CHROM_CTL 0x03 |
#define GET_CHROM_CTL 0x04 |
#define SET_STATUS_CTL 0x05 |
#define GET_STATUS_CTL 0x06 |
#define SET_EP_STREAM_CTL 0x07 |
#define GET_EP_STREAM_CTL 0x08 |
#define SET_MPT_CTL 0x0D |
#define GET_MPT_CTL 0x0E |
/* Selectors for the Luminance controls [GS]ET_LUM_CTL */ |
#define AGC_MODE_FORMATTER 0x2000 |
#define PRESET_AGC_FORMATTER 0x2100 |
#define SHUTTER_MODE_FORMATTER 0x2200 |
#define PRESET_SHUTTER_FORMATTER 0x2300 |
#define PRESET_CONTOUR_FORMATTER 0x2400 |
#define AUTO_CONTOUR_FORMATTER 0x2500 |
#define BACK_LIGHT_COMPENSATION_FORMATTER 0x2600 |
#define CONTRAST_FORMATTER 0x2700 |
#define DYNAMIC_NOISE_CONTROL_FORMATTER 0x2800 |
#define FLICKERLESS_MODE_FORMATTER 0x2900 |
#define AE_CONTROL_SPEED 0x2A00 |
#define BRIGHTNESS_FORMATTER 0x2B00 |
#define GAMMA_FORMATTER 0x2C00 |
/* Selectors for the Chrominance controls [GS]ET_CHROM_CTL */ |
#define WB_MODE_FORMATTER 0x1000 |
#define AWB_CONTROL_SPEED_FORMATTER 0x1100 |
#define AWB_CONTROL_DELAY_FORMATTER 0x1200 |
#define PRESET_MANUAL_RED_GAIN_FORMATTER 0x1300 |
#define PRESET_MANUAL_BLUE_GAIN_FORMATTER 0x1400 |
#define COLOUR_MODE_FORMATTER 0x1500 |
#define SATURATION_MODE_FORMATTER1 0x1600 |
#define SATURATION_MODE_FORMATTER2 0x1700 |
/* Selectors for the Status controls [GS]ET_STATUS_CTL */ |
#define SAVE_USER_DEFAULTS_FORMATTER 0x0200 |
#define RESTORE_USER_DEFAULTS_FORMATTER 0x0300 |
#define RESTORE_FACTORY_DEFAULTS_FORMATTER 0x0400 |
#define READ_AGC_FORMATTER 0x0500 |
#define READ_SHUTTER_FORMATTER 0x0600 |
#define READ_RED_GAIN_FORMATTER 0x0700 |
#define READ_BLUE_GAIN_FORMATTER 0x0800 |
#define SENSOR_TYPE_FORMATTER1 0x0C00 |
#define READ_RAW_Y_MEAN_FORMATTER 0x3100 |
#define SET_POWER_SAVE_MODE_FORMATTER 0x3200 |
#define MIRROR_IMAGE_FORMATTER 0x3300 |
#define LED_FORMATTER 0x3400 |
#define SENSOR_TYPE_FORMATTER2 0x3700 |
/* Formatters for the Video Endpoint controls [GS]ET_EP_STREAM_CTL */ |
#define VIDEO_OUTPUT_CONTROL_FORMATTER 0x0100 |
/* Formatters for the motorized pan & tilt [GS]ET_MPT_CTL */ |
#define PT_RELATIVE_CONTROL_FORMATTER 0x01 |
#define PT_RESET_CONTROL_FORMATTER 0x02 |
#define PT_STATUS_FORMATTER 0x03 |
static char *size2name[PSZ_MAX] = |
{ |
"subQCIF", |
"QSIF", |
"QCIF", |
"SIF", |
"CIF", |
"VGA", |
}; |
/********/ |
/* Entries for the Nala (645/646) camera; the Nala doesn't have compression |
preferences, so you either get compressed or non-compressed streams. |
An alternate value of 0 means this mode is not available at all. |
*/ |
struct Nala_table_entry { |
char alternate; /* USB alternate setting */ |
int compressed; /* Compressed yes/no */ |
unsigned char mode[3]; /* precomputed mode table */ |
}; |
static struct Nala_table_entry Nala_table[PSZ_MAX][8] = |
{ |
#include "pwc_nala.h" |
}; |
/* This tables contains entries for the 675/680/690 (Timon) camera, with |
4 different qualities (no compression, low, medium, high). |
It lists the bandwidth requirements for said mode by its alternate interface |
number. An alternate of 0 means that the mode is unavailable. |
There are 6 * 4 * 4 entries: |
6 different resolutions subqcif, qsif, qcif, sif, cif, vga |
6 framerates: 5, 10, 15, 20, 25, 30 |
4 compression modi: none, low, medium, high |
When an uncompressed mode is not available, the next available compressed mode |
will be chosen (unless the decompressor is absent). Sometimes there are only |
1 or 2 compressed modes available; in that case entries are duplicated. |
*/ |
struct Timon_table_entry |
{ |
char alternate; /* USB alternate interface */ |
unsigned short packetsize; /* Normal packet size */ |
unsigned short bandlength; /* Bandlength when decompressing */ |
unsigned char mode[13]; /* precomputed mode settings for cam */ |
}; |
static struct Timon_table_entry Timon_table[PSZ_MAX][6][4] = |
{ |
#include "pwc_timon.h" |
}; |
/* Entries for the Kiara (730/740/750) camera */ |
struct Kiara_table_entry |
{ |
char alternate; /* USB alternate interface */ |
unsigned short packetsize; /* Normal packet size */ |
unsigned short bandlength; /* Bandlength when decompressing */ |
unsigned char mode[12]; /* precomputed mode settings for cam */ |
}; |
static struct Kiara_table_entry Kiara_table[PSZ_MAX][6][4] = |
{ |
#include "pwc_kiara.h" |
}; |
/****************************************************************************/ |
#define SendControlMsg(request, value, buflen) \ |
usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), \ |
request, \ |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, \ |
value, \ |
pdev->vcinterface, \ |
&buf, buflen, HZ / 2) |
#define RecvControlMsg(request, value, buflen) \ |
usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), \ |
request, \ |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, \ |
value, \ |
pdev->vcinterface, \ |
&buf, buflen, HZ / 2) |
#if PWC_DEBUG |
void pwc_hexdump(void *p, int len) |
{ |
int i; |
unsigned char *s; |
char buf[100], *d; |
s = (unsigned char *)p; |
d = buf; |
*d = '\0'; |
Debug("Doing hexdump @ %p, %d bytes.\n", p, len); |
for (i = 0; i < len; i++) { |
d += sprintf26(d, "%02X ", *s++); |
if ((i & 0xF) == 0xF) { |
Debug("%s\n", buf); |
d = buf; |
*d = '\0'; |
} |
} |
if ((i & 0xF) != 0) |
Debug("%s\n", buf); |
} |
#endif |
static inline int send_video_command(struct usb_device *udev, int index, void *buf, int buflen) |
{ |
return usb_control_msg(udev, |
usb_sndctrlpipe(udev, 0), |
SET_EP_STREAM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
VIDEO_OUTPUT_CONTROL_FORMATTER, |
index, |
buf, buflen, HZ); |
} |
static inline int set_video_mode_Nala(struct pwc_device *pdev, int size, int frames) |
{ |
unsigned char buf[3]; |
int ret, fps; |
struct Nala_table_entry *pEntry; |
int frames2frames[31] = |
{ /* closest match of framerate */ |
0, 0, 0, 0, 4, /* 0-4 */ |
5, 5, 7, 7, 10, /* 5-9 */ |
10, 10, 12, 12, 15, /* 10-14 */ |
15, 15, 15, 20, 20, /* 15-19 */ |
20, 20, 20, 24, 24, /* 20-24 */ |
24, 24, 24, 24, 24, /* 25-29 */ |
24 /* 30 */ |
}; |
int frames2table[31] = |
{ 0, 0, 0, 0, 0, /* 0-4 */ |
1, 1, 1, 2, 2, /* 5-9 */ |
3, 3, 4, 4, 4, /* 10-14 */ |
5, 5, 5, 5, 5, /* 15-19 */ |
6, 6, 6, 6, 7, /* 20-24 */ |
7, 7, 7, 7, 7, /* 25-29 */ |
7 /* 30 */ |
}; |
if (size < 0 || size > PSZ_CIF || frames < 4 || frames > 25) |
return -EINVAL; |
frames = frames2frames[frames]; |
fps = frames2table[frames]; |
pEntry = &Nala_table[size][fps]; |
if (pEntry->alternate == 0) |
return -EINVAL; |
if (pEntry->compressed && pdev->decompressor == NULL) |
return -ENOENT; /* Not supported. */ |
memcpy(buf, pEntry->mode, 3); |
ret = send_video_command(pdev->udev, pdev->vendpoint, buf, 3); |
if (ret < 0) { |
Debug("Failed to send video command... %d\n", ret); |
return ret; |
} |
if (pEntry->compressed && pdev->decompressor != 0 && pdev->vpalette != VIDEO_PALETTE_RAW) |
pdev->decompressor->init(pdev->type, pdev->release, buf, pdev->decompress_data); |
pdev->cmd_len = 3; |
memcpy(pdev->cmd_buf, buf, 3); |
/* Set various parameters */ |
pdev->vframes = frames; |
pdev->vsize = size; |
pdev->valternate = pEntry->alternate; |
pdev->image = pwc_image_sizes[size]; |
pdev->frame_size = (pdev->image.x * pdev->image.y * 3) / 2; |
if (pEntry->compressed) { |
if (pdev->release < 5) { /* 4 fold compression */ |
pdev->vbandlength = 528; |
pdev->frame_size /= 4; |
} |
else { |
pdev->vbandlength = 704; |
pdev->frame_size /= 3; |
} |
} |
else |
pdev->vbandlength = 0; |
return 0; |
} |
static inline int set_video_mode_Timon(struct pwc_device *pdev, int size, int frames, int compression, int snapshot) |
{ |
unsigned char buf[13]; |
struct Timon_table_entry *pChoose; |
int ret, fps; |
if (size >= PSZ_MAX || frames < 5 || frames > 30 || compression < 0 || compression > 3) |
return -EINVAL; |
if (size == PSZ_VGA && frames > 15) |
return -EINVAL; |
fps = (frames / 5) - 1; |
/* Find a supported framerate with progressively higher compression ratios |
if the preferred ratio is not available. |
*/ |
pChoose = NULL; |
if (pdev->decompressor == NULL) { |
#if PWC_DEBUG |
Debug("Trying to find uncompressed mode.\n"); |
#endif |
pChoose = &Timon_table[size][fps][0]; |
} |
else { |
while (compression <= 3) { |
pChoose = &Timon_table[size][fps][compression]; |
if (pChoose->alternate != 0) |
break; |
compression++; |
} |
} |
if (pChoose == NULL || pChoose->alternate == 0) |
return -ENOENT; /* Not supported. */ |
memcpy(buf, pChoose->mode, 13); |
if (snapshot) |
buf[0] |= 0x80; |
ret = send_video_command(pdev->udev, pdev->vendpoint, buf, 13); |
if (ret < 0) |
return ret; |
if (pChoose->bandlength > 0 && pdev->decompressor != 0 && pdev->vpalette != VIDEO_PALETTE_RAW) |
pdev->decompressor->init(pdev->type, pdev->release, buf, pdev->decompress_data); |
pdev->cmd_len = 13; |
memcpy(pdev->cmd_buf, buf, 13); |
/* Set various parameters */ |
pdev->vframes = frames; |
pdev->vsize = size; |
pdev->vsnapshot = snapshot; |
pdev->valternate = pChoose->alternate; |
pdev->image = pwc_image_sizes[size]; |
pdev->vbandlength = pChoose->bandlength; |
if (pChoose->bandlength > 0) |
pdev->frame_size = (pChoose->bandlength * pdev->image.y) / 4; |
else |
pdev->frame_size = (pdev->image.x * pdev->image.y * 12) / 8; |
return 0; |
} |
static inline int set_video_mode_Kiara(struct pwc_device *pdev, int size, int frames, int compression, int snapshot) |
{ |
struct Kiara_table_entry *pChoose = 0; |
int fps, ret; |
unsigned char buf[12]; |
struct Kiara_table_entry RawEntry = {6, 773, 1272, {0xAD, 0xF4, 0x10, 0x27, 0xB6, 0x24, 0x96, 0x02, 0x30, 0x05, 0x03, 0x80}}; |
if (size >= PSZ_MAX || frames < 5 || frames > 30 || compression < 0 || compression > 3) |
return -EINVAL; |
if (size == PSZ_VGA && frames > 15) |
return -EINVAL; |
fps = (frames / 5) - 1; |
/* special case: VGA @ 5 fps and snapshot is raw bayer mode */ |
if (size == PSZ_VGA && frames == 5 && snapshot) |
{ |
Info("Choosing VGA/5 BAYER mode (%d).\n", pdev->vpalette); |
pChoose = &RawEntry; |
} |
else |
{ |
/* Find a supported framerate with progressively higher compression ratios |
if the preferred ratio is not available. |
Skip this step when using RAW modes. |
*/ |
if (pdev->decompressor == NULL && pdev->vpalette != VIDEO_PALETTE_RAW) { |
#if PWC_DEBUG |
Debug("Trying to find uncompressed mode.\n"); |
#endif |
pChoose = &Kiara_table[size][fps][0]; |
} |
else { |
while (compression <= 3) { |
pChoose = &Kiara_table[size][fps][compression]; |
if (pChoose->alternate != 0) |
break; |
compression++; |
} |
} |
} |
if (pChoose == NULL || pChoose->alternate == 0) |
return -ENOENT; /* Not supported. */ |
/* usb_control_msg won't take staticly allocated arrays as argument?? */ |
memcpy(buf, pChoose->mode, 12); |
if (snapshot) |
buf[0] |= 0x80; |
/* Firmware bug: video endpoint is 5, but commands are sent to endpoint 4 */ |
ret = send_video_command(pdev->udev, 4 /* pdev->vendpoint */, buf, 12); |
if (ret < 0) |
return ret; |
if (pChoose->bandlength > 0 && pdev->decompressor != 0 && pdev->vpalette != VIDEO_PALETTE_RAW) |
pdev->decompressor->init(pdev->type, pdev->release, buf, pdev->decompress_data); |
pdev->cmd_len = 12; |
memcpy(pdev->cmd_buf, buf, 12); |
/* All set and go */ |
pdev->vframes = frames; |
pdev->vsize = size; |
pdev->vsnapshot = snapshot; |
pdev->valternate = pChoose->alternate; |
pdev->image = pwc_image_sizes[size]; |
pdev->vbandlength = pChoose->bandlength; |
if (pdev->vbandlength > 0) |
pdev->frame_size = (pdev->vbandlength * pdev->image.y) / 4; |
else |
pdev->frame_size = (pdev->image.x * pdev->image.y * 12) / 8; |
return 0; |
} |
/** |
@pdev: device structure |
@width: viewport width |
@height: viewport height |
@frame: framerate, in fps |
@compression: preferred compression ratio |
@snapshot: snapshot mode or streaming |
*/ |
int pwc_set_video_mode(struct pwc_device *pdev, int width, int height, int frames, int compression, int snapshot) |
{ |
int ret, size; |
Trace(TRACE_FLOW, "set_video_mode(%dx%d @ %d, palette %d).\n", width, height, frames, pdev->vpalette); |
size = pwc_decode_size(pdev, width, height); |
if (size < 0) { |
Debug("Could not find suitable size.\n"); |
return -ERANGE; |
} |
Debug("decode_size = %d.\n", size); |
ret = -EINVAL; |
switch(pdev->type) { |
case 645: |
case 646: |
ret = set_video_mode_Nala(pdev, size, frames); |
break; |
case 675: |
case 680: |
case 690: |
ret = set_video_mode_Timon(pdev, size, frames, compression, snapshot); |
break; |
case 720: |
case 730: |
case 740: |
case 750: |
ret = set_video_mode_Kiara(pdev, size, frames, compression, snapshot); |
break; |
} |
if (ret < 0) { |
if (ret == -ENOENT) |
Info("Video mode %s@%d fps is only supported with the decompressor module (pwcx).\n", size2name[size], frames); |
else { |
Err("Failed to set video mode %s@%d fps; return code = %d\n", size2name[size], frames, ret); |
} |
return ret; |
} |
pdev->view.x = width; |
pdev->view.y = height; |
pdev->frame_total_size = pdev->frame_size + pdev->frame_header_size + pdev->frame_trailer_size; |
pwc_set_image_buffer_size(pdev); |
Trace(TRACE_SIZE, "Set viewport to %dx%d, image size is %dx%d.\n", width, height, pwc_image_sizes[size].x, pwc_image_sizes[size].y); |
return 0; |
} |
void pwc_set_image_buffer_size(struct pwc_device *pdev) |
{ |
int i, factor = 0, filler = 0; |
/* for PALETTE_YUV420P */ |
switch(pdev->vpalette) |
{ |
case VIDEO_PALETTE_YUV420P: |
factor = 6; |
filler = 128; |
break; |
case VIDEO_PALETTE_RAW: |
factor = 6; /* can be uncompressed YUV420P */ |
filler = 0; |
break; |
} |
/* Set sizes in bytes */ |
pdev->image.size = pdev->image.x * pdev->image.y * factor / 4; |
pdev->view.size = pdev->view.x * pdev->view.y * factor / 4; |
/* Align offset, or you'll get some very weird results in |
YUV420 mode... x must be multiple of 4 (to get the Y's in |
place), and y even (or you'll mixup U & V). This is less of a |
problem for YUV420P. |
*/ |
pdev->offset.x = ((pdev->view.x - pdev->image.x) / 2) & 0xFFFC; |
pdev->offset.y = ((pdev->view.y - pdev->image.y) / 2) & 0xFFFE; |
/* Fill buffers with gray or black */ |
for (i = 0; i < MAX_IMAGES; i++) { |
if (pdev->image_ptr[i] != NULL) |
memset(pdev->image_ptr[i], filler, pdev->view.size); |
} |
} |
/* BRIGHTNESS */ |
int pwc_get_brightness(struct pwc_device *pdev) |
{ |
char buf; |
int ret; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_LUM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
BRIGHTNESS_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return buf << 9; |
} |
int pwc_set_brightness(struct pwc_device *pdev, int value) |
{ |
char buf; |
if (value < 0) |
value = 0; |
if (value > 0xffff) |
value = 0xffff; |
buf = (value >> 9) & 0x7f; |
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_LUM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
BRIGHTNESS_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
} |
/* CONTRAST */ |
int pwc_get_contrast(struct pwc_device *pdev) |
{ |
char buf; |
int ret; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_LUM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
CONTRAST_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return buf << 10; |
} |
int pwc_set_contrast(struct pwc_device *pdev, int value) |
{ |
char buf; |
if (value < 0) |
value = 0; |
if (value > 0xffff) |
value = 0xffff; |
buf = (value >> 10) & 0x3f; |
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_LUM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
CONTRAST_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
} |
/* GAMMA */ |
int pwc_get_gamma(struct pwc_device *pdev) |
{ |
char buf; |
int ret; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_LUM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
GAMMA_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return buf << 11; |
} |
int pwc_set_gamma(struct pwc_device *pdev, int value) |
{ |
char buf; |
if (value < 0) |
value = 0; |
if (value > 0xffff) |
value = 0xffff; |
buf = (value >> 11) & 0x1f; |
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_LUM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
GAMMA_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
} |
/* SATURATION */ |
int pwc_get_saturation(struct pwc_device *pdev) |
{ |
char buf; |
int ret; |
if (pdev->type < 675) |
return -1; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_CHROM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
pdev->type < 730 ? SATURATION_MODE_FORMATTER2 : SATURATION_MODE_FORMATTER1, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return 32768 + buf * 327; |
} |
int pwc_set_saturation(struct pwc_device *pdev, int value) |
{ |
char buf; |
if (pdev->type < 675) |
return -EINVAL; |
if (value < 0) |
value = 0; |
if (value > 0xffff) |
value = 0xffff; |
/* saturation ranges from -100 to +100 */ |
buf = (value - 32768) / 327; |
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_CHROM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
pdev->type < 730 ? SATURATION_MODE_FORMATTER2 : SATURATION_MODE_FORMATTER1, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
} |
/* AGC */ |
static inline int pwc_set_agc(struct pwc_device *pdev, int mode, int value) |
{ |
char buf; |
int ret; |
if (mode) |
buf = 0x0; /* auto */ |
else |
buf = 0xff; /* fixed */ |
ret = usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_LUM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
AGC_MODE_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (!mode && ret >= 0) { |
if (value < 0) |
value = 0; |
if (value > 0xffff) |
value = 0xffff; |
buf = (value >> 10) & 0x3F; |
ret = usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_LUM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
PRESET_AGC_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
} |
if (ret < 0) |
return ret; |
return 0; |
} |
static inline int pwc_get_agc(struct pwc_device *pdev, int *value) |
{ |
unsigned char buf; |
int ret; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_LUM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
AGC_MODE_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
if (buf != 0) { /* fixed */ |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_LUM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
PRESET_AGC_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
if (buf > 0x3F) |
buf = 0x3F; |
*value = (buf << 10); |
} |
else { /* auto */ |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_STATUS_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
READ_AGC_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
/* Gah... this value ranges from 0x00 ... 0x9F */ |
if (buf > 0x9F) |
buf = 0x9F; |
*value = -(48 + buf * 409); |
} |
return 0; |
} |
static inline int pwc_set_shutter_speed(struct pwc_device *pdev, int mode, int value) |
{ |
char buf[2]; |
int speed, ret; |
if (mode) |
buf[0] = 0x0; /* auto */ |
else |
buf[0] = 0xff; /* fixed */ |
ret = usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_LUM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
SHUTTER_MODE_FORMATTER, |
pdev->vcinterface, |
buf, 1, HZ / 2); |
if (!mode && ret >= 0) { |
if (value < 0) |
value = 0; |
if (value > 0xffff) |
value = 0xffff; |
switch(pdev->type) { |
case 675: |
case 680: |
case 690: |
/* speed ranges from 0x0 to 0x290 (656) */ |
speed = (value / 100); |
buf[1] = speed >> 8; |
buf[0] = speed & 0xff; |
break; |
case 720: |
case 730: |
case 740: |
case 750: |
/* speed seems to range from 0x0 to 0xff */ |
buf[1] = 0; |
buf[0] = value >> 8; |
break; |
} |
ret = usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_LUM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
PRESET_SHUTTER_FORMATTER, |
pdev->vcinterface, |
&buf, 2, HZ / 2); |
} |
return ret; |
} |
/* POWER */ |
int pwc_camera_power(struct pwc_device *pdev, int power) |
{ |
char buf; |
if (pdev->type < 675 || (pdev->type < 730 && pdev->release < 6)) |
return 0; /* Not supported by Nala or Timon < release 6 */ |
if (power) |
buf = 0x00; /* active */ |
else |
buf = 0xFF; /* power save */ |
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_STATUS_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
SET_POWER_SAVE_MODE_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
} |
/* private calls */ |
static inline int pwc_restore_user(struct pwc_device *pdev) |
{ |
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_STATUS_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
RESTORE_USER_DEFAULTS_FORMATTER, |
pdev->vcinterface, |
NULL, 0, HZ / 2); |
} |
static inline int pwc_save_user(struct pwc_device *pdev) |
{ |
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_STATUS_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
SAVE_USER_DEFAULTS_FORMATTER, |
pdev->vcinterface, |
NULL, 0, HZ / 2); |
} |
static inline int pwc_restore_factory(struct pwc_device *pdev) |
{ |
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_STATUS_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
RESTORE_FACTORY_DEFAULTS_FORMATTER, |
pdev->vcinterface, |
NULL, 0, HZ / 2); |
} |
/* ************************************************* */ |
/* Patch by Alvarado: (not in the original version */ |
/* |
* the camera recognizes modes from 0 to 4: |
* |
* 00: indoor (incandescant lighting) |
* 01: outdoor (sunlight) |
* 02: fluorescent lighting |
* 03: manual |
* 04: auto |
*/ |
static inline int pwc_set_awb(struct pwc_device *pdev, int mode) |
{ |
char buf; |
int ret; |
if (mode < 0) |
mode = 0; |
if (mode > 4) |
mode = 4; |
buf = mode & 0x07; /* just the lowest three bits */ |
ret = usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_CHROM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
WB_MODE_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return 0; |
} |
static inline int pwc_get_awb(struct pwc_device *pdev) |
{ |
unsigned char buf; |
int ret; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_CHROM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
WB_MODE_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return buf; |
} |
static inline int pwc_set_red_gain(struct pwc_device *pdev, int value) |
{ |
unsigned char buf; |
if (value < 0) |
value = 0; |
if (value > 0xffff) |
value = 0xffff; |
/* only the msb are considered */ |
buf = value >> 8; |
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_CHROM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
PRESET_MANUAL_RED_GAIN_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
} |
static inline int pwc_get_red_gain(struct pwc_device *pdev) |
{ |
unsigned char buf; |
int ret; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_CHROM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
PRESET_MANUAL_RED_GAIN_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return (buf << 8); |
} |
static inline int pwc_set_blue_gain(struct pwc_device *pdev, int value) |
{ |
unsigned char buf; |
if (value < 0) |
value = 0; |
if (value > 0xffff) |
value = 0xffff; |
/* linear mapping of 0..0xffff to -0x80..0x7f */ |
buf = (value >> 8); |
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_CHROM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
PRESET_MANUAL_BLUE_GAIN_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
} |
static inline int pwc_get_blue_gain(struct pwc_device *pdev) |
{ |
unsigned char buf; |
int ret; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_CHROM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
PRESET_MANUAL_BLUE_GAIN_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return (buf << 8); |
} |
/* The following two functions are different, since they only read the |
internal red/blue gains, which may be different from the manual |
gains set or read above. |
*/ |
static inline int pwc_read_red_gain(struct pwc_device *pdev) |
{ |
unsigned char buf; |
int ret; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_STATUS_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
READ_RED_GAIN_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return (buf << 8); |
} |
static inline int pwc_read_blue_gain(struct pwc_device *pdev) |
{ |
unsigned char buf; |
int ret; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_STATUS_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
READ_BLUE_GAIN_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return (buf << 8); |
} |
static inline int pwc_set_wb_speed(struct pwc_device *pdev, int speed) |
{ |
unsigned char buf; |
/* useful range is 0x01..0x20 */ |
buf = speed / 0x7f0; |
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_CHROM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
AWB_CONTROL_SPEED_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
} |
static inline int pwc_get_wb_speed(struct pwc_device *pdev) |
{ |
unsigned char buf; |
int ret; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_CHROM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
AWB_CONTROL_SPEED_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return (buf * 0x7f0); |
} |
static inline int pwc_set_wb_delay(struct pwc_device *pdev, int delay) |
{ |
unsigned char buf; |
/* useful range is 0x01..0x3F */ |
buf = (delay >> 10); |
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_CHROM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
AWB_CONTROL_DELAY_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
} |
static inline int pwc_get_wb_delay(struct pwc_device *pdev) |
{ |
unsigned char buf; |
int ret; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_CHROM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
AWB_CONTROL_DELAY_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return (buf << 10); |
} |
int pwc_set_leds(struct pwc_device *pdev, int on_value, int off_value) |
{ |
unsigned char buf[2]; |
if (pdev->type < 730) |
return 0; |
on_value /= 100; |
off_value /= 100; |
if (on_value < 0) |
on_value = 0; |
if (on_value > 0xff) |
on_value = 0xff; |
if (off_value < 0) |
off_value = 0; |
if (off_value > 0xff) |
off_value = 0xff; |
buf[0] = on_value; |
buf[1] = off_value; |
return SendControlMsg(SET_STATUS_CTL, LED_FORMATTER, 2); |
} |
int pwc_get_leds(struct pwc_device *pdev, int *on_value, int *off_value) |
{ |
unsigned char buf[2]; |
int ret; |
if (pdev->type < 730) { |
*on_value = -1; |
*off_value = -1; |
return 0; |
} |
ret = RecvControlMsg(GET_STATUS_CTL, LED_FORMATTER, 2); |
if (ret < 0) |
return ret; |
*on_value = buf[0] * 100; |
*off_value = buf[1] * 100; |
return 0; |
} |
static inline int pwc_set_contour(struct pwc_device *pdev, int contour) |
{ |
unsigned char buf; |
int ret; |
if (contour < 0) |
buf = 0xff; /* auto contour on */ |
else |
buf = 0x0; /* auto contour off */ |
ret = usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_LUM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
AUTO_CONTOUR_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
if (contour < 0) |
return 0; |
if (contour > 0xffff) |
contour = 0xffff; |
buf = (contour >> 10); /* contour preset is [0..3f] */ |
ret = usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_LUM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
PRESET_CONTOUR_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return 0; |
} |
static inline int pwc_get_contour(struct pwc_device *pdev, int *contour) |
{ |
unsigned char buf; |
int ret; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_LUM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
AUTO_CONTOUR_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
if (buf == 0) { |
/* auto mode off, query current preset value */ |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_LUM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
PRESET_CONTOUR_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
*contour = (buf << 10); |
} |
else |
*contour = -1; |
return 0; |
} |
static inline int pwc_set_backlight(struct pwc_device *pdev, int backlight) |
{ |
unsigned char buf; |
if (backlight) |
buf = 0xff; |
else |
buf = 0x0; |
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), |
SET_LUM_CTL, |
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
BACK_LIGHT_COMPENSATION_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
} |
static inline int pwc_get_backlight(struct pwc_device *pdev) |
{ |
int ret; |
unsigned char buf; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_LUM_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
BACK_LIGHT_COMPENSATION_FORMATTER, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
return buf; |
} |
static inline int pwc_set_flicker(struct pwc_device *pdev, int flicker) |
{ |
unsigned char buf; |
if (flicker) |
buf = 0xff; |
else |
buf = 0x0; |
return SendControlMsg(SET_LUM_CTL, FLICKERLESS_MODE_FORMATTER, 1); |
} |
static inline int pwc_get_flicker(struct pwc_device *pdev) |
{ |
int ret; |
unsigned char buf; |
ret = RecvControlMsg(GET_LUM_CTL, FLICKERLESS_MODE_FORMATTER, 1); |
if (ret < 0) |
return ret; |
return buf; |
} |
static inline int pwc_set_dynamic_noise(struct pwc_device *pdev, int noise) |
{ |
unsigned char buf; |
if (noise < 0) |
noise = 0; |
if (noise > 3) |
noise = 3; |
buf = noise; |
return SendControlMsg(SET_LUM_CTL, DYNAMIC_NOISE_CONTROL_FORMATTER, 1); |
} |
static inline int pwc_get_dynamic_noise(struct pwc_device *pdev) |
{ |
int ret; |
unsigned char buf; |
ret = RecvControlMsg(GET_LUM_CTL, DYNAMIC_NOISE_CONTROL_FORMATTER, 1); |
if (ret < 0) |
return ret; |
return buf; |
} |
int pwc_mpt_reset(struct pwc_device *pdev, int flags) |
{ |
unsigned char buf; |
buf = flags & 0x03; // only lower two bits are currently used |
return SendControlMsg(SET_MPT_CTL, PT_RESET_CONTROL_FORMATTER, 1); |
} |
static inline int pwc_mpt_set_angle(struct pwc_device *pdev, int pan, int tilt) |
{ |
unsigned char buf[4]; |
/* set new relative angle; angles are expressed in degrees * 100, |
but cam as .5 degree resolution, hence devide by 200. Also |
the angle must be multiplied by 64 before it's send to |
the cam (??) |
*/ |
pan = 64 * pan / 100; |
tilt = -64 * tilt / 100; /* positive tilt is down, which is not what the user would expect */ |
buf[0] = pan & 0xFF; |
buf[1] = (pan >> 8) & 0xFF; |
buf[2] = tilt & 0xFF; |
buf[3] = (tilt >> 8) & 0xFF; |
return SendControlMsg(SET_MPT_CTL, PT_RELATIVE_CONTROL_FORMATTER, 4); |
} |
static inline int pwc_mpt_get_status(struct pwc_device *pdev, struct pwc_mpt_status *status) |
{ |
int ret; |
unsigned char buf[5]; |
ret = RecvControlMsg(GET_MPT_CTL, PT_STATUS_FORMATTER, 5); |
if (ret < 0) |
return ret; |
status->status = buf[0] & 0x7; // 3 bits are used for reporting |
status->time_pan = (buf[1] << 8) + buf[2]; |
status->time_tilt = (buf[3] << 8) + buf[4]; |
return 0; |
} |
int pwc_get_cmos_sensor(struct pwc_device *pdev) |
{ |
unsigned char buf; |
int ret = -1, request; |
if (pdev->type < 675) |
request = SENSOR_TYPE_FORMATTER1; |
else if (pdev->type < 730) |
return -1; /* The Vesta series doesn't have this call */ |
else |
request = SENSOR_TYPE_FORMATTER2; |
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), |
GET_STATUS_CTL, |
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
request, |
pdev->vcinterface, |
&buf, 1, HZ / 2); |
if (ret < 0) |
return ret; |
if (pdev->type < 675) |
return buf | 0x100; |
else |
return buf; |
} |
/* End of Add-Ons */ |
/* ************************************************* */ |
int pwc_ioctl(struct pwc_device *pdev, unsigned int cmd, void *arg) |
{ |
int ret = 0; |
switch(cmd) { |
case VIDIOCPWCRUSER: |
{ |
if (pwc_restore_user(pdev)) |
ret = -EINVAL; |
break; |
} |
case VIDIOCPWCSUSER: |
{ |
if (pwc_save_user(pdev)) |
ret = -EINVAL; |
break; |
} |
case VIDIOCPWCFACTORY: |
{ |
if (pwc_restore_factory(pdev)) |
ret = -EINVAL; |
break; |
} |
case VIDIOCPWCSCQUAL: |
{ |
int *qual = arg; |
if (*qual < 0 || *qual > 3) |
ret = -EINVAL; |
else |
ret = pwc_try_video_mode(pdev, pdev->view.x, pdev->view.y, pdev->vframes, *qual, pdev->vsnapshot); |
if (ret >= 0) |
pdev->vcompression = *qual; |
break; |
} |
case VIDIOCPWCGCQUAL: |
{ |
int *qual = arg; |
*qual = pdev->vcompression; |
break; |
} |
case VIDIOCPWCPROBE: |
{ |
struct pwc_probe *probe = arg; |
strcpy(probe->name, pdev->vdev.name); |
probe->type = pdev->type; |
break; |
} |
case VIDIOCPWCSAGC: |
{ |
int *agc = arg; |
if (pwc_set_agc(pdev, *agc < 0 ? 1 : 0, *agc)) |
ret = -EINVAL; |
break; |
} |
case VIDIOCPWCGAGC: |
{ |
int *agc = arg; |
if (pwc_get_agc(pdev, agc)) |
ret = -EINVAL; |
break; |
} |
case VIDIOCPWCSSHUTTER: |
{ |
int *shutter_speed = arg; |
ret = pwc_set_shutter_speed(pdev, *shutter_speed < 0 ? 1 : 0, *shutter_speed); |
break; |
} |
case VIDIOCPWCSAWB: |
{ |
struct pwc_whitebalance *wb = arg; |
ret = pwc_set_awb(pdev, wb->mode); |
if (ret >= 0 && wb->mode == PWC_WB_MANUAL) { |
pwc_set_red_gain(pdev, wb->manual_red); |
pwc_set_blue_gain(pdev, wb->manual_blue); |
} |
break; |
} |
case VIDIOCPWCGAWB: |
{ |
struct pwc_whitebalance *wb = arg; |
memset(wb, 0, sizeof(*wb)); |
wb->mode = pwc_get_awb(pdev); |
if (wb->mode < 0) |
ret = -EINVAL; |
else { |
if (wb->mode == PWC_WB_MANUAL) { |
wb->manual_red = pwc_get_red_gain(pdev); |
wb->manual_blue = pwc_get_blue_gain(pdev); |
} |
if (wb->mode == PWC_WB_AUTO) { |
wb->read_red = pwc_read_red_gain(pdev); |
wb->read_blue = pwc_read_blue_gain(pdev); |
} |
} |
break; |
} |
case VIDIOCPWCSAWBSPEED: |
{ |
struct pwc_wb_speed *wbs = arg; |
if (wbs->control_speed > 0) { |
ret = pwc_set_wb_speed(pdev, wbs->control_speed); |
} |
if (wbs->control_delay > 0) { |
ret = pwc_set_wb_delay(pdev, wbs->control_delay); |
} |
break; |
} |
case VIDIOCPWCGAWBSPEED: |
{ |
struct pwc_wb_speed *wbs = arg; |
ret = pwc_get_wb_speed(pdev); |
if (ret < 0) |
break; |
wbs->control_speed = ret; |
ret = pwc_get_wb_delay(pdev); |
if (ret < 0) |
break; |
wbs->control_delay = ret; |
break; |
} |
case VIDIOCPWCSLED: |
{ |
struct pwc_leds *leds = arg; |
ret = pwc_set_leds(pdev, leds->led_on, leds->led_off); |
break; |
} |
case VIDIOCPWCGLED: |
{ |
struct pwc_leds *leds = arg; |
ret = pwc_get_leds(pdev, &leds->led_on, &leds->led_off); |
break; |
} |
case VIDIOCPWCSCONTOUR: |
{ |
int *contour = arg; |
ret = pwc_set_contour(pdev, *contour); |
break; |
} |
case VIDIOCPWCGCONTOUR: |
{ |
int *contour = arg; |
ret = pwc_get_contour(pdev, contour); |
break; |
} |
case VIDIOCPWCSBACKLIGHT: |
{ |
int *backlight = arg; |
ret = pwc_set_backlight(pdev, *backlight); |
break; |
} |
case VIDIOCPWCGBACKLIGHT: |
{ |
int *backlight = arg; |
ret = pwc_get_backlight(pdev); |
if (ret >= 0) |
*backlight = ret; |
break; |
} |
case VIDIOCPWCSFLICKER: |
{ |
int *flicker = arg; |
ret = pwc_set_flicker(pdev, *flicker); |
break; |
} |
case VIDIOCPWCGFLICKER: |
{ |
int *flicker = arg; |
ret = pwc_get_flicker(pdev); |
if (ret >= 0) |
*flicker = ret; |
break; |
} |
case VIDIOCPWCSDYNNOISE: |
{ |
int *dynnoise = arg; |
ret = pwc_set_dynamic_noise(pdev, *dynnoise); |
break; |
} |
case VIDIOCPWCGDYNNOISE: |
{ |
int *dynnoise = arg; |
ret = pwc_get_dynamic_noise(pdev); |
if (ret < 0) |
break; |
*dynnoise = ret; |
break; |
} |
case VIDIOCPWCGREALSIZE: |
{ |
struct pwc_imagesize *size = arg; |
size->width = pdev->image.x; |
size->height = pdev->image.y; |
break; |
} |
case VIDIOCPWCMPTRESET: |
{ |
int *flags = arg; |
if (pdev->features & FEATURE_MOTOR_PANTILT) |
{ |
ret = pwc_mpt_reset(pdev, *flags); |
if (ret >= 0) |
{ |
pdev->pan_angle = 0; |
pdev->tilt_angle = 0; |
} |
} |
else |
{ |
ret = -ENXIO; |
} |
break; |
} |
case VIDIOCPWCMPTGRANGE: |
{ |
if (pdev->features & FEATURE_MOTOR_PANTILT) |
{ |
memcpy(arg, &pdev->angle_range, sizeof(struct pwc_mpt_range)); |
} |
else |
{ |
ret = -ENXIO; |
} |
break; |
} |
case VIDIOCPWCMPTSANGLE: |
{ |
struct pwc_mpt_angles *angles = arg; |
int new_pan, new_tilt; |
if (pdev->features & FEATURE_MOTOR_PANTILT) |
{ |
/* The camera can only set relative angles, so |
do some calculations when getting an absolute angle . |
*/ |
if (angles->absolute) |
{ |
new_pan = angles->pan; |
new_tilt = angles->tilt; |
} |
else |
{ |
new_pan = pdev->pan_angle + angles->pan; |
new_tilt = pdev->tilt_angle + angles->tilt; |
} |
/* check absolute ranges */ |
if (new_pan < pdev->angle_range.pan_min || |
new_pan > pdev->angle_range.pan_max || |
new_tilt < pdev->angle_range.tilt_min || |
new_tilt > pdev->angle_range.tilt_max) |
{ |
ret = -ERANGE; |
} |
else |
{ |
/* go to relative range, check again */ |
new_pan -= pdev->pan_angle; |
new_tilt -= pdev->tilt_angle; |
/* angles are specified in degrees * 100, thus the limit = 36000 */ |
if (new_pan < -36000 || new_pan > 36000 || new_tilt < -36000 || new_tilt > 36000) |
ret = -ERANGE; |
} |
if (ret == 0) /* no errors so far */ |
{ |
ret = pwc_mpt_set_angle(pdev, new_pan, new_tilt); |
if (ret >= 0) |
{ |
pdev->pan_angle += new_pan; |
pdev->tilt_angle += new_tilt; |
} |
if (ret == -EPIPE) /* stall -> out of range */ |
ret = -ERANGE; |
} |
} |
else |
{ |
ret = -ENXIO; |
} |
break; |
} |
case VIDIOCPWCMPTGANGLE: |
{ |
struct pwc_mpt_angles *angles = arg; |
if (pdev->features & FEATURE_MOTOR_PANTILT) |
{ |
angles->absolute = 1; |
angles->pan = pdev->pan_angle; |
angles->tilt = pdev->tilt_angle; |
} |
else |
{ |
ret = -ENXIO; |
} |
break; |
} |
case VIDIOCPWCMPTSTATUS: |
{ |
struct pwc_mpt_status *status = arg; |
if (pdev->features & FEATURE_MOTOR_PANTILT) |
{ |
ret = pwc_mpt_get_status(pdev, status); |
} |
else |
{ |
ret = -ENXIO; |
} |
break; |
} |
case VIDIOCPWCGVIDCMD: |
{ |
struct pwc_video_command cmd; |
cmd.type = pdev->type; |
cmd.release = pdev->release; |
cmd.command_len = pdev->cmd_len; |
memcpy(cmd.command_buf, pdev->cmd_buf, pdev->cmd_len); |
cmd.bandlength = pdev->vbandlength; |
cmd.frame_size = pdev->frame_size; |
if (copy_to_user(arg, &cmd, sizeof(cmd))) |
ret = -EFAULT; |
break; |
} |
default: |
ret = -ENOIOCTLCMD; |
break; |
} |
if (ret > 0) |
return 0; |
return ret; |
} |
/shark/trunk/drivers/usb/serial/console.c |
---|
0,0 → 1,265 |
/* |
* USB Serial Console driver |
* |
* Copyright (C) 2001 - 2002 Greg Kroah-Hartman (greg@kroah.com) |
* |
* This program is free software; you can redistribute it and/or |
* modify it under the terms of the GNU General Public License version |
* 2 as published by the Free Software Foundation. |
* |
* Thanks to Randy Dunlap for the original version of this code. |
* |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/kernel.h> |
#include <linux/init.h> |
#include <linux/slab.h> |
#include <linux/tty.h> |
#include <linux/console.h> |
#include <linux/usb.h> |
static int debug; |
#include "usb-serial.h" |
struct usbcons_info { |
int magic; |
int break_flag; |
struct usb_serial_port *port; |
}; |
static struct usbcons_info usbcons_info; |
static struct console usbcons; |
/* |
* ------------------------------------------------------------ |
* USB Serial console driver |
* |
* Much of the code here is copied from drivers/char/serial.c |
* and implements a phony serial console in the same way that |
* serial.c does so that in case some software queries it, |
* it will get the same results. |
* |
* Things that are different from the way the serial port code |
* does things, is that we call the lower level usb-serial |
* driver code to initialize the device, and we set the initial |
* console speeds based on the command line arguments. |
* ------------------------------------------------------------ |
*/ |
/* |
* The parsing of the command line works exactly like the |
* serial.c code, except that the specifier is "ttyUSB" instead |
* of "ttyS". |
*/ |
static int __init usb_console_setup(struct console *co, char *options) |
{ |
struct usbcons_info *info = &usbcons_info; |
int baud = 9600; |
int bits = 8; |
int parity = 'n'; |
int doflow = 0; |
int cflag = CREAD | HUPCL | CLOCAL; |
char *s; |
struct usb_serial *serial; |
struct usb_serial_port *port; |
int retval = 0; |
struct tty_struct *tty; |
struct termios *termios; |
dbg ("%s", __FUNCTION__); |
if (options) { |
baud = simple_strtoul(options, NULL, 10); |
s = options; |
while (*s >= '0' && *s <= '9') |
s++; |
if (*s) |
parity = *s++; |
if (*s) |
bits = *s++ - '0'; |
if (*s) |
doflow = (*s++ == 'r'); |
} |
/* build a cflag setting */ |
switch (baud) { |
case 1200: |
cflag |= B1200; |
break; |
case 2400: |
cflag |= B2400; |
break; |
case 4800: |
cflag |= B4800; |
break; |
case 19200: |
cflag |= B19200; |
break; |
case 38400: |
cflag |= B38400; |
break; |
case 57600: |
cflag |= B57600; |
break; |
case 115200: |
cflag |= B115200; |
break; |
case 9600: |
default: |
cflag |= B9600; |
/* |
* Set this to a sane value to prevent a divide error |
*/ |
baud = 9600; |
break; |
} |
switch (bits) { |
case 7: |
cflag |= CS7; |
break; |
default: |
case 8: |
cflag |= CS8; |
break; |
} |
switch (parity) { |
case 'o': case 'O': |
cflag |= PARODD; |
break; |
case 'e': case 'E': |
cflag |= PARENB; |
break; |
} |
co->cflag = cflag; |
/* grab the first serial port that happens to be connected */ |
serial = usb_serial_get_by_index(0); |
if (serial_paranoia_check (serial, __FUNCTION__)) { |
/* no device is connected yet, sorry :( */ |
err ("No USB device connected to ttyUSB0"); |
return -ENODEV; |
} |
port = serial->port[0]; |
port->tty = NULL; |
info->port = port; |
++port->open_count; |
if (port->open_count == 1) { |
/* only call the device specific open if this |
* is the first time the port is opened */ |
if (serial->type->open) |
retval = serial->type->open(port, NULL); |
else |
retval = usb_serial_generic_open(port, NULL); |
if (retval) |
port->open_count = 0; |
} |
if (retval) { |
err ("could not open USB console port"); |
return retval; |
} |
if (serial->type->set_termios) { |
/* build up a fake tty structure so that the open call has something |
* to look at to get the cflag value */ |
tty = kmalloc (sizeof (*tty), GFP_KERNEL); |
if (!tty) { |
err ("no more memory"); |
return -ENOMEM; |
} |
termios = kmalloc (sizeof (*termios), GFP_KERNEL); |
if (!termios) { |
err ("no more memory"); |
kfree (tty); |
return -ENOMEM; |
} |
memset (tty, 0x00, sizeof(*tty)); |
memset (termios, 0x00, sizeof(*termios)); |
termios->c_cflag = cflag; |
tty->termios = termios; |
port->tty = tty; |
/* set up the initial termios settings */ |
serial->type->set_termios(port, NULL); |
port->tty = NULL; |
kfree (termios); |
kfree (tty); |
} |
return retval; |
} |
static void usb_console_write(struct console *co, const char *buf, unsigned count) |
{ |
static struct usbcons_info *info = &usbcons_info; |
struct usb_serial_port *port = info->port; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
int retval = -ENODEV; |
if (!serial || !port) |
return; |
if (count == 0) |
return; |
dbg("%s - port %d, %d byte(s)", __FUNCTION__, port->number, count); |
if (!port->open_count) { |
dbg ("%s - port not opened", __FUNCTION__); |
goto exit; |
} |
/* pass on to the driver specific version of this function if it is available */ |
if (serial->type->write) |
retval = serial->type->write(port, 0, buf, count); |
else |
retval = usb_serial_generic_write(port, 0, buf, count); |
exit: |
dbg("%s - return value (if we had one): %d", __FUNCTION__, retval); |
} |
static struct console usbcons = { |
.name = "ttyUSB", |
.write = usb_console_write, |
.setup = usb_console_setup, |
.flags = CON_PRINTBUFFER, |
.index = -1, |
}; |
void usb_serial_console_init (int serial_debug, int minor) |
{ |
debug = serial_debug; |
if (minor == 0) { |
/* |
* Call register_console() if this is the first device plugged |
* in. If we call it earlier, then the callback to |
* console_setup() will fail, as there is not a device seen by |
* the USB subsystem yet. |
*/ |
/* |
* Register console. |
* NOTES: |
* console_setup() is called (back) immediately (from register_console). |
* console_write() is called immediately from register_console iff |
* CON_PRINTBUFFER is set in flags. |
*/ |
dbg ("registering the USB serial console."); |
register_console(&usbcons); |
} |
} |
void usb_serial_console_exit (void) |
{ |
unregister_console(&usbcons); |
} |
/shark/trunk/drivers/usb/serial/pl2303.c |
---|
0,0 → 1,818 |
/* |
* Prolific PL2303 USB to serial adaptor driver |
* |
* Copyright (C) 2001-2003 Greg Kroah-Hartman (greg@kroah.com) |
* Copyright (C) 2003 IBM Corp. |
* |
* Original driver for 2.2.x by anonymous |
* |
* 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. |
* |
* See Documentation/usb/usb-serial.txt for more information on using this driver |
* |
* 2002_Mar_26 gkh |
* allowed driver to work properly if there is no tty assigned to a port |
* (this happens for serial console devices.) |
* |
* 2001_Oct_06 gkh |
* Added RTS and DTR line control. Thanks to joe@bndlg.de for parts of it. |
* |
* 2001_Sep_19 gkh |
* Added break support. |
* |
* 2001_Aug_30 gkh |
* fixed oops in write_bulk_callback. |
* |
* 2001_Aug_28 gkh |
* reworked buffer logic to be like other usb-serial drivers. Hopefully |
* removing some reported problems. |
* |
* 2001_Jun_06 gkh |
* finished porting to 2.4 format. |
* |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/kernel.h> |
#include <linux/errno.h> |
#include <linux/init.h> |
#include <linux/slab.h> |
#include <linux/tty.h> |
#include <linux/tty_driver.h> |
#include <linux/tty_flip.h> |
#include <linux/serial.h> |
#include <linux/module.h> |
#include <linux/spinlock.h> |
#include <asm/uaccess.h> |
#include <linux/usb.h> |
#ifdef CONFIG_USB_SERIAL_DEBUG |
static int debug = 1; |
#else |
static int debug; |
#endif |
#include "usb-serial.h" |
#include "pl2303.h" |
/* |
* Version Information |
*/ |
#define DRIVER_VERSION "v0.10" |
#define DRIVER_DESC "Prolific PL2303 USB to serial adaptor driver" |
static struct usb_device_id id_table [] = { |
{ USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID) }, |
{ USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ2) }, |
{ USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) }, |
{ USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID) }, |
{ USB_DEVICE(ELCOM_VENDOR_ID, ELCOM_PRODUCT_ID) }, |
{ USB_DEVICE(ITEGNO_VENDOR_ID, ITEGNO_PRODUCT_ID) }, |
{ USB_DEVICE(MA620_VENDOR_ID, MA620_PRODUCT_ID) }, |
{ USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID) }, |
{ USB_DEVICE(TRIPP_VENDOR_ID, TRIPP_PRODUCT_ID) }, |
{ USB_DEVICE(RADIOSHACK_VENDOR_ID, RADIOSHACK_PRODUCT_ID) }, |
{ USB_DEVICE(DCU10_VENDOR_ID, DCU10_PRODUCT_ID) }, |
{ } /* Terminating entry */ |
}; |
MODULE_DEVICE_TABLE (usb, id_table); |
static struct usb_driver pl2303_driver = { |
.owner = THIS_MODULE, |
.name = "pl2303", |
.probe = usb_serial_probe, |
.disconnect = usb_serial_disconnect, |
.id_table = id_table, |
}; |
#define SET_LINE_REQUEST_TYPE 0x21 |
#define SET_LINE_REQUEST 0x20 |
#define SET_CONTROL_REQUEST_TYPE 0x21 |
#define SET_CONTROL_REQUEST 0x22 |
#define CONTROL_DTR 0x01 |
#define CONTROL_RTS 0x02 |
#define BREAK_REQUEST_TYPE 0x21 |
#define BREAK_REQUEST 0x23 |
#define BREAK_ON 0xffff |
#define BREAK_OFF 0x0000 |
#define GET_LINE_REQUEST_TYPE 0xa1 |
#define GET_LINE_REQUEST 0x21 |
#define VENDOR_WRITE_REQUEST_TYPE 0x40 |
#define VENDOR_WRITE_REQUEST 0x01 |
#define VENDOR_READ_REQUEST_TYPE 0xc0 |
#define VENDOR_READ_REQUEST 0x01 |
#define UART_STATE 0x08 |
#define UART_DCD 0x01 |
#define UART_DSR 0x02 |
#define UART_BREAK_ERROR 0x04 |
#define UART_RING 0x08 |
#define UART_FRAME_ERROR 0x10 |
#define UART_PARITY_ERROR 0x20 |
#define UART_OVERRUN_ERROR 0x40 |
#define UART_CTS 0x80 |
/* function prototypes for a PL2303 serial converter */ |
static int pl2303_open (struct usb_serial_port *port, struct file *filp); |
static void pl2303_close (struct usb_serial_port *port, struct file *filp); |
static void pl2303_set_termios (struct usb_serial_port *port, |
struct termios *old); |
static int pl2303_ioctl (struct usb_serial_port *port, struct file *file, |
unsigned int cmd, unsigned long arg); |
static void pl2303_read_int_callback (struct urb *urb, struct pt_regs *regs); |
static void pl2303_read_bulk_callback (struct urb *urb, struct pt_regs *regs); |
static void pl2303_write_bulk_callback (struct urb *urb, struct pt_regs *regs); |
static int pl2303_write (struct usb_serial_port *port, int from_user, |
const unsigned char *buf, int count); |
static void pl2303_break_ctl(struct usb_serial_port *port,int break_state); |
static int pl2303_tiocmget (struct usb_serial_port *port, struct file *file); |
static int pl2303_tiocmset (struct usb_serial_port *port, struct file *file, |
unsigned int set, unsigned int clear); |
static int pl2303_startup (struct usb_serial *serial); |
static void pl2303_shutdown (struct usb_serial *serial); |
/* All of the device info needed for the PL2303 SIO serial converter */ |
static struct usb_serial_device_type pl2303_device = { |
.owner = THIS_MODULE, |
.name = "PL-2303", |
.id_table = id_table, |
.num_interrupt_in = NUM_DONT_CARE, |
.num_bulk_in = 1, |
.num_bulk_out = 1, |
.num_ports = 1, |
.open = pl2303_open, |
.close = pl2303_close, |
.write = pl2303_write, |
.ioctl = pl2303_ioctl, |
.break_ctl = pl2303_break_ctl, |
.set_termios = pl2303_set_termios, |
.tiocmget = pl2303_tiocmget, |
.tiocmset = pl2303_tiocmset, |
.read_bulk_callback = pl2303_read_bulk_callback, |
.read_int_callback = pl2303_read_int_callback, |
.write_bulk_callback = pl2303_write_bulk_callback, |
.attach = pl2303_startup, |
.shutdown = pl2303_shutdown, |
}; |
struct pl2303_private { |
spinlock_t lock; |
u8 line_control; |
u8 line_status; |
u8 termios_initialized; |
}; |
static int pl2303_startup (struct usb_serial *serial) |
{ |
struct pl2303_private *priv; |
int i; |
for (i = 0; i < serial->num_ports; ++i) { |
priv = kmalloc (sizeof (struct pl2303_private), GFP_KERNEL); |
if (!priv) |
return -ENOMEM; |
memset (priv, 0x00, sizeof (struct pl2303_private)); |
spin_lock_init(&priv->lock); |
usb_set_serial_port_data(serial->port[i], priv); |
} |
return 0; |
} |
static int set_control_lines (struct usb_device *dev, u8 value) |
{ |
int retval; |
retval = usb_control_msg (dev, usb_sndctrlpipe (dev, 0), |
SET_CONTROL_REQUEST, SET_CONTROL_REQUEST_TYPE, |
value, 0, NULL, 0, 100); |
dbg("%s - value = %d, retval = %d", __FUNCTION__, value, retval); |
return retval; |
} |
static int pl2303_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count) |
{ |
int result; |
dbg("%s - port %d, %d bytes", __FUNCTION__, port->number, count); |
if (port->write_urb->status == -EINPROGRESS) { |
dbg("%s - already writing", __FUNCTION__); |
return 0; |
} |
count = (count > port->bulk_out_size) ? port->bulk_out_size : count; |
if (from_user) { |
if (copy_from_user (port->write_urb->transfer_buffer, buf, count)) |
return -EFAULT; |
} else { |
memcpy (port->write_urb->transfer_buffer, buf, count); |
} |
usb_serial_debug_data (__FILE__, __FUNCTION__, count, port->write_urb->transfer_buffer); |
port->write_urb->transfer_buffer_length = count; |
port->write_urb->dev = port->serial->dev; |
result = usb_submit_urb (port->write_urb, GFP_ATOMIC); |
if (result) |
dev_err(&port->dev, "%s - failed submitting write urb, error %d\n", __FUNCTION__, result); |
else |
result = count; |
return result; |
} |
static void pl2303_set_termios (struct usb_serial_port *port, struct termios *old_termios) |
{ |
struct usb_serial *serial = port->serial; |
struct pl2303_private *priv = usb_get_serial_port_data(port); |
unsigned long flags; |
unsigned int cflag; |
unsigned char *buf; |
int baud; |
int i; |
u8 control; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if ((!port->tty) || (!port->tty->termios)) { |
dbg("%s - no tty structures", __FUNCTION__); |
return; |
} |
spin_lock_irqsave(&priv->lock, flags); |
if (!priv->termios_initialized) { |
printk(KERN_INFO "@####### termios initializated\n"); |
*(port->tty->termios) = tty_std_termios; |
port->tty->termios->c_cflag = B38400 | CS8 | CREAD | HUPCL | CLOCAL; |
priv->termios_initialized = 1; |
} |
spin_unlock_irqrestore(&priv->lock, flags); |
cflag = port->tty->termios->c_cflag; |
/* check that they really want us to change something */ |
if (old_termios) { |
if ((cflag == old_termios->c_cflag) && |
(RELEVANT_IFLAG(port->tty->termios->c_iflag) == RELEVANT_IFLAG(old_termios->c_iflag))) { |
dbg("%s - nothing to change...", __FUNCTION__); |
return; |
} |
} |
buf = kmalloc (7, GFP_KERNEL); |
if (!buf) { |
dev_err(&port->dev, "%s - out of memory.\n", __FUNCTION__); |
return; |
} |
memset (buf, 0x00, 0x07); |
i = usb_control_msg (serial->dev, usb_rcvctrlpipe (serial->dev, 0), |
GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, |
0, 0, buf, 7, 100); |
dbg ("0xa1:0x21:0:0 %d - %x %x %x %x %x %x %x", i, |
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); |
if (cflag & CSIZE) { |
switch (cflag & CSIZE) { |
case CS5: buf[6] = 5; break; |
case CS6: buf[6] = 6; break; |
case CS7: buf[6] = 7; break; |
default: |
case CS8: buf[6] = 8; break; |
} |
dbg("%s - data bits = %d", __FUNCTION__, buf[6]); |
} |
baud = 0; |
switch (cflag & CBAUD) { |
case B0: baud = 0; break; |
case B75: baud = 75; break; |
case B150: baud = 150; break; |
case B300: baud = 300; break; |
case B600: baud = 600; break; |
case B1200: baud = 1200; break; |
case B1800: baud = 1800; break; |
case B2400: baud = 2400; break; |
case B4800: baud = 4800; break; |
case B9600: baud = 9600; break; |
case B19200: baud = 19200; break; |
case B38400: baud = 38400; break; |
case B57600: baud = 57600; break; |
case B115200: baud = 115200; break; |
case B230400: baud = 230400; break; |
case B460800: baud = 460800; break; |
default: |
dev_err(&port->dev, "pl2303 driver does not support the baudrate requested (fix it)\n"); |
break; |
} |
dbg("%s - baud = %d", __FUNCTION__, baud); |
if (baud) { |
buf[0] = baud & 0xff; |
buf[1] = (baud >> 8) & 0xff; |
buf[2] = (baud >> 16) & 0xff; |
buf[3] = (baud >> 24) & 0xff; |
} |
/* For reference buf[4]=0 is 1 stop bits */ |
/* For reference buf[4]=1 is 1.5 stop bits */ |
/* For reference buf[4]=2 is 2 stop bits */ |
if (cflag & CSTOPB) { |
buf[4] = 2; |
dbg("%s - stop bits = 2", __FUNCTION__); |
} else { |
buf[4] = 0; |
dbg("%s - stop bits = 1", __FUNCTION__); |
} |
if (cflag & PARENB) { |
/* For reference buf[5]=0 is none parity */ |
/* For reference buf[5]=1 is odd parity */ |
/* For reference buf[5]=2 is even parity */ |
/* For reference buf[5]=3 is mark parity */ |
/* For reference buf[5]=4 is space parity */ |
if (cflag & PARODD) { |
buf[5] = 1; |
dbg("%s - parity = odd", __FUNCTION__); |
} else { |
buf[5] = 2; |
dbg("%s - parity = even", __FUNCTION__); |
} |
} else { |
buf[5] = 0; |
dbg("%s - parity = none", __FUNCTION__); |
} |
i = usb_control_msg (serial->dev, usb_sndctrlpipe (serial->dev, 0), |
SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE, |
0, 0, buf, 7, 100); |
dbg ("0x21:0x20:0:0 %d", i); |
/* change control lines if we are switching to or from B0 */ |
spin_lock_irqsave(&priv->lock, flags); |
control = priv->line_control; |
if ((cflag & CBAUD) == B0) |
priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS); |
else |
priv->line_control |= (CONTROL_DTR | CONTROL_RTS); |
if (control != priv->line_control) { |
control = priv->line_control; |
spin_unlock_irqrestore(&priv->lock, flags); |
set_control_lines(serial->dev, control); |
} else { |
spin_unlock_irqrestore(&priv->lock, flags); |
} |
buf[0] = buf[1] = buf[2] = buf[3] = buf[4] = buf[5] = buf[6] = 0; |
i = usb_control_msg (serial->dev, usb_rcvctrlpipe (serial->dev, 0), |
GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, |
0, 0, buf, 7, 100); |
dbg ("0xa1:0x21:0:0 %d - %x %x %x %x %x %x %x", i, |
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); |
if (cflag & CRTSCTS) { |
i = usb_control_msg (serial->dev, usb_sndctrlpipe (serial->dev, 0), |
VENDOR_WRITE_REQUEST, VENDOR_WRITE_REQUEST_TYPE, |
0x0, 0x41, NULL, 0, 100); |
dbg ("0x40:0x1:0x0:0x41 %d", i); |
} |
kfree (buf); |
} |
static int pl2303_open (struct usb_serial_port *port, struct file *filp) |
{ |
struct termios tmp_termios; |
struct usb_serial *serial = port->serial; |
unsigned char buf[10]; |
int result; |
if (port_paranoia_check (port, __FUNCTION__)) |
return -ENODEV; |
dbg("%s - port %d", __FUNCTION__, port->number); |
usb_clear_halt(serial->dev, port->write_urb->pipe); |
usb_clear_halt(serial->dev, port->read_urb->pipe); |
#define FISH(a,b,c,d) \ |
result=usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev,0), \ |
b, a, c, d, buf, 1, 100); \ |
dbg("0x%x:0x%x:0x%x:0x%x %d - %x",a,b,c,d,result,buf[0]); |
#define SOUP(a,b,c,d) \ |
result=usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev,0), \ |
b, a, c, d, NULL, 0, 100); \ |
dbg("0x%x:0x%x:0x%x:0x%x %d",a,b,c,d,result); |
FISH (VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, 0x8484, 0); |
SOUP (VENDOR_WRITE_REQUEST_TYPE, VENDOR_WRITE_REQUEST, 0x0404, 0); |
FISH (VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, 0x8484, 0); |
FISH (VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, 0x8383, 0); |
FISH (VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, 0x8484, 0); |
SOUP (VENDOR_WRITE_REQUEST_TYPE, VENDOR_WRITE_REQUEST, 0x0404, 1); |
FISH (VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, 0x8484, 0); |
FISH (VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, 0x8383, 0); |
/* Setup termios */ |
if (port->tty) { |
printk(KERN_INFO "#####@ set termios"); |
pl2303_set_termios (port, &tmp_termios); |
} |
//FIXME: need to assert RTS and DTR if CRTSCTS off |
dbg("%s - submitting read urb", __FUNCTION__); |
port->read_urb->dev = serial->dev; |
result = usb_submit_urb (port->read_urb, GFP_KERNEL); |
if (result) { |
dev_err(&port->dev, "%s - failed submitting read urb, error %d\n", __FUNCTION__, result); |
pl2303_close (port, NULL); |
return -EPROTO; |
} |
dbg("%s - submitting interrupt urb", __FUNCTION__); |
port->interrupt_in_urb->dev = serial->dev; |
result = usb_submit_urb (port->interrupt_in_urb, GFP_KERNEL); |
if (result) { |
dev_err(&port->dev, "%s - failed submitting interrupt urb, error %d\n", __FUNCTION__, result); |
pl2303_close (port, NULL); |
return -EPROTO; |
} |
return 0; |
} |
static void pl2303_close (struct usb_serial_port *port, struct file *filp) |
{ |
struct usb_serial *serial; |
struct pl2303_private *priv; |
unsigned long flags; |
unsigned int c_cflag; |
int result; |
if (port_paranoia_check (port, __FUNCTION__)) |
return; |
serial = get_usb_serial (port, __FUNCTION__); |
if (!serial) |
return; |
dbg("%s - port %d", __FUNCTION__, port->number); |
/* shutdown our urbs */ |
dbg("%s - shutting down urbs", __FUNCTION__); |
result = usb_unlink_urb (port->write_urb); |
if (result) |
dbg("%s - usb_unlink_urb (write_urb)" |
" failed with reason: %d", __FUNCTION__, |
result); |
result = usb_unlink_urb (port->read_urb); |
if (result) |
dbg("%s - usb_unlink_urb (read_urb) " |
"failed with reason: %d", __FUNCTION__, |
result); |
result = usb_unlink_urb (port->interrupt_in_urb); |
if (result) |
dbg("%s - usb_unlink_urb (interrupt_in_urb)" |
" failed with reason: %d", __FUNCTION__, |
result); |
if (port->tty) { |
c_cflag = port->tty->termios->c_cflag; |
if (c_cflag & HUPCL) { |
/* drop DTR and RTS */ |
priv = usb_get_serial_port_data(port); |
spin_lock_irqsave(&priv->lock, flags); |
priv->line_control = 0; |
spin_unlock_irqrestore (&priv->lock, flags); |
set_control_lines (port->serial->dev, 0); |
} |
} |
} |
static int pl2303_tiocmset (struct usb_serial_port *port, struct file *file, |
unsigned int set, unsigned int clear) |
{ |
struct pl2303_private *priv = usb_get_serial_port_data(port); |
unsigned long flags; |
u8 control; |
spin_lock_irqsave (&priv->lock, flags); |
if (set & TIOCM_RTS) |
priv->line_control |= CONTROL_RTS; |
if (set & TIOCM_DTR) |
priv->line_control |= CONTROL_DTR; |
if (clear & TIOCM_RTS) |
priv->line_control &= ~CONTROL_RTS; |
if (clear & TIOCM_DTR) |
priv->line_control &= ~CONTROL_DTR; |
control = priv->line_control; |
spin_unlock_irqrestore (&priv->lock, flags); |
return set_control_lines (port->serial->dev, control); |
} |
static int pl2303_tiocmget (struct usb_serial_port *port, struct file *file) |
{ |
struct pl2303_private *priv = usb_get_serial_port_data(port); |
unsigned long flags; |
unsigned int mcr; |
unsigned int status; |
unsigned int result; |
dbg("%s (%d)", __FUNCTION__, port->number); |
spin_lock_irqsave (&priv->lock, flags); |
mcr = priv->line_control; |
status = priv->line_status; |
spin_unlock_irqrestore (&priv->lock, flags); |
result = ((mcr & CONTROL_DTR) ? TIOCM_DTR : 0) |
| ((mcr & CONTROL_RTS) ? TIOCM_RTS : 0) |
| ((status & UART_CTS) ? TIOCM_CTS : 0) |
| ((status & UART_DSR) ? TIOCM_DSR : 0) |
| ((status & UART_RING) ? TIOCM_RI : 0) |
| ((status & UART_DCD) ? TIOCM_CD : 0); |
dbg("%s - result = %x", __FUNCTION__, result); |
return result; |
} |
static int pl2303_ioctl (struct usb_serial_port *port, struct file *file, unsigned int cmd, unsigned long arg) |
{ |
dbg("%s (%d) cmd = 0x%04x", __FUNCTION__, port->number, cmd); |
switch (cmd) { |
default: |
dbg("%s not supported = 0x%04x", __FUNCTION__, cmd); |
break; |
} |
return -ENOIOCTLCMD; |
} |
static void pl2303_break_ctl (struct usb_serial_port *port, int break_state) |
{ |
struct usb_serial *serial = port->serial; |
u16 state; |
int result; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (break_state == 0) |
state = BREAK_OFF; |
else |
state = BREAK_ON; |
dbg("%s - turning break %s", state==BREAK_OFF ? "off" : "on", __FUNCTION__); |
result = usb_control_msg (serial->dev, usb_sndctrlpipe (serial->dev, 0), |
BREAK_REQUEST, BREAK_REQUEST_TYPE, state, |
0, NULL, 0, 100); |
if (result) |
dbg("%s - error sending break = %d", __FUNCTION__, result); |
} |
static void pl2303_shutdown (struct usb_serial *serial) |
{ |
int i; |
dbg("%s", __FUNCTION__); |
for (i = 0; i < serial->num_ports; ++i) { |
kfree (usb_get_serial_port_data(serial->port[i])); |
usb_set_serial_port_data(serial->port[i], NULL); |
} |
} |
static void pl2303_read_int_callback (struct urb *urb, struct pt_regs *regs) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) urb->context; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
struct pl2303_private *priv = usb_get_serial_port_data(port); |
unsigned char *data = urb->transfer_buffer; |
unsigned long flags; |
int status; |
dbg("%s (%d)", __FUNCTION__, port->number); |
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", __FUNCTION__, urb->status); |
return; |
default: |
dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); |
goto exit; |
} |
if (!serial) { |
return; |
} |
usb_serial_debug_data (__FILE__, __FUNCTION__, urb->actual_length, urb->transfer_buffer); |
if (urb->actual_length < UART_STATE) |
goto exit; |
/* Save off the uart status for others to look at */ |
spin_lock_irqsave(&priv->lock, flags); |
priv->line_status = data[UART_STATE]; |
spin_unlock_irqrestore(&priv->lock, flags); |
exit: |
status = usb_submit_urb (urb, GFP_ATOMIC); |
if (status) |
dev_err(&urb->dev->dev, "%s - usb_submit_urb failed with result %d\n", |
__FUNCTION__, status); |
} |
static void pl2303_read_bulk_callback (struct urb *urb, struct pt_regs *regs) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) urb->context; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
struct pl2303_private *priv = usb_get_serial_port_data(port); |
struct tty_struct *tty; |
unsigned char *data = urb->transfer_buffer; |
unsigned long flags; |
int i; |
int result; |
u8 status; |
char tty_flag; |
if (port_paranoia_check (port, __FUNCTION__)) |
return; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (!serial) { |
dbg("%s - bad serial pointer, exiting", __FUNCTION__); |
return; |
} |
if (urb->status) { |
dbg("%s - urb->status = %d", __FUNCTION__, urb->status); |
if (!port->open_count) { |
dbg("%s - port is closed, exiting.", __FUNCTION__); |
return; |
} |
if (urb->status == -EPROTO) { |
/* PL2303 mysteriously fails with -EPROTO reschedule the read */ |
dbg("%s - caught -EPROTO, resubmitting the urb", __FUNCTION__); |
urb->status = 0; |
urb->dev = serial->dev; |
result = usb_submit_urb(urb, GFP_ATOMIC); |
if (result) |
dev_err(&urb->dev->dev, "%s - failed resubmitting read urb, error %d\n", __FUNCTION__, result); |
return; |
} |
dbg("%s - unable to handle the error, exiting.", __FUNCTION__); |
return; |
} |
usb_serial_debug_data (__FILE__, __FUNCTION__, urb->actual_length, data); |
/* get tty_flag from status */ |
tty_flag = TTY_NORMAL; |
spin_lock_irqsave(&priv->lock, flags); |
status = priv->line_status; |
spin_unlock_irqrestore(&priv->lock, flags); |
/* break takes precedence over parity, */ |
/* which takes precedence over framing errors */ |
if (status & UART_BREAK_ERROR ) |
tty_flag = TTY_BREAK; |
else if (status & UART_PARITY_ERROR) |
tty_flag = TTY_PARITY; |
else if (status & UART_FRAME_ERROR) |
tty_flag = TTY_FRAME; |
dbg("%s - tty_flag = %d", __FUNCTION__, tty_flag); |
tty = port->tty; |
if (tty && urb->actual_length) { |
/* overrun is special, not associated with a char */ |
if (status & UART_OVERRUN_ERROR) |
tty_insert_flip_char(tty, 0, TTY_OVERRUN); |
for (i = 0; i < urb->actual_length; ++i) { |
if (tty->flip.count >= TTY_FLIPBUF_SIZE) { |
tty_flip_buffer_push(tty); |
} |
//**#ifdef DEBUG_ME |
tty_insert_flip_char (tty, data[i], tty_flag); |
//**#endif /* DEBUG_ME */ |
} |
tty_flip_buffer_push (tty); |
} |
/* Schedule the next read _if_ we are still open */ |
if (port->open_count) { |
urb->dev = serial->dev; |
result = usb_submit_urb(urb, GFP_ATOMIC); |
if (result) |
dev_err(&urb->dev->dev, "%s - failed resubmitting read urb, error %d\n", __FUNCTION__, result); |
} |
return; |
} |
static void pl2303_write_bulk_callback (struct urb *urb, struct pt_regs *regs) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) urb->context; |
int result; |
if (port_paranoia_check (port, __FUNCTION__)) |
return; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (urb->status) { |
/* error in the urb, so we have to resubmit it */ |
if (serial_paranoia_check (port->serial, __FUNCTION__)) { |
return; |
} |
dbg("%s - Overflow in write", __FUNCTION__); |
dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status); |
port->write_urb->transfer_buffer_length = 1; |
port->write_urb->dev = port->serial->dev; |
result = usb_submit_urb (port->write_urb, GFP_ATOMIC); |
if (result) |
dev_err(&urb->dev->dev, "%s - failed resubmitting write urb, error %d\n", __FUNCTION__, result); |
return; |
} |
schedule_work(&port->work); |
} |
/*static*/ int __init pl2303_init (void) |
{ |
int retval; |
retval = usb_serial_register(&pl2303_device); |
if (retval) |
goto failed_usb_serial_register; |
retval = usb_register(&pl2303_driver); |
if (retval) |
goto failed_usb_register; |
info(DRIVER_DESC " " DRIVER_VERSION); |
return 0; |
failed_usb_register: |
usb_serial_deregister(&pl2303_device); |
failed_usb_serial_register: |
return retval; |
} |
/*static*/ void __exit pl2303_exit (void) |
{ |
usb_deregister (&pl2303_driver); |
usb_serial_deregister (&pl2303_device); |
} |
module_init(pl2303_init); |
module_exit(pl2303_exit); |
MODULE_DESCRIPTION(DRIVER_DESC); |
MODULE_LICENSE("GPL"); |
MODULE_PARM(debug, "i"); |
MODULE_PARM_DESC(debug, "Debug enabled or not"); |
/shark/trunk/drivers/usb/serial/usb-serial.c |
---|
0,0 → 1,1476 |
/* |
* USB Serial Converter driver |
* |
* Copyright (C) 1999 - 2003 Greg Kroah-Hartman (greg@kroah.com) |
* Copyright (C) 2000 Peter Berger (pberger@brimson.com) |
* Copyright (C) 2000 Al Borchers (borchers@steinerpoint.com) |
* |
* This program is free software; you can redistribute it and/or |
* modify it under the terms of the GNU General Public License version |
* 2 as published by the Free Software Foundation. |
* |
* This driver was originally based on the ACM driver by Armin Fuerst (which was |
* based on a driver by Brad Keryan) |
* |
* See Documentation/usb/usb-serial.txt for more information on using this driver |
* |
* (12/10/2002) gkh |
* Split the ports off into their own struct device, and added a |
* usb-serial bus driver. |
* |
* (11/19/2002) gkh |
* removed a few #ifdefs for the generic code and cleaned up the failure |
* logic in initialization. |
* |
* (10/02/2002) gkh |
* moved the console code to console.c and out of this file. |
* |
* (06/05/2002) gkh |
* moved location of startup() call in serial_probe() until after all |
* of the port information and endpoints are initialized. This makes |
* things easier for some drivers. |
* |
* (04/10/2002) gkh |
* added serial_read_proc function which creates a |
* /proc/tty/driver/usb-serial file. |
* |
* (03/27/2002) gkh |
* Got USB serial console code working properly and merged into the main |
* version of the tree. Thanks to Randy Dunlap for the initial version |
* of this code, and for pushing me to finish it up. |
* The USB serial console works with any usb serial driver device. |
* |
* (03/21/2002) gkh |
* Moved all manipulation of port->open_count into the core. Now the |
* individual driver's open and close functions are called only when the |
* first open() and last close() is called. Making the drivers a bit |
* smaller and simpler. |
* Fixed a bug if a driver didn't have the owner field set. |
* |
* (02/26/2002) gkh |
* Moved all locking into the main serial_* functions, instead of having |
* the individual drivers have to grab the port semaphore. This should |
* reduce races. |
* Reworked the MOD_INC logic a bit to always increment and decrement, even |
* if the generic driver is being used. |
* |
* (10/10/2001) gkh |
* usb_serial_disconnect() now sets the serial->dev pointer is to NULL to |
* help prevent child drivers from accessing the device since it is now |
* gone. |
* |
* (09/13/2001) gkh |
* Moved generic driver initialize after we have registered with the USB |
* core. Thanks to Randy Dunlap for pointing this problem out. |
* |
* (07/03/2001) gkh |
* Fixed module paramater size. Thanks to John Brockmeyer for the pointer. |
* Fixed vendor and product getting defined through the MODULE_PARM macro |
* if the Generic driver wasn't compiled in. |
* Fixed problem with generic_shutdown() not being called for drivers that |
* don't have a shutdown() function. |
* |
* (06/06/2001) gkh |
* added evil hack that is needed for the prolific pl2303 device due to the |
* crazy way its endpoints are set up. |
* |
* (05/30/2001) gkh |
* switched from using spinlock to a semaphore, which fixes lots of problems. |
* |
* (04/08/2001) gb |
* Identify version on module load. |
* |
* 2001_02_05 gkh |
* Fixed buffer overflows bug with the generic serial driver. Thanks to |
* Todd Squires <squirest@ct0.com> for fixing this. |
* |
* (01/10/2001) gkh |
* Fixed bug where the generic serial adaptor grabbed _any_ device that was |
* offered to it. |
* |
* (12/12/2000) gkh |
* Removed MOD_INC and MOD_DEC from poll and disconnect functions, and |
* moved them to the serial_open and serial_close functions. |
* Also fixed bug with there not being a MOD_DEC for the generic driver |
* (thanks to Gary Brubaker for finding this.) |
* |
* (11/29/2000) gkh |
* Small NULL pointer initialization cleanup which saves a bit of disk image |
* |
* (11/01/2000) Adam J. Richter |
* instead of using idVendor/idProduct pairs, usb serial drivers |
* now identify their hardware interest with usb_device_id tables, |
* which they usually have anyhow for use with MODULE_DEVICE_TABLE. |
* |
* (10/05/2000) gkh |
* Fixed bug with urb->dev not being set properly, now that the usb |
* core needs it. |
* |
* (09/11/2000) gkh |
* Removed DEBUG #ifdefs with call to usb_serial_debug_data |
* |
* (08/28/2000) gkh |
* Added port_lock to port structure. |
* Added locks for SMP safeness to generic driver |
* Fixed the ability to open a generic device's port more than once. |
* |
* (07/23/2000) gkh |
* Added bulk_out_endpointAddress to port structure. |
* |
* (07/19/2000) gkh, pberger, and borchers |
* Modifications to allow usb-serial drivers to be modules. |
* |
* (07/03/2000) gkh |
* Added more debugging to serial_ioctl call |
* |
* (06/25/2000) gkh |
* Changed generic_write_bulk_callback to not call wake_up_interruptible |
* directly, but to have port_softint do it at a safer time. |
* |
* (06/23/2000) gkh |
* Cleaned up debugging statements in a quest to find UHCI timeout bug. |
* |
* (05/22/2000) gkh |
* Changed the makefile, enabling the big CONFIG_USB_SERIAL_SOMTHING to be |
* removed from the individual device source files. |
* |
* (05/03/2000) gkh |
* Added the Digi Acceleport driver from Al Borchers and Peter Berger. |
* |
* (05/02/2000) gkh |
* Changed devfs and tty register code to work properly now. This was based on |
* the ACM driver changes by Vojtech Pavlik. |
* |
* (04/27/2000) Ryan VanderBijl |
* Put calls to *_paranoia_checks into one function. |
* |
* (04/23/2000) gkh |
* Fixed bug that Randy Dunlap found for Generic devices with no bulk out ports. |
* Moved when the startup code printed out the devices that are supported. |
* |
* (04/19/2000) gkh |
* Added driver for ZyXEL omni.net lcd plus ISDN TA |
* Made startup info message specify which drivers were compiled in. |
* |
* (04/03/2000) gkh |
* Changed the probe process to remove the module unload races. |
* Changed where the tty layer gets initialized to have devfs work nicer. |
* Added initial devfs support. |
* |
* (03/26/2000) gkh |
* Split driver up into device specific pieces. |
* |
* (03/19/2000) gkh |
* Fixed oops that could happen when device was removed while a program |
* was talking to the device. |
* Removed the static urbs and now all urbs are created and destroyed |
* dynamically. |
* Reworked the internal interface. Now everything is based on the |
* usb_serial_port structure instead of the larger usb_serial structure. |
* This fixes the bug that a multiport device could not have more than |
* one port open at one time. |
* |
* (03/17/2000) gkh |
* Added config option for debugging messages. |
* Added patch for keyspan pda from Brian Warner. |
* |
* (03/06/2000) gkh |
* Added the keyspan pda code from Brian Warner <warner@lothar.com> |
* Moved a bunch of the port specific stuff into its own structure. This |
* is in anticipation of the true multiport devices (there's a bug if you |
* try to access more than one port of any multiport device right now) |
* |
* (02/21/2000) gkh |
* Made it so that any serial devices only have to specify which functions |
* they want to overload from the generic function calls (great, |
* inheritance in C, in a driver, just what I wanted...) |
* Added support for set_termios and ioctl function calls. No drivers take |
* advantage of this yet. |
* Removed the #ifdef MODULE, now there is no module specific code. |
* Cleaned up a few comments in usb-serial.h that were wrong (thanks again |
* to Miles Lott). |
* Small fix to get_free_serial. |
* |
* (02/14/2000) gkh |
* Removed the Belkin and Peracom functionality from the driver due to |
* the lack of support from the vendor, and me not wanting people to |
* accidenatly buy the device, expecting it to work with Linux. |
* Added read_bulk_callback and write_bulk_callback to the type structure |
* for the needs of the FTDI and WhiteHEAT driver. |
* Changed all reverences to FTDI to FTDI_SIO at the request of Bill |
* Ryder. |
* Changed the output urb size back to the max endpoint size to make |
* the ftdi_sio driver have it easier, and due to the fact that it didn't |
* really increase the speed any. |
* |
* (02/11/2000) gkh |
* Added VISOR_FUNCTION_CONSOLE to the visor startup function. This was a |
* patch from Miles Lott (milos@insync.net). |
* Fixed bug with not restoring the minor range that a device grabs, if |
* the startup function fails (thanks Miles for finding this). |
* |
* (02/05/2000) gkh |
* Added initial framework for the Keyspan PDA serial converter so that |
* Brian Warner has a place to put his code. |
* Made the ezusb specific functions generic enough that different |
* devices can use them (whiteheat and keyspan_pda both need them). |
* Split out a whole bunch of structure and other stuff to a separate |
* usb-serial.h file. |
* Made the Visor connection messages a little more understandable, now |
* that Miles Lott (milos@insync.net) has gotten the Generic channel to |
* work. Also made them always show up in the log file. |
* |
* (01/25/2000) gkh |
* Added initial framework for FTDI serial converter so that Bill Ryder |
* has a place to put his code. |
* Added the vendor specific info from Handspring. Now we can print out |
* informational debug messages as well as understand what is happening. |
* |
* (01/23/2000) gkh |
* Fixed problem of crash when trying to open a port that didn't have a |
* device assigned to it. Made the minor node finding a little smarter, |
* now it looks to find a continuous space for the new device. |
* |
* (01/21/2000) gkh |
* Fixed bug in visor_startup with patch from Miles Lott (milos@insync.net) |
* Fixed get_serial_by_minor which was all messed up for multi port |
* devices. Fixed multi port problem for generic devices. Now the number |
* of ports is determined by the number of bulk out endpoints for the |
* generic device. |
* |
* (01/19/2000) gkh |
* Removed lots of cruft that was around from the old (pre urb) driver |
* interface. |
* Made the serial_table dynamic. This should save lots of memory when |
* the number of minor nodes goes up to 256. |
* Added initial support for devices that have more than one port. |
* Added more debugging comments for the Visor, and added a needed |
* set_configuration call. |
* |
* (01/17/2000) gkh |
* Fixed the WhiteHEAT firmware (my processing tool had a bug) |
* and added new debug loader firmware for it. |
* Removed the put_char function as it isn't really needed. |
* Added visor startup commands as found by the Win98 dump. |
* |
* (01/13/2000) gkh |
* Fixed the vendor id for the generic driver to the one I meant it to be. |
* |
* (01/12/2000) gkh |
* Forget the version numbering...that's pretty useless... |
* Made the driver able to be compiled so that the user can select which |
* converter they want to use. This allows people who only want the Visor |
* support to not pay the memory size price of the WhiteHEAT. |
* Fixed bug where the generic driver (idVendor=0000 and idProduct=0000) |
* grabbed the root hub. Not good. |
* |
* version 0.4.0 (01/10/2000) gkh |
* Added whiteheat.h containing the firmware for the ConnectTech WhiteHEAT |
* device. Added startup function to allow firmware to be downloaded to |
* a device if it needs to be. |
* Added firmware download logic to the WhiteHEAT device. |
* Started to add #defines to split up the different drivers for potential |
* configuration option. |
* |
* version 0.3.1 (12/30/99) gkh |
* Fixed problems with urb for bulk out. |
* Added initial support for multiple sets of endpoints. This enables |
* the Handspring Visor to be attached successfully. Only the first |
* bulk in / bulk out endpoint pair is being used right now. |
* |
* version 0.3.0 (12/27/99) gkh |
* Added initial support for the Handspring Visor based on a patch from |
* Miles Lott (milos@sneety.insync.net) |
* Cleaned up the code a bunch and converted over to using urbs only. |
* |
* version 0.2.3 (12/21/99) gkh |
* Added initial support for the Connect Tech WhiteHEAT converter. |
* Incremented the number of ports in expectation of getting the |
* WhiteHEAT to work properly (4 ports per connection). |
* Added notification on insertion and removal of what port the |
* device is/was connected to (and what kind of device it was). |
* |
* version 0.2.2 (12/16/99) gkh |
* Changed major number to the new allocated number. We're legal now! |
* |
* version 0.2.1 (12/14/99) gkh |
* Fixed bug that happens when device node is opened when there isn't a |
* device attached to it. Thanks to marek@webdesign.no for noticing this. |
* |
* version 0.2.0 (11/10/99) gkh |
* Split up internals to make it easier to add different types of serial |
* converters to the code. |
* Added a "generic" driver that gets it's vendor and product id |
* from when the module is loaded. Thanks to David E. Nelson (dnelson@jump.net) |
* for the idea and sample code (from the usb scanner driver.) |
* Cleared up any licensing questions by releasing it under the GNU GPL. |
* |
* version 0.1.2 (10/25/99) gkh |
* Fixed bug in detecting device. |
* |
* version 0.1.1 (10/05/99) gkh |
* Changed the major number to not conflict with anything else. |
* |
* version 0.1 (09/28/99) gkh |
* Can recognize the two different devices and start up a read from |
* device when asked to. Writes also work. No control signals yet, this |
* all is vendor specific data (i.e. no spec), also no control for |
* different baud rates or other bit settings. |
* Currently we are using the same devid as the acm driver. This needs |
* to change. |
* |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/kernel.h> |
#include <linux/errno.h> |
#include <linux/init.h> |
#include <linux/slab.h> |
#include <linux/tty.h> |
#include <linux/tty_driver.h> |
#include <linux/tty_flip.h> |
#include <linux/module.h> |
#include <linux/spinlock.h> |
#include <linux/list.h> |
#include <linux/smp_lock.h> |
#include <asm/uaccess.h> |
#include <linux/usb.h> |
#ifdef CONFIG_USB_SERIAL_DEBUG |
static int debug = 1; |
#else |
static int debug; |
#endif |
#include "usb-serial.h" |
#include "pl2303.h" |
/* |
* Version Information |
*/ |
#define DRIVER_VERSION "v2.0" |
#define DRIVER_AUTHOR "Greg Kroah-Hartman, greg@kroah.com, http://www.kroah.com/linux/" |
#define DRIVER_DESC "USB Serial Driver core" |
#ifdef CONFIG_USB_SERIAL_GENERIC |
/* we want to look at all devices, as the vendor/product id can change |
* depending on the command line argument */ |
static struct usb_device_id generic_serial_ids[] = { |
{.driver_info = 42}, |
{} |
}; |
#endif /* CONFIG_USB_SERIAL_GENERIC */ |
/* Driver structure we register with the USB core */ |
static struct usb_driver usb_serial_driver = { |
.owner = THIS_MODULE, |
.name = "usbserial", |
.probe = usb_serial_probe, |
.disconnect = usb_serial_disconnect, |
#ifdef CONFIG_USB_SERIAL_GENERIC |
.id_table = generic_serial_ids, |
#endif |
}; |
/* There is no MODULE_DEVICE_TABLE for usbserial.c. Instead |
the MODULE_DEVICE_TABLE declarations in each serial driver |
cause the "hotplug" program to pull in whatever module is necessary |
via modprobe, and modprobe will load usbserial because the serial |
drivers depend on it. |
*/ |
static struct usb_serial *serial_table[SERIAL_TTY_MINORS]; /* initially all NULL */ |
static LIST_HEAD(usb_serial_driver_list); |
struct usb_serial *usb_serial_get_by_index(unsigned index) |
{ |
struct usb_serial *serial = serial_table[index]; |
if (serial) |
kobject_get (&serial->kobj); |
return serial; |
} |
static struct usb_serial *get_free_serial (struct usb_serial *serial, int num_ports, unsigned int *minor) |
{ |
unsigned int i, j; |
int good_spot; |
dbg("%s %d", __FUNCTION__, num_ports); |
*minor = 0; |
for (i = 0; i < SERIAL_TTY_MINORS; ++i) { |
if (serial_table[i]) |
continue; |
good_spot = 1; |
for (j = 1; j <= num_ports-1; ++j) |
if ((serial_table[i+j]) || (i+j >= SERIAL_TTY_MINORS)) { |
good_spot = 0; |
i += j; |
break; |
} |
if (good_spot == 0) |
continue; |
serial->magic = USB_SERIAL_MAGIC; |
*minor = i; |
dbg("%s - minor base = %d", __FUNCTION__, *minor); |
for (i = *minor; (i < (*minor + num_ports)) && (i < SERIAL_TTY_MINORS); ++i) |
serial_table[i] = serial; |
return serial; |
} |
return NULL; |
} |
static void return_serial (struct usb_serial *serial) |
{ |
int i; |
dbg("%s", __FUNCTION__); |
if (serial == NULL) |
return; |
for (i = 0; i < serial->num_ports; ++i) { |
serial_table[serial->minor + i] = NULL; |
} |
return; |
} |
/***************************************************************************** |
* Driver tty interface functions |
*****************************************************************************/ |
/*static*/ int serial_open (struct tty_struct *tty, struct file * filp) |
{ |
struct usb_serial *serial; |
struct usb_serial_port *port; |
unsigned int portNumber; |
int retval = 0; |
dbg("%s", __FUNCTION__); |
/* initialize the pointer incase something fails */ |
tty->driver_data = NULL; |
/* get the serial object associated with this tty pointer */ |
serial = usb_serial_get_by_index(tty->index); |
if (serial_paranoia_check (serial, __FUNCTION__)) |
return -ENODEV; |
/* set up our port structure making the tty driver remember our port object, and us it */ |
portNumber = tty->index - serial->minor; |
port = serial->port[portNumber]; |
tty->driver_data = port; |
port->tty = tty; |
/* lock this module before we call it, |
this may, which means we must bail out, safe because we are called with BKL held */ |
if (!try_module_get(serial->type->owner)) { |
retval = -ENODEV; |
goto bailout; |
} |
++port->open_count; |
if (port->open_count == 1) { |
/* only call the device specific open if this |
* is the first time the port is opened */ |
retval = serial->type->open(port, filp); |
if (retval) { |
port->open_count = 0; |
module_put(serial->type->owner); |
kobject_put(&serial->kobj); |
} |
} |
bailout: |
return retval; |
} |
static void serial_close(struct tty_struct *tty, struct file * filp) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
if (!serial) |
return; |
dbg("%s - port %d", __FUNCTION__, port->number); |
--port->open_count; |
if (port->open_count <= 0) { |
/* only call the device specific close if this |
* port is being closed by the last owner */ |
port->serial->type->close(port, filp); |
port->open_count = 0; |
if (port->tty) { |
if (port->tty->driver_data) |
port->tty->driver_data = NULL; |
port->tty = NULL; |
} |
} |
module_put(port->serial->type->owner); |
kobject_put(&port->serial->kobj); |
} |
/*static*/ int serial_write (struct tty_struct * tty, int from_user, const unsigned char *buf, int count) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
int retval = -EINVAL; |
if (!serial) |
return -ENODEV; |
dbg("%s - port %d, %d byte(s)", __FUNCTION__, port->number, count); |
if (!port->open_count) { |
dbg("%s - port not opened", __FUNCTION__); |
goto exit; |
} |
/* pass on to the driver specific version of this function */ |
retval = serial->type->write(port, from_user, buf, count); |
exit: |
return retval; |
} |
static int serial_write_room (struct tty_struct *tty) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
int retval = -EINVAL; |
if (!serial) |
return -ENODEV; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (!port->open_count) { |
dbg("%s - port not open", __FUNCTION__); |
goto exit; |
} |
/* pass on to the driver specific version of this function */ |
retval = serial->type->write_room(port); |
exit: |
return retval; |
} |
static int serial_chars_in_buffer (struct tty_struct *tty) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
int retval = -EINVAL; |
if (!serial) |
return -ENODEV; |
dbg("%s = port %d", __FUNCTION__, port->number); |
if (!port->open_count) { |
dbg("%s - port not open", __FUNCTION__); |
goto exit; |
} |
/* pass on to the driver specific version of this function */ |
retval = serial->type->chars_in_buffer(port); |
exit: |
return retval; |
} |
static void serial_throttle (struct tty_struct * tty) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
if (!serial) |
return; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (!port->open_count) { |
dbg ("%s - port not open", __FUNCTION__); |
goto exit; |
} |
/* pass on to the driver specific version of this function */ |
if (serial->type->throttle) |
serial->type->throttle(port); |
exit: |
; |
} |
static void serial_unthrottle (struct tty_struct * tty) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
if (!serial) |
return; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (!port->open_count) { |
dbg("%s - port not open", __FUNCTION__); |
goto exit; |
} |
/* pass on to the driver specific version of this function */ |
if (serial->type->unthrottle) |
serial->type->unthrottle(port); |
exit: |
; |
} |
static int serial_ioctl (struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
int retval = -ENODEV; |
if (!serial) |
return -ENODEV; |
dbg("%s - port %d, cmd 0x%.4x", __FUNCTION__, port->number, cmd); |
if (!port->open_count) { |
dbg ("%s - port not open", __FUNCTION__); |
goto exit; |
} |
/* pass on to the driver specific version of this function if it is available */ |
if (serial->type->ioctl) |
retval = serial->type->ioctl(port, file, cmd, arg); |
else |
retval = -ENOIOCTLCMD; |
exit: |
return retval; |
} |
static void serial_set_termios (struct tty_struct *tty, struct termios * old) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
if (!serial) |
return; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (!port->open_count) { |
dbg("%s - port not open", __FUNCTION__); |
goto exit; |
} |
/* pass on to the driver specific version of this function if it is available */ |
if (serial->type->set_termios) |
serial->type->set_termios(port, old); |
exit: |
; |
} |
static void serial_break (struct tty_struct *tty, int break_state) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
if (!serial) |
return; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (!port->open_count) { |
dbg("%s - port not open", __FUNCTION__); |
goto exit; |
} |
/* pass on to the driver specific version of this function if it is available */ |
if (serial->type->break_ctl) |
serial->type->break_ctl(port, break_state); |
exit: |
; |
} |
static void serial_shutdown (struct usb_serial *serial) |
{ |
dbg ("%s", __FUNCTION__); |
serial->type->shutdown(serial); |
} |
static int serial_read_proc (char *page, char **start, off_t off, int count, int *eof, void *data) |
{ |
struct usb_serial *serial; |
int length = 0; |
int i; |
off_t begin = 0; |
char tmp[40]; |
dbg("%s", __FUNCTION__); |
length += sprintf26 (page, "usbserinfo:1.0 driver:%s\n", DRIVER_VERSION); |
for (i = 0; i < SERIAL_TTY_MINORS && length < PAGE_SIZE; ++i) { |
serial = usb_serial_get_by_index(i); |
if (serial == NULL) |
continue; |
length += sprintf26 (page+length, "%d:", i); |
if (serial->type->owner) |
length += sprintf26 (page+length, " module:%s", module_name(serial->type->owner)); |
length += sprintf26 (page+length, " name:\"%s\"", serial->type->name); |
length += sprintf26 (page+length, " vendor:%04x product:%04x", serial->vendor, serial->product); |
length += sprintf26 (page+length, " num_ports:%d", serial->num_ports); |
length += sprintf26 (page+length, " port:%d", i - serial->minor + 1); |
usb_make_path(serial->dev, tmp, sizeof(tmp)); |
length += sprintf26 (page+length, " path:%s", tmp); |
length += sprintf26 (page+length, "\n"); |
if ((length + begin) > (off + count)) |
goto done; |
if ((length + begin) < off) { |
begin += length; |
length = 0; |
} |
kobject_put(&serial->kobj); |
} |
*eof = 1; |
done: |
if (off >= (length + begin)) |
return 0; |
*start = page + (off-begin); |
return ((count < begin+length-off) ? count : begin+length-off); |
} |
static int serial_tiocmget (struct tty_struct *tty, struct file *file) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
if (!serial) |
goto exit; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (!port->open_count) { |
dbg("%s - port not open", __FUNCTION__); |
goto exit; |
} |
if (serial->type->tiocmget) |
return serial->type->tiocmget(port, file); |
exit: |
return -EINVAL; |
} |
static int serial_tiocmset (struct tty_struct *tty, struct file *file, |
unsigned int set, unsigned int clear) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
if (!serial) |
goto exit; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (!port->open_count) { |
dbg("%s - port not open", __FUNCTION__); |
goto exit; |
} |
if (serial->type->tiocmset) |
return serial->type->tiocmset(port, file, set, clear); |
exit: |
return -EINVAL; |
} |
void usb_serial_port_softint(void *private) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *)private; |
struct usb_serial *serial; |
struct tty_struct *tty; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (!port) |
return; |
serial = get_usb_serial (port, __FUNCTION__); |
if (!serial) |
return; |
tty = port->tty; |
if (!tty) |
return; |
if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) { |
dbg("%s - write wakeup call.", __FUNCTION__); |
(tty->ldisc.write_wakeup)(tty); |
} |
wake_up_interruptible(&tty->write_wait); |
} |
static void destroy_serial (struct kobject *kobj) |
{ |
struct usb_serial *serial; |
struct usb_serial_port *port; |
int i; |
dbg ("%s - %s", __FUNCTION__, kobj->name); |
serial = to_usb_serial(kobj); |
serial_shutdown (serial); |
/* return the minor range that this device had */ |
return_serial(serial); |
for (i = 0; i < serial->num_ports; ++i) |
serial->port[i]->open_count = 0; |
/* the ports are cleaned up and released in port_release() */ |
for (i = 0; i < serial->num_ports; ++i) |
if (serial->port[i]->dev.parent != NULL) { |
device_unregister(&serial->port[i]->dev); |
serial->port[i] = NULL; |
} |
/* If this is a "fake" port, we have to clean it up here, as it will |
* not get cleaned up in port_release() as it was never registered with |
* the driver core */ |
if (serial->num_ports < serial->num_port_pointers) { |
for (i = serial->num_ports; i < serial->num_port_pointers; ++i) { |
port = serial->port[i]; |
if (!port) |
continue; |
if (port->read_urb) { |
usb_unlink_urb(port->read_urb); |
usb_free_urb(port->read_urb); |
} |
if (port->write_urb) { |
usb_unlink_urb(port->write_urb); |
usb_free_urb(port->write_urb); |
} |
if (port->interrupt_in_urb) { |
usb_unlink_urb(port->interrupt_in_urb); |
usb_free_urb(port->interrupt_in_urb); |
} |
kfree(port->bulk_in_buffer); |
kfree(port->bulk_out_buffer); |
kfree(port->interrupt_in_buffer); |
} |
} |
usb_put_dev(serial->dev); |
/* free up any memory that we allocated */ |
kfree (serial); |
} |
static struct kobj_type usb_serial_kobj_type = { |
.release = destroy_serial, |
}; |
static void port_release(struct device *dev) |
{ |
struct usb_serial_port *port = to_usb_serial_port(dev); |
dbg ("%s - %s", __FUNCTION__, dev->bus_id); |
if (port->read_urb) { |
usb_unlink_urb(port->read_urb); |
usb_free_urb(port->read_urb); |
} |
if (port->write_urb) { |
usb_unlink_urb(port->write_urb); |
usb_free_urb(port->write_urb); |
} |
if (port->interrupt_in_urb) { |
usb_unlink_urb(port->interrupt_in_urb); |
usb_free_urb(port->interrupt_in_urb); |
} |
kfree(port->bulk_in_buffer); |
kfree(port->bulk_out_buffer); |
kfree(port->interrupt_in_buffer); |
kfree(port); |
} |
static struct usb_serial * create_serial (struct usb_device *dev, |
struct usb_interface *interface, |
struct usb_serial_device_type *type) |
{ |
struct usb_serial *serial; |
serial = kmalloc (sizeof (*serial), GFP_KERNEL); |
if (!serial) { |
dev_err(&dev->dev, "%s - out of memory\n", __FUNCTION__); |
return NULL; |
} |
memset (serial, 0, sizeof(*serial)); |
serial->dev = usb_get_dev(dev); |
serial->type = type; |
serial->interface = interface; |
serial->vendor = dev->descriptor.idVendor; |
serial->product = dev->descriptor.idProduct; |
/* initialize the kobject portion of the usb_device */ |
kobject_init(&serial->kobj); |
serial->kobj.ktype = &usb_serial_kobj_type; |
return serial; |
} |
int usb_serial_probe(struct usb_interface *interface, |
const struct usb_device_id *id) |
{ |
struct usb_device *dev = interface_to_usbdev (interface); |
struct usb_serial *serial = NULL; |
struct usb_serial_port *port; |
struct usb_host_interface *iface_desc; |
struct usb_endpoint_descriptor *endpoint; |
struct usb_endpoint_descriptor *interrupt_in_endpoint[MAX_NUM_PORTS]; |
struct usb_endpoint_descriptor *bulk_in_endpoint[MAX_NUM_PORTS]; |
struct usb_endpoint_descriptor *bulk_out_endpoint[MAX_NUM_PORTS]; |
struct usb_serial_device_type *type = NULL; |
struct list_head *tmp; |
int retval; |
int found; |
int minor; |
int buffer_size; |
int i; |
int num_interrupt_in = 0; |
int num_bulk_in = 0; |
int num_bulk_out = 0; |
int num_ports = 0; |
int max_endpoints; |
const struct usb_device_id *id_pattern = NULL; |
/* loop through our list of known serial converters, and see if this |
device matches. */ |
found = 0; |
list_for_each (tmp, &usb_serial_driver_list) { |
type = list_entry(tmp, struct usb_serial_device_type, driver_list); |
id_pattern = usb_match_id(interface, type->id_table); |
if (id_pattern != NULL) { |
dbg("descriptor matches"); |
found = 1; |
break; |
} |
} |
if (!found) { |
/* no match */ |
dbg("none matched"); |
return -ENODEV; |
} |
serial = create_serial (dev, interface, type); |
if (!serial) { |
dev_err(&interface->dev, "%s - out of memory\n", __FUNCTION__); |
return -ENODEV; |
} |
/* if this device type has a probe function, call it */ |
if (type->probe) { |
if (!try_module_get(type->owner)) { |
dev_err(&interface->dev, "module get failed, exiting\n"); |
kfree (serial); |
return -EIO; |
} |
retval = type->probe (serial, id_pattern); |
module_put(type->owner); |
if (retval) { |
dbg ("sub driver rejected device"); |
kfree (serial); |
return retval; |
} |
} |
/* descriptor matches, let's find the endpoints needed */ |
/* check out the endpoints */ |
iface_desc = &interface->altsetting[0]; |
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { |
endpoint = &iface_desc->endpoint[i].desc; |
if ((endpoint->bEndpointAddress & 0x80) && |
((endpoint->bmAttributes & 3) == 0x02)) { |
/* we found a bulk in endpoint */ |
dbg("found bulk in"); |
bulk_in_endpoint[num_bulk_in] = endpoint; |
++num_bulk_in; |
} |
if (((endpoint->bEndpointAddress & 0x80) == 0x00) && |
((endpoint->bmAttributes & 3) == 0x02)) { |
/* we found a bulk out endpoint */ |
dbg("found bulk out"); |
bulk_out_endpoint[num_bulk_out] = endpoint; |
++num_bulk_out; |
} |
if ((endpoint->bEndpointAddress & 0x80) && |
((endpoint->bmAttributes & 3) == 0x03)) { |
/* we found a interrupt in endpoint */ |
dbg("found interrupt in"); |
interrupt_in_endpoint[num_interrupt_in] = endpoint; |
++num_interrupt_in; |
} |
} |
#if defined(CONFIG_USB_SERIAL_PL2303) || defined(CONFIG_USB_SERIAL_PL2303_MODULE) |
/* BEGIN HORRIBLE HACK FOR PL2303 */ |
/* this is needed due to the looney way its endpoints are set up */ |
if (((dev->descriptor.idVendor == PL2303_VENDOR_ID) && |
(dev->descriptor.idProduct == PL2303_PRODUCT_ID)) || |
((dev->descriptor.idVendor == ATEN_VENDOR_ID) && |
(dev->descriptor.idProduct == ATEN_PRODUCT_ID))) { |
if (interface != dev->actconfig->interface[0]) { |
/* check out the endpoints of the other interface*/ |
iface_desc = &dev->actconfig->interface[0]->altsetting[0]; |
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { |
endpoint = &iface_desc->endpoint[i].desc; |
if ((endpoint->bEndpointAddress & 0x80) && |
((endpoint->bmAttributes & 3) == 0x03)) { |
/* we found a interrupt in endpoint */ |
dbg("found interrupt in for Prolific device on separate interface"); |
interrupt_in_endpoint[num_interrupt_in] = endpoint; |
++num_interrupt_in; |
} |
} |
} |
/* Now make sure the PL-2303 is configured correctly. |
* If not, give up now and hope this hack will work |
* properly during a later invocation of usb_serial_probe |
*/ |
if (num_bulk_in == 0 || num_bulk_out == 0) { |
dev_info(&interface->dev, "PL-2303 hack: descriptors matched but endpoints did not\n"); |
kfree (serial); |
return -ENODEV; |
} |
} |
/* END HORRIBLE HACK FOR PL2303 */ |
#endif |
/* found all that we need */ |
dev_info(&interface->dev, "%s converter detected\n", type->name); |
#ifdef CONFIG_USB_SERIAL_GENERIC |
if (type == &usb_serial_generic_device) { |
num_ports = num_bulk_out; |
if (num_ports == 0) { |
dev_err(&interface->dev, "Generic device with no bulk out, not allowed.\n"); |
kfree (serial); |
return -EIO; |
} |
} |
#endif |
if (!num_ports) { |
/* if this device type has a calc_num_ports function, call it */ |
if (type->calc_num_ports) { |
if (!try_module_get(type->owner)) { |
dev_err(&interface->dev, "module get failed, exiting\n"); |
kfree (serial); |
return -EIO; |
} |
num_ports = type->calc_num_ports (serial); |
module_put(type->owner); |
} |
if (!num_ports) |
num_ports = type->num_ports; |
} |
if (get_free_serial (serial, num_ports, &minor) == NULL) { |
dev_err(&interface->dev, "No more free serial devices\n"); |
kfree (serial); |
return -ENOMEM; |
} |
serial->minor = minor; |
serial->num_ports = num_ports; |
serial->num_bulk_in = num_bulk_in; |
serial->num_bulk_out = num_bulk_out; |
serial->num_interrupt_in = num_interrupt_in; |
/* create our ports, we need as many as the max endpoints */ |
/* we don't use num_ports here cauz some devices have more endpoint pairs than ports */ |
max_endpoints = max(num_bulk_in, num_bulk_out); |
max_endpoints = max(max_endpoints, num_interrupt_in); |
max_endpoints = max(max_endpoints, (int)serial->num_ports); |
serial->num_port_pointers = max_endpoints; |
dbg("%s - setting up %d port structures for this device", __FUNCTION__, max_endpoints); |
for (i = 0; i < max_endpoints; ++i) { |
port = kmalloc(sizeof(struct usb_serial_port), GFP_KERNEL); |
if (!port) |
goto probe_error; |
memset(port, 0x00, sizeof(struct usb_serial_port)); |
port->number = i + serial->minor; |
port->serial = serial; |
port->magic = USB_SERIAL_PORT_MAGIC; |
INIT_WORK(&port->work, usb_serial_port_softint, port); |
serial->port[i] = port; |
} |
/* set up the endpoint information */ |
for (i = 0; i < num_bulk_in; ++i) { |
endpoint = bulk_in_endpoint[i]; |
port = serial->port[i]; |
port->read_urb = usb_alloc_urb (0, GFP_KERNEL); |
if (!port->read_urb) { |
dev_err(&interface->dev, "No free urbs available\n"); |
goto probe_error; |
} |
buffer_size = endpoint->wMaxPacketSize; |
port->bulk_in_endpointAddress = endpoint->bEndpointAddress; |
port->bulk_in_buffer = kmalloc (buffer_size, GFP_KERNEL); |
if (!port->bulk_in_buffer) { |
dev_err(&interface->dev, "Couldn't allocate bulk_in_buffer\n"); |
goto probe_error; |
} |
usb_fill_bulk_urb (port->read_urb, dev, |
usb_rcvbulkpipe (dev, |
endpoint->bEndpointAddress), |
port->bulk_in_buffer, buffer_size, |
serial->type->read_bulk_callback, |
port); |
} |
for (i = 0; i < num_bulk_out; ++i) { |
endpoint = bulk_out_endpoint[i]; |
port = serial->port[i]; |
port->write_urb = usb_alloc_urb(0, GFP_KERNEL); |
if (!port->write_urb) { |
dev_err(&interface->dev, "No free urbs available\n"); |
goto probe_error; |
} |
buffer_size = endpoint->wMaxPacketSize; |
port->bulk_out_size = buffer_size; |
port->bulk_out_endpointAddress = endpoint->bEndpointAddress; |
port->bulk_out_buffer = kmalloc (buffer_size, GFP_KERNEL); |
if (!port->bulk_out_buffer) { |
dev_err(&interface->dev, "Couldn't allocate bulk_out_buffer\n"); |
goto probe_error; |
} |
usb_fill_bulk_urb (port->write_urb, dev, |
usb_sndbulkpipe (dev, |
endpoint->bEndpointAddress), |
port->bulk_out_buffer, buffer_size, |
serial->type->write_bulk_callback, |
port); |
} |
for (i = 0; i < num_interrupt_in; ++i) { |
endpoint = interrupt_in_endpoint[i]; |
port = serial->port[i]; |
port->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); |
if (!port->interrupt_in_urb) { |
dev_err(&interface->dev, "No free urbs available\n"); |
goto probe_error; |
} |
buffer_size = endpoint->wMaxPacketSize; |
port->interrupt_in_endpointAddress = endpoint->bEndpointAddress; |
port->interrupt_in_buffer = kmalloc (buffer_size, GFP_KERNEL); |
if (!port->interrupt_in_buffer) { |
dev_err(&interface->dev, "Couldn't allocate interrupt_in_buffer\n"); |
goto probe_error; |
} |
usb_fill_int_urb (port->interrupt_in_urb, dev, |
usb_rcvintpipe (dev, |
endpoint->bEndpointAddress), |
port->interrupt_in_buffer, buffer_size, |
serial->type->read_int_callback, port, |
endpoint->bInterval); |
} |
/* if this device type has an attach function, call it */ |
if (type->attach) { |
if (!try_module_get(type->owner)) { |
dev_err(&interface->dev, "module get failed, exiting\n"); |
goto probe_error; |
} |
retval = type->attach (serial); |
module_put(type->owner); |
if (retval < 0) |
goto probe_error; |
if (retval > 0) { |
/* quietly accept this device, but don't bind to a serial port |
* as it's about to disappear */ |
goto exit; |
} |
} |
/* register all of the individual ports with the driver core */ |
for (i = 0; i < num_ports; ++i) { |
port = serial->port[i]; |
port->dev.parent = &interface->dev; |
port->dev.driver = NULL; |
port->dev.bus = &usb_serial_bus_type; |
port->dev.release = &port_release; |
snprintf26(&port->dev.bus_id[0], sizeof(port->dev.bus_id), "ttyUSB%d", port->number); |
dbg ("%s - registering %s", __FUNCTION__, port->dev.bus_id); |
device_register (&port->dev); |
} |
usb_serial_console_init (debug, minor); |
exit: |
/* success */ |
usb_set_intfdata (interface, serial); |
return 0; |
probe_error: |
for (i = 0; i < num_bulk_in; ++i) { |
port = serial->port[i]; |
if (!port) |
continue; |
if (port->read_urb) |
usb_free_urb (port->read_urb); |
kfree(port->bulk_in_buffer); |
} |
for (i = 0; i < num_bulk_out; ++i) { |
port = serial->port[i]; |
if (!port) |
continue; |
if (port->write_urb) |
usb_free_urb (port->write_urb); |
kfree(port->bulk_out_buffer); |
} |
for (i = 0; i < num_interrupt_in; ++i) { |
port = serial->port[i]; |
if (!port) |
continue; |
if (port->interrupt_in_urb) |
usb_free_urb (port->interrupt_in_urb); |
kfree(port->interrupt_in_buffer); |
} |
/* return the minor range that this device had */ |
return_serial (serial); |
/* free up any memory that we allocated */ |
for (i = 0; i < serial->num_port_pointers; ++i) |
kfree(serial->port[i]); |
kfree (serial); |
return -EIO; |
} |
void usb_serial_disconnect(struct usb_interface *interface) |
{ |
struct usb_serial *serial = usb_get_intfdata (interface); |
struct device *dev = &interface->dev; |
dbg ("%s", __FUNCTION__); |
usb_set_intfdata (interface, NULL); |
if (serial) { |
/* let the last holder of this object |
* cause it to be cleaned up */ |
kobject_put (&serial->kobj); |
} |
dev_info(dev, "device disconnected\n"); |
} |
static struct tty_operations serial_ops = { |
.open = serial_open, |
.close = serial_close, |
.write = serial_write, |
.write_room = serial_write_room, |
.ioctl = serial_ioctl, |
.set_termios = serial_set_termios, |
.throttle = serial_throttle, |
.unthrottle = serial_unthrottle, |
.break_ctl = serial_break, |
.chars_in_buffer = serial_chars_in_buffer, |
.read_proc = serial_read_proc, |
.tiocmget = serial_tiocmget, |
.tiocmset = serial_tiocmset, |
}; |
struct tty_driver *usb_serial_tty_driver; |
/*static*/ int __init usb_serial_init(void) |
{ |
int i; |
int result = 0; |
usb_serial_tty_driver = alloc_tty_driver(SERIAL_TTY_MINORS); |
if (!usb_serial_tty_driver) |
return -ENOMEM; |
/* Initialize our global data */ |
for (i = 0; i < SERIAL_TTY_MINORS; ++i) { |
serial_table[i] = NULL; |
} |
bus_register(&usb_serial_bus_type); |
/* register the generic driver, if we should */ |
result = usb_serial_generic_register(debug); |
if (result < 0) { |
err("%s - registering generic driver failed", __FUNCTION__); |
goto exit; |
} |
usb_serial_tty_driver->owner = THIS_MODULE; |
usb_serial_tty_driver->driver_name = "usbserial"; |
usb_serial_tty_driver->devfs_name = "usb/tts/"; |
usb_serial_tty_driver->name = "ttyUSB"; |
usb_serial_tty_driver->major = SERIAL_TTY_MAJOR; |
usb_serial_tty_driver->minor_start = 0; |
usb_serial_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; |
usb_serial_tty_driver->subtype = SERIAL_TYPE_NORMAL; |
usb_serial_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS; |
usb_serial_tty_driver->init_termios = tty_std_termios; |
usb_serial_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; |
tty_set_operations(usb_serial_tty_driver, &serial_ops); |
result = tty_register_driver(usb_serial_tty_driver); |
if (result) { |
err("%s - tty_register_driver failed", __FUNCTION__); |
goto exit_generic; |
} |
/* register the USB driver */ |
result = usb_register(&usb_serial_driver); |
if (result < 0) { |
err("%s - usb_register failed", __FUNCTION__); |
goto exit_tty; |
} |
info(DRIVER_DESC " " DRIVER_VERSION); |
return result; |
exit_tty: |
tty_unregister_driver(usb_serial_tty_driver); |
exit_generic: |
usb_serial_generic_deregister(); |
exit: |
err ("%s - returning with error %d", __FUNCTION__, result); |
put_tty_driver(usb_serial_tty_driver); |
return result; |
} |
/*static*/ void __exit usb_serial_exit(void) |
{ |
usb_serial_console_exit(); |
usb_serial_generic_deregister(); |
usb_deregister(&usb_serial_driver); |
tty_unregister_driver(usb_serial_tty_driver); |
put_tty_driver(usb_serial_tty_driver); |
bus_unregister(&usb_serial_bus_type); |
} |
module_init(usb_serial_init); |
module_exit(usb_serial_exit); |
#define set_to_generic_if_null(type, function) \ |
do { \ |
if (!type->function) { \ |
type->function = usb_serial_generic_##function; \ |
dbg("Had to override the " #function \ |
" usb serial operation with the generic one.");\ |
} \ |
} while (0) |
static void fixup_generic(struct usb_serial_device_type *device) |
{ |
set_to_generic_if_null(device, open); |
set_to_generic_if_null(device, write); |
set_to_generic_if_null(device, close); |
set_to_generic_if_null(device, write_room); |
set_to_generic_if_null(device, chars_in_buffer); |
set_to_generic_if_null(device, read_bulk_callback); |
set_to_generic_if_null(device, write_bulk_callback); |
set_to_generic_if_null(device, shutdown); |
} |
int usb_serial_register(struct usb_serial_device_type *new_device) |
{ |
int retval; |
fixup_generic(new_device); |
/* Add this device to our list of devices */ |
list_add(&new_device->driver_list, &usb_serial_driver_list); |
retval = usb_serial_bus_register (new_device); |
if (retval) |
goto error; |
info("USB Serial support registered for %s", new_device->name); |
return retval; |
error: |
err("problem %d when registering driver %s", retval, new_device->name); |
list_del(&new_device->driver_list); |
return retval; |
} |
void usb_serial_deregister(struct usb_serial_device_type *device) |
{ |
struct usb_serial *serial; |
int i; |
info("USB Serial deregistering driver %s", device->name); |
/* clear out the serial_table if the device is attached to a port */ |
for(i = 0; i < SERIAL_TTY_MINORS; ++i) { |
serial = serial_table[i]; |
if ((serial != NULL) && (serial->type == device)) { |
usb_driver_release_interface (&usb_serial_driver, serial->interface); |
usb_serial_disconnect (serial->interface); |
} |
} |
list_del(&device->driver_list); |
usb_serial_bus_deregister (device); |
} |
/* If the usb-serial core is built into the core, the usb-serial drivers |
need these symbols to load properly as modules. */ |
EXPORT_SYMBOL(usb_serial_register); |
EXPORT_SYMBOL(usb_serial_deregister); |
EXPORT_SYMBOL(usb_serial_probe); |
EXPORT_SYMBOL(usb_serial_disconnect); |
EXPORT_SYMBOL(usb_serial_port_softint); |
/* Module information */ |
MODULE_AUTHOR( DRIVER_AUTHOR ); |
MODULE_DESCRIPTION( DRIVER_DESC ); |
MODULE_LICENSE("GPL"); |
MODULE_PARM(debug, "i"); |
MODULE_PARM_DESC(debug, "Debug enabled or not"); |
/shark/trunk/drivers/usb/serial/tty_io.c |
---|
0,0 → 1,210 |
/* |
* linux/drivers/char/tty_io.c |
* |
* Copyright (C) 1991, 1992 Linus Torvalds |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/types.h> |
#include <linux/major.h> |
#include <linux/errno.h> |
#include <linux/signal.h> |
#include <linux/fcntl.h> |
#include <linux/sched.h> |
#include <linux/interrupt.h> |
#include <linux/tty.h> |
#include <linux/tty_driver.h> |
#include <linux/tty_flip.h> |
#include <linux/devpts_fs.h> |
#include <linux/file.h> |
#include <linux/console.h> |
#include <linux/timer.h> |
#include <linux/ctype.h> |
#include <linux/kd.h> |
#include <linux/mm.h> |
#include <linux/string.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/proc_fs.h> |
#include <linux/init.h> |
#include <linux/module.h> |
#include <linux/smp_lock.h> |
#include <linux/device.h> |
#include <asm/uaccess.h> |
#include <asm/system.h> |
#include <asm/bitops.h> |
#include <linux/kbd_kern.h> |
#include <linux/vt_kern.h> |
#include <linux/selection.h> |
#include <linux/devfs_fs_kernel.h> |
#include <linux/kmod.h> |
#undef TTY_DEBUG_HANGUP |
#define TTY_PARANOIA_CHECK 1 |
#define CHECK_TTY_COUNT 1 |
struct tty_ldisc ldiscs[NR_LDISCS]; |
struct termios tty_std_termios = { /* for the benefit of tty drivers */ |
.c_iflag = ICRNL | IXON, |
.c_oflag = OPOST | ONLCR, |
.c_cflag = B38400 | CS8 | CREAD | HUPCL, |
.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | |
ECHOCTL | ECHOKE | IEXTEN, |
.c_cc = INIT_C_CC |
}; |
struct tty_driver *alloc_tty_driver(int lines) |
{ |
struct tty_driver *driver; |
driver = kmalloc(sizeof(struct tty_driver), GFP_KERNEL); |
if (driver) { |
memset(driver, 0, sizeof(struct tty_driver)); |
driver->magic = TTY_DRIVER_MAGIC; |
driver->num = lines; |
/* later we'll move allocation of tables here */ |
} |
return driver; |
} |
void put_tty_driver(struct tty_driver *driver) |
{ |
kfree(driver); |
} |
void tty_set_operations(struct tty_driver *driver, struct tty_operations *op) |
{ |
driver->open = op->open; |
driver->close = op->close; |
driver->write = op->write; |
driver->put_char = op->put_char; |
driver->flush_chars = op->flush_chars; |
driver->write_room = op->write_room; |
driver->chars_in_buffer = op->chars_in_buffer; |
driver->ioctl = op->ioctl; |
driver->set_termios = op->set_termios; |
driver->throttle = op->throttle; |
driver->unthrottle = op->unthrottle; |
driver->stop = op->stop; |
driver->start = op->start; |
driver->hangup = op->hangup; |
driver->break_ctl = op->break_ctl; |
driver->flush_buffer = op->flush_buffer; |
driver->set_ldisc = op->set_ldisc; |
driver->wait_until_sent = op->wait_until_sent; |
driver->send_xchar = op->send_xchar; |
driver->read_proc = op->read_proc; |
driver->write_proc = op->write_proc; |
driver->tiocmget = op->tiocmget; |
driver->tiocmset = op->tiocmset; |
} |
int tty_register_driver(struct tty_driver *driver) |
{ |
return 0; |
} |
int tty_unregister_driver(struct tty_driver *driver) |
{ |
return 0; |
} |
void tty_register_device(struct tty_driver *driver, unsigned index, |
struct device *device) |
{ |
} |
void tty_unregister_device(struct tty_driver *driver, unsigned index) |
{ |
} |
void tty_flip_buffer_push(struct tty_struct *tty) |
{ |
printk(KERN_INFO "%c\n", *(tty->flip.char_buf_ptr -1)); |
} |
static void flush_to_ldisc(void *private_) |
{ |
} |
static struct tty_struct *alloc_tty_struct(void) |
{ |
struct tty_struct *tty; |
tty = kmalloc(sizeof(struct tty_struct), GFP_KERNEL); |
// if (tty) |
// memset(tty, 0, sizeof(struct tty_struct)); |
return tty; |
} |
void do_tty_hangup(void *data) |
{ |
} |
/* |
* This subroutine initializes a tty structure. |
*/ |
static void initialize_tty_struct(struct tty_struct *tty) |
{ |
memset(tty, 0, sizeof(struct tty_struct)); |
tty->magic = TTY_MAGIC; |
tty->ldisc = ldiscs[N_TTY]; |
tty->pgrp = -1; |
tty->flip.char_buf_ptr = tty->flip.char_buf; |
tty->flip.flag_buf_ptr = tty->flip.flag_buf; |
INIT_WORK(&tty->flip.work, flush_to_ldisc, tty); |
init_MUTEX(&tty->flip.pty_sem); |
init_waitqueue_head(&tty->write_wait); |
init_waitqueue_head(&tty->read_wait); |
INIT_WORK(&tty->hangup_work, do_tty_hangup, tty); |
//*sema_init(&tty->atomic_read, 1); |
//*sema_init(&tty->atomic_write, 1); |
spin_lock_init(&tty->read_lock); |
INIT_LIST_HEAD(&tty->tty_files); |
INIT_WORK(&tty->SAK_work, NULL, NULL); |
} |
extern int serial_open (struct tty_struct *tty, struct file * filp); |
extern int serial_write (struct tty_struct * tty, int from_user, const unsigned char *buf, int count); |
struct tty_struct *tty; |
int serial_usbport_open(int port_number) |
{ |
int retval; |
tty = alloc_tty_struct(); |
if(!tty) |
goto fail_no_mem; |
initialize_tty_struct (tty); |
tty->termios = malloc (sizeof(struct termios)); |
*(tty->termios) = tty_std_termios; |
retval = serial_open(tty, NULL); |
return retval; |
fail_no_mem: |
return -ENOMEM; |
} |
int serial_usbport_write(const unsigned char *buf, int count) |
{ |
int retval; |
retval = serial_write(tty, 0, buf, count); |
return retval; |
// printk(KERN_DEBUG "######### retval=%d\n", retval); |
} |
/shark/trunk/drivers/usb/serial/pl2303.h |
---|
0,0 → 1,39 |
/* |
* Prolific PL2303 USB to serial adaptor driver header file |
* |
* 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. |
* |
*/ |
#define PL2303_VENDOR_ID 0x067b |
#define PL2303_PRODUCT_ID 0x2303 |
#define PL2303_PRODUCT_ID_RSAQ2 0x04bb |
#define ATEN_VENDOR_ID 0x0557 |
#define ATEN_PRODUCT_ID 0x2008 |
#define IODATA_VENDOR_ID 0x04bb |
#define IODATA_PRODUCT_ID 0x0a03 |
#define ELCOM_VENDOR_ID 0x056e |
#define ELCOM_PRODUCT_ID 0x5003 |
#define ITEGNO_VENDOR_ID 0x0eba |
#define ITEGNO_PRODUCT_ID 0x1080 |
#define MA620_VENDOR_ID 0x0df7 |
#define MA620_PRODUCT_ID 0x0620 |
#define RATOC_VENDOR_ID 0x0584 |
#define RATOC_PRODUCT_ID 0xb000 |
#define TRIPP_VENDOR_ID 0x2478 |
#define TRIPP_PRODUCT_ID 0x2008 |
#define RADIOSHACK_VENDOR_ID 0x1453 |
#define RADIOSHACK_PRODUCT_ID 0x4026 |
#define DCU10_VENDOR_ID 0x0731 |
#define DCU10_PRODUCT_ID 0x0528 |
/shark/trunk/drivers/usb/serial/bus.c |
---|
0,0 → 1,142 |
/* |
* USB Serial Converter Bus specific functions |
* |
* Copyright (C) 2002 Greg Kroah-Hartman (greg@kroah.com) |
* |
* This program is free software; you can redistribute it and/or |
* modify it under the terms of the GNU General Public License version |
* 2 as published by the Free Software Foundation. |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/kernel.h> |
#include <linux/errno.h> |
#include <linux/tty.h> |
#include <linux/module.h> |
#include <linux/usb.h> |
#ifdef CONFIG_USB_SERIAL_DEBUG |
static int debug = 1; |
#else |
static int debug; |
#endif |
#include "usb-serial.h" |
static int usb_serial_device_match (struct device *dev, struct device_driver *drv) |
{ |
struct usb_serial_device_type *driver; |
const struct usb_serial_port *port; |
/* |
* drivers are already assigned to ports in serial_probe so it's |
* a simple check here. |
*/ |
port = to_usb_serial_port(dev); |
if (!port) |
return 0; |
driver = to_usb_serial_driver(drv); |
if (driver == port->serial->type) |
return 1; |
return 0; |
} |
struct bus_type usb_serial_bus_type = { |
.name = "usb-serial", |
.match = usb_serial_device_match, |
}; |
static int usb_serial_device_probe (struct device *dev) |
{ |
struct usb_serial_device_type *driver; |
struct usb_serial_port *port; |
int retval = 0; |
int minor; |
port = to_usb_serial_port(dev); |
if (!port) { |
retval = -ENODEV; |
goto exit; |
} |
driver = port->serial->type; |
if (driver->port_probe) { |
if (!try_module_get(driver->owner)) { |
dev_err(dev, "module get failed, exiting\n"); |
retval = -EIO; |
goto exit; |
} |
retval = driver->port_probe (port); |
module_put(driver->owner); |
if (retval) |
goto exit; |
} |
minor = port->number; |
tty_register_device (usb_serial_tty_driver, minor, dev); |
dev_info(&port->serial->dev->dev, |
"%s converter now attached to ttyUSB%d (or usb/tts/%d for devfs)\n", |
driver->name, minor, minor); |
exit: |
return retval; |
} |
static int usb_serial_device_remove (struct device *dev) |
{ |
struct usb_serial_device_type *driver; |
struct usb_serial_port *port; |
int retval = 0; |
int minor; |
port = to_usb_serial_port(dev); |
if (!port) { |
return -ENODEV; |
} |
driver = port->serial->type; |
if (driver->port_remove) { |
if (!try_module_get(driver->owner)) { |
dev_err(dev, "module get failed, exiting\n"); |
retval = -EIO; |
goto exit; |
} |
retval = driver->port_remove (port); |
module_put(driver->owner); |
} |
exit: |
minor = port->number; |
tty_unregister_device (usb_serial_tty_driver, minor); |
dev_info(dev, "%s converter now disconnected from ttyUSB%d\n", |
driver->name, minor); |
return retval; |
} |
int usb_serial_bus_register(struct usb_serial_device_type *device) |
{ |
int retval; |
if (device->short_name) |
device->driver.name = (char *)device->short_name; |
else |
device->driver.name = (char *)device->name; |
device->driver.bus = &usb_serial_bus_type; |
device->driver.probe = usb_serial_device_probe; |
device->driver.remove = usb_serial_device_remove; |
retval = driver_register(&device->driver); |
return retval; |
} |
void usb_serial_bus_deregister(struct usb_serial_device_type *device) |
{ |
driver_unregister (&device->driver); |
} |
/shark/trunk/drivers/usb/serial/usb-serial.h |
---|
0,0 → 1,379 |
/* |
* USB Serial Converter driver |
* |
* Copyright (C) 1999 - 2003 |
* Greg Kroah-Hartman (greg@kroah.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. |
* |
* See Documentation/usb/usb-serial.txt for more information on using this driver |
* |
* (03/26/2002) gkh |
* removed the port->tty check from port_paranoia_check() due to serial |
* consoles not having a tty device assigned to them. |
* |
* (12/03/2001) gkh |
* removed active from the port structure. |
* added documentation to the usb_serial_device_type structure |
* |
* (10/10/2001) gkh |
* added vendor and product to serial structure. Needed to determine device |
* owner when the device is disconnected. |
* |
* (05/30/2001) gkh |
* added sem to port structure and removed port_lock |
* |
* (10/05/2000) gkh |
* Added interrupt_in_endpointAddress and bulk_in_endpointAddress to help |
* fix bug with urb->dev not being set properly, now that the usb core |
* needs it. |
* |
* (09/11/2000) gkh |
* Added usb_serial_debug_data function to help get rid of #DEBUG in the |
* drivers. |
* |
* (08/28/2000) gkh |
* Added port_lock to port structure. |
* |
* (08/08/2000) gkh |
* Added open_count to port structure. |
* |
* (07/23/2000) gkh |
* Added bulk_out_endpointAddress to port structure. |
* |
* (07/19/2000) gkh, pberger, and borchers |
* Modifications to allow usb-serial drivers to be modules. |
* |
* |
*/ |
#ifndef __LINUX_USB_SERIAL_H |
#define __LINUX_USB_SERIAL_H |
#include <linux/config.h> |
#define SERIAL_TTY_MAJOR 188 /* Nice legal number now */ |
#define SERIAL_TTY_MINORS 255 /* loads of devices :) */ |
#define MAX_NUM_PORTS 8 /* The maximum number of ports one device can grab at once */ |
#define USB_SERIAL_MAGIC 0x6702 /* magic number for usb_serial struct */ |
#define USB_SERIAL_PORT_MAGIC 0x7301 /* magic number for usb_serial_port struct */ |
/* parity check flag */ |
#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) |
/** |
* usb_serial_port: structure for the specific ports of a device. |
* @magic: magic number for internal validity of this pointer. |
* @serial: pointer back to the struct usb_serial owner of this port. |
* @tty: pointer to the corresponding tty for this port. |
* @number: the number of the port (the minor number). |
* @interrupt_in_buffer: pointer to the interrupt in buffer for this port. |
* @interrupt_in_urb: pointer to the interrupt in struct urb for this port. |
* @interrupt_in_endpointAddress: endpoint address for the interrupt in pipe |
* for this port. |
* @bulk_in_buffer: pointer to the bulk in buffer for this port. |
* @read_urb: pointer to the bulk in struct urb for this port. |
* @bulk_in_endpointAddress: endpoint address for the bulk in pipe for this |
* port. |
* @bulk_out_buffer: pointer to the bulk out buffer for this port. |
* @bulk_out_size: the size of the bulk_out_buffer, in bytes. |
* @write_urb: pointer to the bulk out struct urb for this port. |
* @bulk_out_endpointAddress: endpoint address for the bulk out pipe for this |
* port. |
* @write_wait: a wait_queue_head_t used by the port. |
* @work: work queue entry for the line discipline waking up. |
* @open_count: number of times this port has been opened. |
* |
* This structure is used by the usb-serial core and drivers for the specific |
* ports of a device. |
*/ |
struct usb_serial_port { |
int magic; |
struct usb_serial *serial; |
struct tty_struct * tty; |
unsigned char number; |
unsigned char * interrupt_in_buffer; |
struct urb * interrupt_in_urb; |
__u8 interrupt_in_endpointAddress; |
unsigned char * bulk_in_buffer; |
struct urb * read_urb; |
__u8 bulk_in_endpointAddress; |
unsigned char * bulk_out_buffer; |
int bulk_out_size; |
struct urb * write_urb; |
__u8 bulk_out_endpointAddress; |
wait_queue_head_t write_wait; |
struct work_struct work; |
int open_count; |
struct device dev; |
}; |
#define to_usb_serial_port(d) container_of(d, struct usb_serial_port, dev) |
/* get and set the port private data pointer helper functions */ |
static inline void *usb_get_serial_port_data (struct usb_serial_port *port) |
{ |
return dev_get_drvdata(&port->dev); |
} |
static inline void usb_set_serial_port_data (struct usb_serial_port *port, void *data) |
{ |
dev_set_drvdata(&port->dev, data); |
} |
/** |
* usb_serial - structure used by the usb-serial core for a device |
* @magic: magic number for internal validity of this pointer. |
* @dev: pointer to the struct usb_device for this device |
* @type: pointer to the struct usb_serial_device_type for this device |
* @interface: pointer to the struct usb_interface for this device |
* @minor: the starting minor number for this device |
* @num_ports: the number of ports this device has |
* @num_interrupt_in: number of interrupt in endpoints we have |
* @num_bulk_in: number of bulk in endpoints we have |
* @num_bulk_out: number of bulk out endpoints we have |
* @vendor: vendor id of this device |
* @product: product id of this device |
* @port: array of struct usb_serial_port structures for the different ports. |
* @private: place to put any driver specific information that is needed. The |
* usb-serial driver is required to manage this data, the usb-serial core |
* will not touch this. Use usb_get_serial_data() and |
* usb_set_serial_data() to access this. |
*/ |
struct usb_serial { |
int magic; |
struct usb_device * dev; |
struct usb_serial_device_type * type; |
struct usb_interface * interface; |
unsigned char minor; |
unsigned char num_ports; |
unsigned char num_port_pointers; |
char num_interrupt_in; |
char num_bulk_in; |
char num_bulk_out; |
__u16 vendor; |
__u16 product; |
struct usb_serial_port * port[MAX_NUM_PORTS]; |
struct kobject kobj; |
void * private; |
}; |
#define to_usb_serial(d) container_of(d, struct usb_serial, kobj) |
#define NUM_DONT_CARE (-1) |
/* get and set the serial private data pointer helper functions */ |
static inline void *usb_get_serial_data (struct usb_serial *serial) |
{ |
return serial->private; |
} |
static inline void usb_set_serial_data (struct usb_serial *serial, void *data) |
{ |
serial->private = data; |
} |
/** |
* usb_serial_device_type - a structure that defines a usb serial device |
* @owner: pointer to the module that owns this device. |
* @name: pointer to a string that describes this device. This string used |
* in the syslog messages when a device is inserted or removed. |
* @short_name: a pointer to a string that describes this device in |
* KOBJ_NAME_LEN characters or less. This is used for the sysfs interface |
* to describe the driver. |
* @id_table: pointer to a list of usb_device_id structures that define all |
* of the devices this structure can support. |
* @num_interrupt_in: the number of interrupt in endpoints this device will |
* have. |
* @num_bulk_in: the number of bulk in endpoints this device will have. |
* @num_bulk_out: the number of bulk out endpoints this device will have. |
* @num_ports: the number of different ports this device will have. |
* @calc_num_ports: pointer to a function to determine how many ports this |
* device has dynamically. It will be called after the probe() |
* callback is called, but before attach() |
* @probe: pointer to the driver's probe function. |
* This will be called when the device is inserted into the system, |
* but before the device has been fully initialized by the usb_serial |
* subsystem. Use this function to download any firmware to the device, |
* or any other early initialization that might be needed. |
* Return 0 to continue on with the initialization sequence. Anything |
* else will abort it. |
* @attach: pointer to the driver's attach function. |
* This will be called when the struct usb_serial structure is fully set |
* set up. Do any local initialization of the device, or any private |
* memory structure allocation at this point in time. |
* @shutdown: pointer to the driver's shutdown function. This will be |
* called when the device is removed from the system. |
* |
* This structure is defines a USB Serial device. It provides all of |
* the information that the USB serial core code needs. If the function |
* pointers are defined, then the USB serial core code will call them when |
* the corresponding tty port functions are called. If they are not |
* called, the generic serial function will be used instead. |
*/ |
struct usb_serial_device_type { |
struct module *owner; |
char *name; |
char *short_name; |
const struct usb_device_id *id_table; |
char num_interrupt_in; |
char num_bulk_in; |
char num_bulk_out; |
char num_ports; |
struct list_head driver_list; |
struct device_driver driver; |
int (*probe) (struct usb_serial *serial, const struct usb_device_id *id); |
int (*attach) (struct usb_serial *serial); |
int (*calc_num_ports) (struct usb_serial *serial); |
void (*shutdown) (struct usb_serial *serial); |
int (*port_probe) (struct usb_serial_port *port); |
int (*port_remove) (struct usb_serial_port *port); |
/* serial function calls */ |
int (*open) (struct usb_serial_port *port, struct file * filp); |
void (*close) (struct usb_serial_port *port, struct file * filp); |
int (*write) (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count); |
int (*write_room) (struct usb_serial_port *port); |
int (*ioctl) (struct usb_serial_port *port, struct file * file, unsigned int cmd, unsigned long arg); |
void (*set_termios) (struct usb_serial_port *port, struct termios * old); |
void (*break_ctl) (struct usb_serial_port *port, int break_state); |
int (*chars_in_buffer) (struct usb_serial_port *port); |
void (*throttle) (struct usb_serial_port *port); |
void (*unthrottle) (struct usb_serial_port *port); |
int (*tiocmget) (struct usb_serial_port *port, struct file *file); |
int (*tiocmset) (struct usb_serial_port *port, struct file *file, unsigned int set, unsigned int clear); |
void (*read_int_callback)(struct urb *urb, struct pt_regs *regs); |
void (*read_bulk_callback)(struct urb *urb, struct pt_regs *regs); |
void (*write_bulk_callback)(struct urb *urb, struct pt_regs *regs); |
}; |
#define to_usb_serial_driver(d) container_of(d, struct usb_serial_device_type, driver) |
extern int usb_serial_register(struct usb_serial_device_type *new_device); |
extern void usb_serial_deregister(struct usb_serial_device_type *device); |
extern void usb_serial_port_softint(void *private); |
extern int usb_serial_probe(struct usb_interface *iface, const struct usb_device_id *id); |
extern void usb_serial_disconnect(struct usb_interface *iface); |
extern int ezusb_writememory (struct usb_serial *serial, int address, unsigned char *data, int length, __u8 bRequest); |
extern int ezusb_set_reset (struct usb_serial *serial, unsigned char reset_bit); |
/* USB Serial console functions */ |
#ifdef CONFIG_USB_SERIAL_CONSOLE |
extern void usb_serial_console_init (int debug, int minor); |
extern void usb_serial_console_exit (void); |
#else |
static inline void usb_serial_console_init (int debug, int minor) { } |
static inline void usb_serial_console_exit (void) { } |
#endif |
/* Functions needed by other parts of the usbserial core */ |
extern struct usb_serial *usb_serial_get_by_index (unsigned int minor); |
extern int usb_serial_generic_open (struct usb_serial_port *port, struct file *filp); |
extern int usb_serial_generic_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count); |
extern void usb_serial_generic_close (struct usb_serial_port *port, struct file *filp); |
extern int usb_serial_generic_write_room (struct usb_serial_port *port); |
extern int usb_serial_generic_chars_in_buffer (struct usb_serial_port *port); |
extern void usb_serial_generic_read_bulk_callback (struct urb *urb, struct pt_regs *regs); |
extern void usb_serial_generic_write_bulk_callback (struct urb *urb, struct pt_regs *regs); |
extern void usb_serial_generic_shutdown (struct usb_serial *serial); |
extern int usb_serial_generic_register (int debug); |
extern void usb_serial_generic_deregister (void); |
extern int usb_serial_bus_register (struct usb_serial_device_type *device); |
extern void usb_serial_bus_deregister (struct usb_serial_device_type *device); |
extern struct usb_serial_device_type usb_serial_generic_device; |
extern struct bus_type usb_serial_bus_type; |
extern struct tty_driver *usb_serial_tty_driver; |
/* Inline functions to check the sanity of a pointer that is passed to us */ |
static inline int serial_paranoia_check (struct usb_serial *serial, const char *function) |
{ |
if (!serial) { |
dbg("%s - serial == NULL", function); |
return -1; |
} |
if (serial->magic != USB_SERIAL_MAGIC) { |
dbg("%s - bad magic number for serial", function); |
return -1; |
} |
if (!serial->type) { |
dbg("%s - serial->type == NULL!", function); |
return -1; |
} |
return 0; |
} |
static inline int port_paranoia_check (struct usb_serial_port *port, const char *function) |
{ |
if (!port) { |
dbg("%s - port == NULL", function); |
return -1; |
} |
if (port->magic != USB_SERIAL_PORT_MAGIC) { |
dbg("%s - bad magic number for port", function); |
return -1; |
} |
if (!port->serial) { |
dbg("%s - port->serial == NULL", function); |
return -1; |
} |
return 0; |
} |
static inline struct usb_serial* get_usb_serial (struct usb_serial_port *port, const char *function) |
{ |
/* if no port was specified, or it fails a paranoia check */ |
if (!port || |
port_paranoia_check (port, function) || |
serial_paranoia_check (port->serial, function)) { |
/* then say that we don't have a valid usb_serial thing, which will |
* end up genrating -ENODEV return values */ |
return NULL; |
} |
return port->serial; |
} |
static inline void usb_serial_debug_data (const char *file, const char *function, int size, const unsigned char *data) |
{ |
int i; |
if (!debug) |
return; |
printk (KERN_DEBUG "%s: %s - length = %d, data = ", file, function, size); |
for (i = 0; i < size; ++i) { |
printk ("%.2x ", data[i]); |
} |
printk ("\n"); |
} |
/* Use our own dbg macro */ |
#undef dbg |
#define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG "%s: " format "\n" , __FILE__ , ## arg); } while (0) |
#endif /* ifdef __LINUX_USB_SERIAL_H */ |
/shark/trunk/drivers/usb/serial/generic.c |
---|
0,0 → 1,308 |
/* |
* USB Serial Converter Generic functions |
* |
* Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) |
* |
* This program is free software; you can redistribute it and/or |
* modify it under the terms of the GNU General Public License version |
* 2 as published by the Free Software Foundation. |
* |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/kernel.h> |
#include <linux/errno.h> |
#include <linux/slab.h> |
#include <linux/tty.h> |
#include <linux/tty_flip.h> |
#include <linux/module.h> |
#include <linux/usb.h> |
#include <asm/uaccess.h> |
#ifdef CONFIG_USB_SERIAL_DEBUG |
static int debug = 1; |
#else |
static int debug; |
#endif |
#include "usb-serial.h" |
#ifdef CONFIG_USB_SERIAL_GENERIC |
static __u16 vendor = 0x05f9; |
static __u16 product = 0xffff; |
MODULE_PARM(vendor, "h"); |
MODULE_PARM_DESC(vendor, "User specified USB idVendor"); |
MODULE_PARM(product, "h"); |
MODULE_PARM_DESC(product, "User specified USB idProduct"); |
static struct usb_device_id generic_device_ids[2]; /* Initially all zeroes. */ |
/* All of the device info needed for the Generic Serial Converter */ |
struct usb_serial_device_type usb_serial_generic_device = { |
.owner = THIS_MODULE, |
.name = "Generic", |
.short_name = "generic", |
.id_table = generic_device_ids, |
.num_interrupt_in = NUM_DONT_CARE, |
.num_bulk_in = NUM_DONT_CARE, |
.num_bulk_out = NUM_DONT_CARE, |
.num_ports = 1, |
.shutdown = usb_serial_generic_shutdown, |
}; |
#endif |
int usb_serial_generic_register (int _debug) |
{ |
int retval = 0; |
debug = _debug; |
#ifdef CONFIG_USB_SERIAL_GENERIC |
generic_device_ids[0].idVendor = vendor; |
generic_device_ids[0].idProduct = product; |
generic_device_ids[0].match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT; |
/* register our generic driver with ourselves */ |
retval = usb_serial_register (&usb_serial_generic_device); |
#endif |
return retval; |
} |
void usb_serial_generic_deregister (void) |
{ |
#ifdef CONFIG_USB_SERIAL_GENERIC |
/* remove our generic driver */ |
usb_serial_deregister (&usb_serial_generic_device); |
#endif |
} |
int usb_serial_generic_open (struct usb_serial_port *port, struct file *filp) |
{ |
struct usb_serial *serial = port->serial; |
int result = 0; |
if (port_paranoia_check (port, __FUNCTION__)) |
return -ENODEV; |
dbg("%s - port %d", __FUNCTION__, port->number); |
/* force low_latency on so that our tty_push actually forces the data through, |
otherwise it is scheduled, and with high data rates (like with OHCI) data |
can get lost. */ |
if (port->tty) |
port->tty->low_latency = 1; |
/* if we have a bulk interrupt, start reading from it */ |
if (serial->num_bulk_in) { |
/* Start reading from the device */ |
usb_fill_bulk_urb (port->read_urb, serial->dev, |
usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress), |
port->read_urb->transfer_buffer, |
port->read_urb->transfer_buffer_length, |
((serial->type->read_bulk_callback) ? |
serial->type->read_bulk_callback : |
usb_serial_generic_read_bulk_callback), |
port); |
result = usb_submit_urb(port->read_urb, GFP_KERNEL); |
if (result) |
dev_err(&port->dev, "%s - failed resubmitting read urb, error %d\n", __FUNCTION__, result); |
} |
return result; |
} |
static void generic_cleanup (struct usb_serial_port *port) |
{ |
struct usb_serial *serial = port->serial; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (serial->dev) { |
/* shutdown any bulk reads that might be going on */ |
if (serial->num_bulk_out) |
usb_unlink_urb (port->write_urb); |
if (serial->num_bulk_in) |
usb_unlink_urb (port->read_urb); |
} |
} |
void usb_serial_generic_close (struct usb_serial_port *port, struct file * filp) |
{ |
dbg("%s - port %d", __FUNCTION__, port->number); |
generic_cleanup (port); |
} |
int usb_serial_generic_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count) |
{ |
struct usb_serial *serial = port->serial; |
int result; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (count == 0) { |
dbg("%s - write request of 0 bytes", __FUNCTION__); |
return (0); |
} |
/* only do something if we have a bulk out endpoint */ |
if (serial->num_bulk_out) { |
if (port->write_urb->status == -EINPROGRESS) { |
dbg("%s - already writing", __FUNCTION__); |
return (0); |
} |
count = (count > port->bulk_out_size) ? port->bulk_out_size : count; |
if (from_user) { |
if (copy_from_user(port->write_urb->transfer_buffer, buf, count)) |
return -EFAULT; |
} |
else { |
memcpy (port->write_urb->transfer_buffer, buf, count); |
} |
usb_serial_debug_data (__FILE__, __FUNCTION__, count, port->write_urb->transfer_buffer); |
/* set up our urb */ |
usb_fill_bulk_urb (port->write_urb, serial->dev, |
usb_sndbulkpipe (serial->dev, |
port->bulk_out_endpointAddress), |
port->write_urb->transfer_buffer, count, |
((serial->type->write_bulk_callback) ? |
serial->type->write_bulk_callback : |
usb_serial_generic_write_bulk_callback), port); |
/* send the data out the bulk port */ |
result = usb_submit_urb(port->write_urb, GFP_ATOMIC); |
if (result) |
dev_err(&port->dev, "%s - failed submitting write urb, error %d\n", __FUNCTION__, result); |
else |
result = count; |
return result; |
} |
/* no bulk out, so return 0 bytes written */ |
return (0); |
} |
int usb_serial_generic_write_room (struct usb_serial_port *port) |
{ |
struct usb_serial *serial = port->serial; |
int room = 0; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (serial->num_bulk_out) { |
if (port->write_urb->status != -EINPROGRESS) |
room = port->bulk_out_size; |
} |
dbg("%s - returns %d", __FUNCTION__, room); |
return (room); |
} |
int usb_serial_generic_chars_in_buffer (struct usb_serial_port *port) |
{ |
struct usb_serial *serial = port->serial; |
int chars = 0; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (serial->num_bulk_out) { |
if (port->write_urb->status == -EINPROGRESS) |
chars = port->write_urb->transfer_buffer_length; |
} |
dbg("%s - returns %d", __FUNCTION__, chars); |
return (chars); |
} |
void usb_serial_generic_read_bulk_callback (struct urb *urb, struct pt_regs *regs) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *)urb->context; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
struct tty_struct *tty; |
unsigned char *data = urb->transfer_buffer; |
int i; |
int result; |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (!serial) { |
dbg("%s - bad serial pointer, exiting", __FUNCTION__); |
return; |
} |
if (urb->status) { |
dbg("%s - nonzero read bulk status received: %d", __FUNCTION__, urb->status); |
return; |
} |
usb_serial_debug_data (__FILE__, __FUNCTION__, urb->actual_length, data); |
tty = port->tty; |
if (tty && urb->actual_length) { |
for (i = 0; i < urb->actual_length ; ++i) { |
/* if we insert more than TTY_FLIPBUF_SIZE characters, we drop them. */ |
if(tty->flip.count >= TTY_FLIPBUF_SIZE) { |
tty_flip_buffer_push(tty); |
} |
/* this doesn't actually push the data through unless tty->low_latency is set */ |
tty_insert_flip_char(tty, data[i], 0); |
} |
tty_flip_buffer_push(tty); |
} |
/* Continue trying to always read */ |
usb_fill_bulk_urb (port->read_urb, serial->dev, |
usb_rcvbulkpipe (serial->dev, |
port->bulk_in_endpointAddress), |
port->read_urb->transfer_buffer, |
port->read_urb->transfer_buffer_length, |
((serial->type->read_bulk_callback) ? |
serial->type->read_bulk_callback : |
usb_serial_generic_read_bulk_callback), port); |
result = usb_submit_urb(port->read_urb, GFP_ATOMIC); |
if (result) |
dev_err(&port->dev, "%s - failed resubmitting read urb, error %d\n", __FUNCTION__, result); |
} |
void usb_serial_generic_write_bulk_callback (struct urb *urb, struct pt_regs *regs) |
{ |
struct usb_serial_port *port = (struct usb_serial_port *)urb->context; |
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); |
dbg("%s - port %d", __FUNCTION__, port->number); |
if (!serial) { |
dbg("%s - bad serial pointer, exiting", __FUNCTION__); |
return; |
} |
if (urb->status) { |
dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status); |
return; |
} |
usb_serial_port_softint((void *)port); |
schedule_work(&port->work); |
} |
void usb_serial_generic_shutdown (struct usb_serial *serial) |
{ |
int i; |
dbg("%s", __FUNCTION__); |
/* stop reads and writes on all ports */ |
for (i=0; i < serial->num_ports; ++i) { |
generic_cleanup(serial->port[i]); |
} |
} |
/shark/trunk/drivers/usb/include/drivers/shark_usb26.h |
---|
0,0 → 1,7 |
#ifndef __SHARK_USB26_H__ |
#define __SHARK_USB26_H__ |
int USB26_init(void); |
int USB26_close(void); |
#endif |
/shark/trunk/drivers/usb/shark_glue/shark_usb.c |
---|
0,0 → 1,56 |
#include <kernel/kern.h> |
extern int usb_init(void); |
extern void usb_exit(void); |
extern int ohci_hcd_pci_init (void); |
extern void ohci_hcd_pci_cleanup (void); |
extern int uhci_hcd_init(void); |
extern void uhci_hcd_cleanup(void); |
extern int usb_mouse_init(void); |
extern void usb_mouse_exit(void); |
extern int usb_kbd_init(void); |
extern void usb_kbd_exit(void); |
extern int hid_init(void); |
extern void hid_exit(void); |
static int usb_installed = FALSE; |
/* to do: return error code */ |
int USB26_init() |
{ |
if (usb_installed == TRUE) |
return 0; |
usb_init(); |
ohci_hcd_pci_init(); |
uhci_hcd_init(); |
usb_mouse_init(); |
usb_kbd_init(); |
hid_init(); |
usb_installed = TRUE; |
return 0; |
} |
/* to do : add all usb closing functions ?*/ |
int USB26_close() |
{ |
if (usb_installed == FALSE) |
return -1; |
ohci_hcd_pci_cleanup(); |
uhci_hcd_cleanup(); |
usb_mouse_exit(); |
usb_kbd_exit(); |
hid_exit(); |
usb_exit(); |
usb_installed = FALSE; |
return 0; |
} |
/shark/trunk/drivers/usb/core/usb.h |
---|
0,0 → 1,16 |
/* Functions local to drivers/usb/core/ */ |
extern void usb_create_driverfs_dev_files (struct usb_device *dev); |
extern void usb_create_driverfs_intf_files (struct usb_interface *intf); |
extern int usb_probe_interface (struct device *dev); |
extern int usb_unbind_interface (struct device *dev); |
extern void usb_disable_endpoint (struct usb_device *dev, unsigned int epaddr); |
extern void usb_disable_interface (struct usb_device *dev, |
struct usb_interface *intf); |
extern void usb_disable_device (struct usb_device *dev, int skip_ep0); |
extern void usb_enable_endpoint (struct usb_device *dev, |
struct usb_endpoint_descriptor *epd); |
extern void usb_enable_interface (struct usb_device *dev, |
struct usb_interface *intf); |
/shark/trunk/drivers/usb/core/hcd.c |
---|
0,0 → 1,1551 |
/* |
* (C) Copyright Linus Torvalds 1999 |
* (C) Copyright Johannes Erdfelt 1999-2001 |
* (C) Copyright Andreas Gal 1999 |
* (C) Copyright Gregory P. Smith 1999 |
* (C) Copyright Deti Fliegl 1999 |
* (C) Copyright Randy Dunlap 2000 |
* (C) Copyright David Brownell 2000-2002 |
* |
* 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. |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#ifdef CONFIG_USB_DEBUG |
#define DEBUG |
#endif |
#include <linux/module.h> |
#include <linux/version.h> |
#include <linux/kernel.h> |
#include <linux/slab.h> |
#include <linux/completion.h> |
#include <linux/uts.h> /* for UTS_SYSNAME */ |
#include <linux/pci.h> /* for hcd->pdev and dma addressing */ |
#include <linux/dma-mapping.h> |
#include <asm/byteorder.h> |
#include <linux/usb.h> |
#include "hcd.h" |
// #define USB_BANDWIDTH_MESSAGES |
/*-------------------------------------------------------------------------*/ |
/* |
* USB Host Controller Driver framework |
* |
* Plugs into usbcore (usb_bus) and lets HCDs share code, minimizing |
* HCD-specific behaviors/bugs. |
* |
* This does error checks, tracks devices and urbs, and delegates to a |
* "hc_driver" only for code (and data) that really needs to know about |
* hardware differences. That includes root hub registers, i/o queues, |
* and so on ... but as little else as possible. |
* |
* Shared code includes most of the "root hub" code (these are emulated, |
* though each HC's hardware works differently) and PCI glue, plus request |
* tracking overhead. The HCD code should only block on spinlocks or on |
* hardware handshaking; blocking on software events (such as other kernel |
* threads releasing resources, or completing actions) is all generic. |
* |
* Happens the USB 2.0 spec says this would be invisible inside the "USBD", |
* and includes mostly a "HCDI" (HCD Interface) along with some APIs used |
* only by the hub driver ... and that neither should be seen or used by |
* usb client device drivers. |
* |
* Contributors of ideas or unattributed patches include: David Brownell, |
* Roman Weissgaerber, Rory Bolt, Greg Kroah-Hartman, ... |
* |
* HISTORY: |
* 2002-02-21 Pull in most of the usb_bus support from usb.c; some |
* associated cleanup. "usb_hcd" still != "usb_bus". |
* 2001-12-12 Initial patch version for Linux 2.5.1 kernel. |
*/ |
/*-------------------------------------------------------------------------*/ |
/* host controllers we manage */ |
LIST_HEAD (usb_bus_list); |
EXPORT_SYMBOL_GPL (usb_bus_list); |
/* used when allocating bus numbers */ |
#define USB_MAXBUS 64 |
struct usb_busmap { |
unsigned long busmap [USB_MAXBUS / (8*sizeof (unsigned long))]; |
}; |
static struct usb_busmap busmap; |
/* used when updating list of hcds */ |
DECLARE_MUTEX (usb_bus_list_lock); /* exported only for usbfs */ |
EXPORT_SYMBOL_GPL (usb_bus_list_lock); |
/* used when updating hcd data */ |
static spinlock_t hcd_data_lock = SPIN_LOCK_UNLOCKED; |
/*-------------------------------------------------------------------------*/ |
/* |
* Sharable chunks of root hub code. |
*/ |
/*-------------------------------------------------------------------------*/ |
#define KERNEL_REL ((LINUX_VERSION_CODE >> 16) & 0x0ff) |
#define KERNEL_VER ((LINUX_VERSION_CODE >> 8) & 0x0ff) |
/* usb 2.0 root hub device descriptor */ |
static const u8 usb2_rh_dev_descriptor [18] = { |
0x12, /* __u8 bLength; */ |
0x01, /* __u8 bDescriptorType; Device */ |
0x00, 0x02, /* __u16 bcdUSB; v2.0 */ |
0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ |
0x00, /* __u8 bDeviceSubClass; */ |
0x01, /* __u8 bDeviceProtocol; [ usb 2.0 single TT ]*/ |
0x08, /* __u8 bMaxPacketSize0; 8 Bytes */ |
0x00, 0x00, /* __u16 idVendor; */ |
0x00, 0x00, /* __u16 idProduct; */ |
KERNEL_VER, KERNEL_REL, /* __u16 bcdDevice */ |
0x03, /* __u8 iManufacturer; */ |
0x02, /* __u8 iProduct; */ |
0x01, /* __u8 iSerialNumber; */ |
0x01 /* __u8 bNumConfigurations; */ |
}; |
/* no usb 2.0 root hub "device qualifier" descriptor: one speed only */ |
/* usb 1.1 root hub device descriptor */ |
static const u8 usb11_rh_dev_descriptor [18] = { |
0x12, /* __u8 bLength; */ |
0x01, /* __u8 bDescriptorType; Device */ |
0x10, 0x01, /* __u16 bcdUSB; v1.1 */ |
0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ |
0x00, /* __u8 bDeviceSubClass; */ |
0x00, /* __u8 bDeviceProtocol; [ low/full speeds only ] */ |
0x08, /* __u8 bMaxPacketSize0; 8 Bytes */ |
0x00, 0x00, /* __u16 idVendor; */ |
0x00, 0x00, /* __u16 idProduct; */ |
KERNEL_VER, KERNEL_REL, /* __u16 bcdDevice */ |
0x03, /* __u8 iManufacturer; */ |
0x02, /* __u8 iProduct; */ |
0x01, /* __u8 iSerialNumber; */ |
0x01 /* __u8 bNumConfigurations; */ |
}; |
/*-------------------------------------------------------------------------*/ |
/* Configuration descriptors for our root hubs */ |
static const u8 fs_rh_config_descriptor [] = { |
/* one configuration */ |
0x09, /* __u8 bLength; */ |
0x02, /* __u8 bDescriptorType; Configuration */ |
0x19, 0x00, /* __u16 wTotalLength; */ |
0x01, /* __u8 bNumInterfaces; (1) */ |
0x01, /* __u8 bConfigurationValue; */ |
0x00, /* __u8 iConfiguration; */ |
0x40, /* __u8 bmAttributes; |
Bit 7: Bus-powered, |
6: Self-powered, |
5 Remote-wakwup, |
4..0: resvd */ |
0x00, /* __u8 MaxPower; */ |
/* USB 1.1: |
* USB 2.0, single TT organization (mandatory): |
* one interface, protocol 0 |
* |
* USB 2.0, multiple TT organization (optional): |
* two interfaces, protocols 1 (like single TT) |
* and 2 (multiple TT mode) ... config is |
* sometimes settable |
* NOT IMPLEMENTED |
*/ |
/* one interface */ |
0x09, /* __u8 if_bLength; */ |
0x04, /* __u8 if_bDescriptorType; Interface */ |
0x00, /* __u8 if_bInterfaceNumber; */ |
0x00, /* __u8 if_bAlternateSetting; */ |
0x01, /* __u8 if_bNumEndpoints; */ |
0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */ |
0x00, /* __u8 if_bInterfaceSubClass; */ |
0x00, /* __u8 if_bInterfaceProtocol; [usb1.1 or single tt] */ |
0x00, /* __u8 if_iInterface; */ |
/* one endpoint (status change endpoint) */ |
0x07, /* __u8 ep_bLength; */ |
0x05, /* __u8 ep_bDescriptorType; Endpoint */ |
0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ |
0x03, /* __u8 ep_bmAttributes; Interrupt */ |
0x02, 0x00, /* __u16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) */ |
0xff /* __u8 ep_bInterval; (255ms -- usb 2.0 spec) */ |
}; |
static const u8 hs_rh_config_descriptor [] = { |
/* one configuration */ |
0x09, /* __u8 bLength; */ |
0x02, /* __u8 bDescriptorType; Configuration */ |
0x19, 0x00, /* __u16 wTotalLength; */ |
0x01, /* __u8 bNumInterfaces; (1) */ |
0x01, /* __u8 bConfigurationValue; */ |
0x00, /* __u8 iConfiguration; */ |
0x40, /* __u8 bmAttributes; |
Bit 7: Bus-powered, |
6: Self-powered, |
5 Remote-wakwup, |
4..0: resvd */ |
0x00, /* __u8 MaxPower; */ |
/* USB 1.1: |
* USB 2.0, single TT organization (mandatory): |
* one interface, protocol 0 |
* |
* USB 2.0, multiple TT organization (optional): |
* two interfaces, protocols 1 (like single TT) |
* and 2 (multiple TT mode) ... config is |
* sometimes settable |
* NOT IMPLEMENTED |
*/ |
/* one interface */ |
0x09, /* __u8 if_bLength; */ |
0x04, /* __u8 if_bDescriptorType; Interface */ |
0x00, /* __u8 if_bInterfaceNumber; */ |
0x00, /* __u8 if_bAlternateSetting; */ |
0x01, /* __u8 if_bNumEndpoints; */ |
0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */ |
0x00, /* __u8 if_bInterfaceSubClass; */ |
0x00, /* __u8 if_bInterfaceProtocol; [usb1.1 or single tt] */ |
0x00, /* __u8 if_iInterface; */ |
/* one endpoint (status change endpoint) */ |
0x07, /* __u8 ep_bLength; */ |
0x05, /* __u8 ep_bDescriptorType; Endpoint */ |
0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ |
0x03, /* __u8 ep_bmAttributes; Interrupt */ |
0x02, 0x00, /* __u16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) */ |
0x0c /* __u8 ep_bInterval; (256ms -- usb 2.0 spec) */ |
}; |
/*-------------------------------------------------------------------------*/ |
/* |
* helper routine for returning string descriptors in UTF-16LE |
* input can actually be ISO-8859-1; ASCII is its 7-bit subset |
*/ |
static int ascii2utf (char *s, u8 *utf, int utfmax) |
{ |
int retval; |
for (retval = 0; *s && utfmax > 1; utfmax -= 2, retval += 2) { |
*utf++ = *s++; |
*utf++ = 0; |
} |
return retval; |
} |
/* |
* rh_string - provides manufacturer, product and serial strings for root hub |
* @id: the string ID number (1: serial number, 2: product, 3: vendor) |
* @hcd: the host controller for this root hub |
* @type: string describing our driver |
* @data: return packet in UTF-16 LE |
* @len: length of the return packet |
* |
* Produces either a manufacturer, product or serial number string for the |
* virtual root hub device. |
*/ |
static int rh_string ( |
int id, |
struct usb_hcd *hcd, |
u8 *data, |
int len |
) { |
char buf [100]; |
// language ids |
if (id == 0) { |
*data++ = 4; *data++ = 3; /* 4 bytes string data */ |
*data++ = 0x09; *data++ = 0x04; /* MSFT-speak for "en-us" */ |
return 4; |
// serial number |
} else if (id == 1) { |
strcpy (buf, hcd->self.bus_name); |
// product description |
} else if (id == 2) { |
strcpy (buf, hcd->product_desc); |
// id 3 == vendor description |
} else if (id == 3) { |
sprintf26 (buf, "%s %s %s", UTS_SYSNAME, UTS_RELEASE, |
hcd->description); |
// unsupported IDs --> "protocol stall" |
} else |
return 0; |
data [0] = 2 * (strlen (buf) + 1); |
data [1] = 3; /* type == string */ |
return 2 + ascii2utf (buf, data + 2, len - 2); |
} |
/* Root hub control transfers execute synchronously */ |
static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) |
{ |
struct usb_ctrlrequest *cmd = (struct usb_ctrlrequest *) urb->setup_packet; |
u16 typeReq, wValue, wIndex, wLength; |
const u8 *bufp = 0; |
u8 *ubuf = urb->transfer_buffer; |
int len = 0; |
unsigned long flags; |
typeReq = (cmd->bRequestType << 8) | cmd->bRequest; |
wValue = le16_to_cpu (cmd->wValue); |
wIndex = le16_to_cpu (cmd->wIndex); |
wLength = le16_to_cpu (cmd->wLength); |
if (wLength > urb->transfer_buffer_length) |
goto error; |
/* set up for success */ |
urb->status = 0; |
urb->actual_length = wLength; |
switch (typeReq) { |
/* DEVICE REQUESTS */ |
case DeviceRequest | USB_REQ_GET_STATUS: |
// DEVICE_REMOTE_WAKEUP |
ubuf [0] = 1; // selfpowered |
ubuf [1] = 0; |
/* FALLTHROUGH */ |
case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: |
case DeviceOutRequest | USB_REQ_SET_FEATURE: |
dev_dbg (hcd->controller, "no device features yet yet\n"); |
break; |
case DeviceRequest | USB_REQ_GET_CONFIGURATION: |
ubuf [0] = 1; |
/* FALLTHROUGH */ |
case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: |
break; |
case DeviceRequest | USB_REQ_GET_DESCRIPTOR: |
switch (wValue & 0xff00) { |
case USB_DT_DEVICE << 8: |
if (hcd->driver->flags & HCD_USB2) |
bufp = usb2_rh_dev_descriptor; |
else if (hcd->driver->flags & HCD_USB11) |
bufp = usb11_rh_dev_descriptor; |
else |
goto error; |
len = 18; |
break; |
case USB_DT_CONFIG << 8: |
if (hcd->driver->flags & HCD_USB2) { |
bufp = hs_rh_config_descriptor; |
len = sizeof hs_rh_config_descriptor; |
} else { |
bufp = fs_rh_config_descriptor; |
len = sizeof fs_rh_config_descriptor; |
} |
break; |
case USB_DT_STRING << 8: |
urb->actual_length = rh_string ( |
wValue & 0xff, hcd, |
ubuf, wLength); |
break; |
default: |
goto error; |
} |
break; |
case DeviceRequest | USB_REQ_GET_INTERFACE: |
ubuf [0] = 0; |
/* FALLTHROUGH */ |
case DeviceOutRequest | USB_REQ_SET_INTERFACE: |
break; |
case DeviceOutRequest | USB_REQ_SET_ADDRESS: |
// wValue == urb->dev->devaddr |
dev_dbg (hcd->controller, "root hub device address %d\n", |
wValue); |
break; |
/* INTERFACE REQUESTS (no defined feature/status flags) */ |
/* ENDPOINT REQUESTS */ |
case EndpointRequest | USB_REQ_GET_STATUS: |
// ENDPOINT_HALT flag |
ubuf [0] = 0; |
ubuf [1] = 0; |
/* FALLTHROUGH */ |
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: |
case EndpointOutRequest | USB_REQ_SET_FEATURE: |
dev_dbg (hcd->controller, "no endpoint features yet\n"); |
break; |
/* CLASS REQUESTS (and errors) */ |
default: |
/* non-generic request */ |
urb->status = hcd->driver->hub_control (hcd, |
typeReq, wValue, wIndex, |
ubuf, wLength); |
break; |
error: |
/* "protocol stall" on error */ |
urb->status = -EPIPE; |
dev_dbg (hcd->controller, "unsupported hub control message (maxchild %d)\n", |
urb->dev->maxchild); |
} |
if (urb->status) { |
urb->actual_length = 0; |
dev_dbg (hcd->controller, "CTRL: TypeReq=0x%x val=0x%x idx=0x%x len=%d ==> %d\n", |
typeReq, wValue, wIndex, wLength, urb->status); |
} |
if (bufp) { |
if (urb->transfer_buffer_length < len) |
len = urb->transfer_buffer_length; |
urb->actual_length = len; |
// always USB_DIR_IN, toward host |
memcpy (ubuf, bufp, len); |
} |
/* any errors get returned through the urb completion */ |
local_irq_save (flags); |
usb_hcd_giveback_urb (hcd, urb, NULL); |
local_irq_restore (flags); |
return 0; |
} |
/*-------------------------------------------------------------------------*/ |
/* |
* Root Hub interrupt transfers are synthesized with a timer. |
* Completions are called in_interrupt() but not in_irq(). |
*/ |
static void rh_report_status (unsigned long ptr); |
static int rh_status_urb (struct usb_hcd *hcd, struct urb *urb) |
{ |
int len = 1 + (urb->dev->maxchild / 8); |
/* rh_timer protected by hcd_data_lock */ |
if (hcd->rh_timer.data |
|| urb->status != -EINPROGRESS |
|| urb->transfer_buffer_length < len |
|| !HCD_IS_RUNNING (hcd->state)) { |
dev_dbg (hcd->controller, |
"not queuing rh status urb, stat %d\n", |
urb->status); |
return -EINVAL; |
} |
init_timer (&hcd->rh_timer); |
hcd->rh_timer.function = rh_report_status; |
hcd->rh_timer.data = (unsigned long) urb; |
/* USB 2.0 spec says 256msec; this is close enough */ |
hcd->rh_timer.expires = jiffies26 + HZ/4; |
add_timer (&hcd->rh_timer); |
urb->hcpriv = hcd; /* nonzero to indicate it's queued */ |
return 0; |
} |
/* timer callback */ |
static void rh_report_status (unsigned long ptr) |
{ |
struct urb *urb; |
struct usb_hcd *hcd; |
int length = 0; |
unsigned long flags; |
urb = (struct urb *) ptr; |
local_irq_save (flags); |
spin_lock (&urb->lock); |
/* do nothing if the urb's been unlinked */ |
if (!urb->dev |
|| urb->status != -EINPROGRESS |
|| (hcd = urb->dev->bus->hcpriv) == 0) { |
spin_unlock (&urb->lock); |
local_irq_restore (flags); |
return; |
} |
if (!HCD_IS_SUSPENDED (hcd->state)) |
length = hcd->driver->hub_status_data ( |
hcd, urb->transfer_buffer); |
/* complete the status urb, or retrigger the timer */ |
spin_lock (&hcd_data_lock); |
if (length > 0) { |
hcd->rh_timer.data = 0; |
urb->actual_length = length; |
urb->status = 0; |
urb->hcpriv = 0; |
} else |
mod_timer (&hcd->rh_timer, jiffies26 + HZ/4); |
spin_unlock (&hcd_data_lock); |
spin_unlock (&urb->lock); |
/* local irqs are always blocked in completions */ |
if (length > 0) |
usb_hcd_giveback_urb (hcd, urb, NULL); |
local_irq_restore (flags); |
} |
/*-------------------------------------------------------------------------*/ |
static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb) |
{ |
if (usb_pipeint (urb->pipe)) { |
int retval; |
unsigned long flags; |
spin_lock_irqsave (&hcd_data_lock, flags); |
retval = rh_status_urb (hcd, urb); |
spin_unlock_irqrestore (&hcd_data_lock, flags); |
return retval; |
} |
if (usb_pipecontrol (urb->pipe)) |
return rh_call_control (hcd, urb); |
else |
return -EINVAL; |
} |
/*-------------------------------------------------------------------------*/ |
void usb_rh_status_dequeue (struct usb_hcd *hcd, struct urb *urb) |
{ |
unsigned long flags; |
/* note: always a synchronous unlink */ |
del_timer_sync (&hcd->rh_timer); |
hcd->rh_timer.data = 0; |
local_irq_save (flags); |
urb->hcpriv = 0; |
usb_hcd_giveback_urb (hcd, urb, NULL); |
local_irq_restore (flags); |
} |
/*-------------------------------------------------------------------------*/ |
/* exported only within usbcore */ |
struct usb_bus *usb_bus_get (struct usb_bus *bus) |
{ |
struct class_device *tmp; |
if (!bus) |
return NULL; |
tmp = class_device_get(&bus->class_dev); |
if (tmp) |
return to_usb_bus(tmp); |
else |
return NULL; |
} |
/* exported only within usbcore */ |
void usb_bus_put (struct usb_bus *bus) |
{ |
if (bus) |
class_device_put(&bus->class_dev); |
} |
/*-------------------------------------------------------------------------*/ |
static void usb_host_release(struct class_device *class_dev) |
{ |
struct usb_bus *bus = to_usb_bus(class_dev); |
if (bus->release) |
bus->release(bus); |
} |
static struct class usb_host_class = { |
.name = "usb_host", |
.release = &usb_host_release, |
}; |
void usb_host_init(void) |
{ |
class_register(&usb_host_class); |
} |
void usb_host_cleanup(void) |
{ |
class_unregister(&usb_host_class); |
} |
/** |
* usb_bus_init - shared initialization code |
* @bus: the bus structure being initialized |
* |
* This code is used to initialize a usb_bus structure, memory for which is |
* separately managed. |
*/ |
void usb_bus_init (struct usb_bus *bus) |
{ |
memset (&bus->devmap, 0, sizeof(struct usb_devmap)); |
bus->devnum_next = 1; |
bus->root_hub = NULL; |
bus->hcpriv = NULL; |
bus->busnum = -1; |
bus->bandwidth_allocated = 0; |
bus->bandwidth_int_reqs = 0; |
bus->bandwidth_isoc_reqs = 0; |
INIT_LIST_HEAD (&bus->bus_list); |
} |
EXPORT_SYMBOL (usb_bus_init); |
/** |
* usb_alloc_bus - creates a new USB host controller structure |
* @op: pointer to a struct usb_operations that this bus structure should use |
* Context: !in_interrupt() |
* |
* Creates a USB host controller bus structure with the specified |
* usb_operations and initializes all the necessary internal objects. |
* |
* If no memory is available, NULL is returned. |
* |
* The caller should call usb_put_bus() when it is finished with the structure. |
*/ |
struct usb_bus *usb_alloc_bus (struct usb_operations *op) |
{ |
struct usb_bus *bus; |
bus = kmalloc (sizeof *bus, GFP_KERNEL); |
if (!bus) |
return NULL; |
memset(bus, 0, sizeof(struct usb_bus)); |
usb_bus_init (bus); |
bus->op = op; |
return bus; |
} |
EXPORT_SYMBOL (usb_alloc_bus); |
/*-------------------------------------------------------------------------*/ |
/** |
* usb_register_bus - registers the USB host controller with the usb core |
* @bus: pointer to the bus to register |
* Context: !in_interrupt() |
* |
* Assigns a bus number, and links the controller into usbcore data |
* structures so that it can be seen by scanning the bus list. |
*/ |
int usb_register_bus(struct usb_bus *bus) |
{ |
int busnum; |
int retval; |
down (&usb_bus_list_lock); |
busnum = find_next_zero_bit (busmap.busmap, USB_MAXBUS, 1); |
if (busnum < USB_MAXBUS) { |
set_bit (busnum, busmap.busmap); |
bus->busnum = busnum; |
} else |
warn ("too many buses"); |
snprintf26(bus->class_dev.class_id, BUS_ID_SIZE, "usb%d", busnum); |
bus->class_dev.class = &usb_host_class; |
bus->class_dev.dev = bus->controller; |
retval = class_device_register(&bus->class_dev); |
if (retval) { |
clear_bit(busnum, busmap.busmap); |
up(&usb_bus_list_lock); |
return retval; |
} |
/* Add it to the local list of buses */ |
list_add (&bus->bus_list, &usb_bus_list); |
up (&usb_bus_list_lock); |
usbfs_add_bus (bus); |
dev_info (bus->controller, "new USB bus registered, assigned bus number %d\n", bus->busnum); |
return 0; |
} |
EXPORT_SYMBOL (usb_register_bus); |
/** |
* usb_deregister_bus - deregisters the USB host controller |
* @bus: pointer to the bus to deregister |
* Context: !in_interrupt() |
* |
* Recycles the bus number, and unlinks the controller from usbcore data |
* structures so that it won't be seen by scanning the bus list. |
*/ |
void usb_deregister_bus (struct usb_bus *bus) |
{ |
dev_info (bus->controller, "USB bus %d deregistered\n", bus->busnum); |
/* |
* NOTE: make sure that all the devices are removed by the |
* controller code, as well as having it call this when cleaning |
* itself up |
*/ |
down (&usb_bus_list_lock); |
list_del (&bus->bus_list); |
up (&usb_bus_list_lock); |
usbfs_remove_bus (bus); |
clear_bit (bus->busnum, busmap.busmap); |
class_device_unregister(&bus->class_dev); |
} |
EXPORT_SYMBOL (usb_deregister_bus); |
/** |
* usb_register_root_hub - called by HCD to register its root hub |
* @usb_dev: the usb root hub device to be registered. |
* @parent_dev: the parent device of this root hub. |
* |
* The USB host controller calls this function to register the root hub |
* properly with the USB subsystem. It sets up the device properly in |
* the driverfs tree, and then calls usb_new_device() to register the |
* usb device. It also assigns the root hub's USB address (always 1). |
*/ |
int usb_register_root_hub (struct usb_device *usb_dev, struct device *parent_dev) |
{ |
const int devnum = 1; |
int retval; |
sprintf26 (&usb_dev->dev.bus_id[0], "usb%d", usb_dev->bus->busnum); |
usb_dev->state = USB_STATE_DEFAULT; |
usb_dev->devnum = devnum; |
usb_dev->bus->devnum_next = devnum + 1; |
set_bit (devnum, usb_dev->bus->devmap.devicemap); |
retval = usb_new_device (usb_dev, parent_dev); |
if (retval) |
dev_err (parent_dev, "can't register root hub for %s, %d\n", |
usb_dev->dev.bus_id, retval); |
return retval; |
} |
EXPORT_SYMBOL (usb_register_root_hub); |
/*-------------------------------------------------------------------------*/ |
/** |
* usb_calc_bus_time - approximate periodic transaction time in nanoseconds |
* @speed: from dev->speed; USB_SPEED_{LOW,FULL,HIGH} |
* @is_input: true iff the transaction sends data to the host |
* @isoc: true for isochronous transactions, false for interrupt ones |
* @bytecount: how many bytes in the transaction. |
* |
* Returns approximate bus time in nanoseconds for a periodic transaction. |
* See USB 2.0 spec section 5.11.3; only periodic transfers need to be |
* scheduled in software, this function is only used for such scheduling. |
*/ |
long usb_calc_bus_time (int speed, int is_input, int isoc, int bytecount) |
{ |
unsigned long tmp; |
switch (speed) { |
case USB_SPEED_LOW: /* INTR only */ |
if (is_input) { |
tmp = (67667L * (31L + 10L * BitTime (bytecount))) / 1000L; |
return (64060L + (2 * BW_HUB_LS_SETUP) + BW_HOST_DELAY + tmp); |
} else { |
tmp = (66700L * (31L + 10L * BitTime (bytecount))) / 1000L; |
return (64107L + (2 * BW_HUB_LS_SETUP) + BW_HOST_DELAY + tmp); |
} |
case USB_SPEED_FULL: /* ISOC or INTR */ |
if (isoc) { |
tmp = (8354L * (31L + 10L * BitTime (bytecount))) / 1000L; |
return (((is_input) ? 7268L : 6265L) + BW_HOST_DELAY + tmp); |
} else { |
tmp = (8354L * (31L + 10L * BitTime (bytecount))) / 1000L; |
return (9107L + BW_HOST_DELAY + tmp); |
} |
case USB_SPEED_HIGH: /* ISOC or INTR */ |
// FIXME adjust for input vs output |
if (isoc) |
tmp = HS_USECS (bytecount); |
else |
tmp = HS_USECS_ISO (bytecount); |
return tmp; |
default: |
dbg ("bogus device speed!"); |
return -1; |
} |
} |
EXPORT_SYMBOL (usb_calc_bus_time); |
/* |
* usb_check_bandwidth(): |
* |
* old_alloc is from host_controller->bandwidth_allocated in microseconds; |
* bustime is from calc_bus_time(), but converted to microseconds. |
* |
* returns <bustime in us> if successful, |
* or -ENOSPC if bandwidth request fails. |
* |
* FIXME: |
* This initial implementation does not use Endpoint.bInterval |
* in managing bandwidth allocation. |
* It probably needs to be expanded to use Endpoint.bInterval. |
* This can be done as a later enhancement (correction). |
* |
* This will also probably require some kind of |
* frame allocation tracking...meaning, for example, |
* that if multiple drivers request interrupts every 10 USB frames, |
* they don't all have to be allocated at |
* frame numbers N, N+10, N+20, etc. Some of them could be at |
* N+11, N+21, N+31, etc., and others at |
* N+12, N+22, N+32, etc. |
* |
* Similarly for isochronous transfers... |
* |
* Individual HCDs can schedule more directly ... this logic |
* is not correct for high speed transfers. |
*/ |
int usb_check_bandwidth (struct usb_device *dev, struct urb *urb) |
{ |
unsigned int pipe = urb->pipe; |
long bustime; |
int is_in = usb_pipein (pipe); |
int is_iso = usb_pipeisoc (pipe); |
int old_alloc = dev->bus->bandwidth_allocated; |
int new_alloc; |
bustime = NS_TO_US (usb_calc_bus_time (dev->speed, is_in, is_iso, |
usb_maxpacket (dev, pipe, !is_in))); |
if (is_iso) |
bustime /= urb->number_of_packets; |
new_alloc = old_alloc + (int) bustime; |
if (new_alloc > FRAME_TIME_MAX_USECS_ALLOC) { |
#ifdef DEBUG |
char *mode = |
#ifdef CONFIG_USB_BANDWIDTH |
""; |
#else |
"would have "; |
#endif |
dev_dbg (&dev->dev, "usb_check_bandwidth %sFAILED: %d + %ld = %d usec\n", |
mode, old_alloc, bustime, new_alloc); |
#endif |
#ifdef CONFIG_USB_BANDWIDTH |
bustime = -ENOSPC; /* report error */ |
#endif |
} |
return bustime; |
} |
EXPORT_SYMBOL (usb_check_bandwidth); |
/** |
* usb_claim_bandwidth - records bandwidth for a periodic transfer |
* @dev: source/target of request |
* @urb: request (urb->dev == dev) |
* @bustime: bandwidth consumed, in (average) microseconds per frame |
* @isoc: true iff the request is isochronous |
* |
* Bus bandwidth reservations are recorded purely for diagnostic purposes. |
* HCDs are expected not to overcommit periodic bandwidth, and to record such |
* reservations whenever endpoints are added to the periodic schedule. |
* |
* FIXME averaging per-frame is suboptimal. Better to sum over the HCD's |
* entire periodic schedule ... 32 frames for OHCI, 1024 for UHCI, settable |
* for EHCI (256/512/1024 frames, default 1024) and have the bus expose how |
* large its periodic schedule is. |
*/ |
void usb_claim_bandwidth (struct usb_device *dev, struct urb *urb, int bustime, int isoc) |
{ |
dev->bus->bandwidth_allocated += bustime; |
if (isoc) |
dev->bus->bandwidth_isoc_reqs++; |
else |
dev->bus->bandwidth_int_reqs++; |
urb->bandwidth = bustime; |
#ifdef USB_BANDWIDTH_MESSAGES |
dev_dbg (&dev->dev, "bandwidth alloc increased by %d (%s) to %d for %d requesters\n", |
bustime, |
isoc ? "ISOC" : "INTR", |
dev->bus->bandwidth_allocated, |
dev->bus->bandwidth_int_reqs + dev->bus->bandwidth_isoc_reqs); |
#endif |
} |
EXPORT_SYMBOL (usb_claim_bandwidth); |
/** |
* usb_release_bandwidth - reverses effect of usb_claim_bandwidth() |
* @dev: source/target of request |
* @urb: request (urb->dev == dev) |
* @isoc: true iff the request is isochronous |
* |
* This records that previously allocated bandwidth has been released. |
* Bandwidth is released when endpoints are removed from the host controller's |
* periodic schedule. |
*/ |
void usb_release_bandwidth (struct usb_device *dev, struct urb *urb, int isoc) |
{ |
dev->bus->bandwidth_allocated -= urb->bandwidth; |
if (isoc) |
dev->bus->bandwidth_isoc_reqs--; |
else |
dev->bus->bandwidth_int_reqs--; |
#ifdef USB_BANDWIDTH_MESSAGES |
dev_dbg (&dev->dev, "bandwidth alloc reduced by %d (%s) to %d for %d requesters\n", |
urb->bandwidth, |
isoc ? "ISOC" : "INTR", |
dev->bus->bandwidth_allocated, |
dev->bus->bandwidth_int_reqs + dev->bus->bandwidth_isoc_reqs); |
#endif |
urb->bandwidth = 0; |
} |
EXPORT_SYMBOL (usb_release_bandwidth); |
/*-------------------------------------------------------------------------*/ |
/* |
* Generic HC operations. |
*/ |
/*-------------------------------------------------------------------------*/ |
/* called from khubd, or root hub init threads for hcd-private init */ |
static int hcd_alloc_dev (struct usb_device *udev) |
{ |
struct hcd_dev *dev; |
struct usb_hcd *hcd; |
unsigned long flags; |
if (!udev || udev->hcpriv) |
return -EINVAL; |
if (!udev->bus || !udev->bus->hcpriv) |
return -ENODEV; |
hcd = udev->bus->hcpriv; |
if (hcd->state == USB_STATE_QUIESCING) |
return -ENOLINK; |
dev = (struct hcd_dev *) kmalloc (sizeof *dev, GFP_KERNEL); |
if (dev == NULL) |
return -ENOMEM; |
memset (dev, 0, sizeof *dev); |
INIT_LIST_HEAD (&dev->dev_list); |
INIT_LIST_HEAD (&dev->urb_list); |
spin_lock_irqsave (&hcd_data_lock, flags); |
list_add (&dev->dev_list, &hcd->dev_list); |
// refcount is implicit |
udev->hcpriv = dev; |
spin_unlock_irqrestore (&hcd_data_lock, flags); |
return 0; |
} |
/*-------------------------------------------------------------------------*/ |
static void urb_unlink (struct urb *urb) |
{ |
unsigned long flags; |
struct usb_device *dev; |
/* Release any periodic transfer bandwidth */ |
if (urb->bandwidth) |
usb_release_bandwidth (urb->dev, urb, |
usb_pipeisoc (urb->pipe)); |
/* clear all state linking urb to this dev (and hcd) */ |
spin_lock_irqsave (&hcd_data_lock, flags); |
list_del_init (&urb->urb_list); |
dev = urb->dev; |
spin_unlock_irqrestore (&hcd_data_lock, flags); |
usb_put_dev (dev); |
} |
/* may be called in any context with a valid urb->dev usecount |
* caller surrenders "ownership" of urb |
* expects usb_submit_urb() to have sanity checked and conditioned all |
* inputs in the urb |
*/ |
static int hcd_submit_urb (struct urb *urb, int mem_flags) |
{ |
int status; |
struct usb_hcd *hcd = urb->dev->bus->hcpriv; |
struct hcd_dev *dev = urb->dev->hcpriv; |
unsigned long flags; |
if (!hcd || !dev) |
return -ENODEV; |
/* |
* FIXME: make urb timeouts be generic, keeping the HCD cores |
* as simple as possible. |
*/ |
// NOTE: a generic device/urb monitoring hook would go here. |
// hcd_monitor_hook(MONITOR_URB_SUBMIT, urb) |
// It would catch submission paths for all urbs. |
/* |
* Atomically queue the urb, first to our records, then to the HCD. |
* Access to urb->status is controlled by urb->lock ... changes on |
* i/o completion (normal or fault) or unlinking. |
*/ |
// FIXME: verify that quiescing hc works right (RH cleans up) |
spin_lock_irqsave (&hcd_data_lock, flags); |
if (HCD_IS_RUNNING (hcd->state) && hcd->state != USB_STATE_QUIESCING) { |
usb_get_dev (urb->dev); |
list_add_tail (&urb->urb_list, &dev->urb_list); |
status = 0; |
} else { |
INIT_LIST_HEAD (&urb->urb_list); |
status = -ESHUTDOWN; |
} |
spin_unlock_irqrestore (&hcd_data_lock, flags); |
if (status) |
return status; |
/* increment urb's reference count as part of giving it to the HCD |
* (which now controls it). HCD guarantees that it either returns |
* an error or calls giveback(), but not both. |
*/ |
urb = usb_get_urb (urb); |
if (urb->dev == hcd->self.root_hub) { |
/* NOTE: requirement on hub callers (usbfs and the hub |
* driver, for now) that URBs' urb->transfer_buffer be |
* valid and usb_buffer_{sync,unmap}() not be needed, since |
* they could clobber root hub response data. |
*/ |
urb->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP |
| URB_NO_SETUP_DMA_MAP); |
status = rh_urb_enqueue (hcd, urb); |
goto done; |
} |
/* lower level hcd code should use *_dma exclusively, |
* unless it uses pio or talks to another transport. |
*/ |
if (hcd->controller->dma_mask) { |
if (usb_pipecontrol (urb->pipe) |
&& !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) |
urb->setup_dma = dma_map_single ( |
hcd->controller, |
urb->setup_packet, |
sizeof (struct usb_ctrlrequest), |
DMA_TO_DEVICE); |
if (urb->transfer_buffer_length != 0 |
&& !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) |
urb->transfer_dma = dma_map_single ( |
hcd->controller, |
urb->transfer_buffer, |
urb->transfer_buffer_length, |
usb_pipein (urb->pipe) |
? DMA_FROM_DEVICE |
: DMA_TO_DEVICE); |
} |
status = hcd->driver->urb_enqueue (hcd, urb, mem_flags); |
done: |
if (status) { |
usb_put_urb (urb); |
urb_unlink (urb); |
} |
return status; |
} |
/*-------------------------------------------------------------------------*/ |
/* called in any context */ |
static int hcd_get_frame_number (struct usb_device *udev) |
{ |
struct usb_hcd *hcd = (struct usb_hcd *)udev->bus->hcpriv; |
if (!HCD_IS_RUNNING (hcd->state)) |
return -ESHUTDOWN; |
return hcd->driver->get_frame_number (hcd); |
} |
/*-------------------------------------------------------------------------*/ |
/* this makes the hcd giveback() the urb more quickly, by kicking it |
* off hardware queues (which may take a while) and returning it as |
* soon as practical. we've already set up the urb's return status, |
* but we can't know if the callback completed already. |
*/ |
static void |
unlink1 (struct usb_hcd *hcd, struct urb *urb) |
{ |
if (urb == (struct urb *) hcd->rh_timer.data) |
usb_rh_status_dequeue (hcd, urb); |
else { |
int value; |
/* failures "should" be harmless */ |
value = hcd->driver->urb_dequeue (hcd, urb); |
if (value != 0) |
dev_dbg (hcd->controller, |
"dequeue %p --> %d\n", |
urb, value); |
} |
} |
struct completion_splice { // modified urb context: |
/* did we complete? */ |
struct completion done; |
/* original urb data */ |
usb_complete_t complete; |
void *context; |
}; |
static void unlink_complete (struct urb *urb, struct pt_regs *regs) |
{ |
struct completion_splice *splice; |
splice = (struct completion_splice *) urb->context; |
/* issue original completion call */ |
urb->complete = splice->complete; |
urb->context = splice->context; |
urb->complete (urb, regs); |
/* then let the synchronous unlink call complete */ |
complete (&splice->done); |
} |
/* |
* called in any context; note ASYNC_UNLINK restrictions |
* |
* caller guarantees urb won't be recycled till both unlink() |
* and the urb's completion function return |
*/ |
static int hcd_unlink_urb (struct urb *urb) |
{ |
struct hcd_dev *dev; |
struct usb_hcd *hcd = 0; |
struct device *sys = 0; |
unsigned long flags; |
struct completion_splice splice; |
int retval; |
if (!urb) |
return -EINVAL; |
/* |
* we contend for urb->status with the hcd core, |
* which changes it while returning the urb. |
* |
* Caller guaranteed that the urb pointer hasn't been freed, and |
* that it was submitted. But as a rule it can't know whether or |
* not it's already been unlinked ... so we respect the reversed |
* lock sequence needed for the usb_hcd_giveback_urb() code paths |
* (urb lock, then hcd_data_lock) in case some other CPU is now |
* unlinking it. |
*/ |
spin_lock_irqsave (&urb->lock, flags); |
spin_lock (&hcd_data_lock); |
if (!urb->dev || !urb->dev->bus) { |
retval = -ENODEV; |
goto done; |
} |
dev = urb->dev->hcpriv; |
sys = &urb->dev->dev; |
hcd = urb->dev->bus->hcpriv; |
if (!dev || !hcd) { |
retval = -ENODEV; |
goto done; |
} |
/* running ~= hc unlink handshake works (irq, timer, etc) |
* halted ~= no unlink handshake is needed |
* suspended, resuming == should never happen |
*/ |
WARN_ON (!HCD_IS_RUNNING (hcd->state) && hcd->state != USB_STATE_HALT); |
if (!urb->hcpriv) { |
retval = -EINVAL; |
goto done; |
} |
/* Any status except -EINPROGRESS means something already started to |
* unlink this URB from the hardware. So there's no more work to do. |
* |
* FIXME use better explicit urb state |
*/ |
if (urb->status != -EINPROGRESS) { |
retval = -EBUSY; |
goto done; |
} |
/* PCI IRQ setup can easily be broken so that USB controllers |
* never get completion IRQs ... maybe even the ones we need to |
* finish unlinking the initial failed usb_set_address(). |
*/ |
if (!hcd->saw_irq) { |
dev_warn (hcd->controller, "Unlink after no-IRQ? " |
"Different ACPI or APIC settings may help." |
"\n"); |
hcd->saw_irq = 1; |
} |
/* maybe set up to block until the urb's completion fires. the |
* lower level hcd code is always async, locking on urb->status |
* updates; an intercepted completion unblocks us. |
*/ |
if (!(urb->transfer_flags & URB_ASYNC_UNLINK)) { |
if (in_interrupt ()) { |
dev_dbg (hcd->controller, "non-async unlink in_interrupt"); |
retval = -EWOULDBLOCK; |
goto done; |
} |
/* synchronous unlink: block till we see the completion */ |
init_completion (&splice.done); |
splice.complete = urb->complete; |
splice.context = urb->context; |
urb->complete = unlink_complete; |
urb->context = &splice; |
urb->status = -ENOENT; |
} else { |
/* asynchronous unlink */ |
urb->status = -ECONNRESET; |
} |
spin_unlock (&hcd_data_lock); |
spin_unlock_irqrestore (&urb->lock, flags); |
// FIXME remove splicing, so this becomes unlink1 (hcd, urb); |
if (urb == (struct urb *) hcd->rh_timer.data) { |
usb_rh_status_dequeue (hcd, urb); |
retval = 0; |
} else { |
retval = hcd->driver->urb_dequeue (hcd, urb); |
/* hcds shouldn't really fail these calls, but... */ |
if (retval) { |
dev_dbg (sys, "dequeue %p --> %d\n", urb, retval); |
if (!(urb->transfer_flags & URB_ASYNC_UNLINK)) { |
spin_lock_irqsave (&urb->lock, flags); |
urb->complete = splice.complete; |
urb->context = splice.context; |
spin_unlock_irqrestore (&urb->lock, flags); |
} |
goto bye; |
} |
} |
/* block till giveback, if needed */ |
if (urb->transfer_flags & URB_ASYNC_UNLINK) |
return -EINPROGRESS; |
wait_for_completion (&splice.done); |
return 0; |
done: |
spin_unlock (&hcd_data_lock); |
spin_unlock_irqrestore (&urb->lock, flags); |
bye: |
if (retval && sys && sys->driver) |
dev_dbg (sys, "hcd_unlink_urb %p fail %d\n", urb, retval); |
return retval; |
} |
/*-------------------------------------------------------------------------*/ |
/* disables the endpoint: cancels any pending urbs, then synchronizes with |
* the hcd to make sure all endpoint state is gone from hardware. use for |
* set_configuration, set_interface, driver removal, physical disconnect. |
* |
* example: a qh stored in hcd_dev.ep[], holding state related to endpoint |
* type, maxpacket size, toggle, halt status, and scheduling. |
*/ |
static void hcd_endpoint_disable (struct usb_device *udev, int endpoint) |
{ |
struct hcd_dev *dev; |
struct usb_hcd *hcd; |
struct urb *urb; |
unsigned epnum = endpoint & USB_ENDPOINT_NUMBER_MASK; |
dev = udev->hcpriv; |
hcd = udev->bus->hcpriv; |
WARN_ON (!HCD_IS_RUNNING (hcd->state) && hcd->state != USB_STATE_HALT); |
local_irq_disable (); |
rescan: |
/* (re)block new requests, as best we can */ |
if (endpoint & USB_DIR_IN) { |
usb_endpoint_halt (udev, epnum, 0); |
udev->epmaxpacketin [epnum] = 0; |
} else { |
usb_endpoint_halt (udev, epnum, 1); |
udev->epmaxpacketout [epnum] = 0; |
} |
/* then kill any current requests */ |
spin_lock (&hcd_data_lock); |
list_for_each_entry (urb, &dev->urb_list, urb_list) { |
int tmp = urb->pipe; |
/* ignore urbs for other endpoints */ |
if (usb_pipeendpoint (tmp) != epnum) |
continue; |
/* NOTE assumption that only ep0 is a control endpoint */ |
if (epnum != 0 && ((tmp ^ endpoint) & USB_DIR_IN)) |
continue; |
/* another cpu may be in hcd, spinning on hcd_data_lock |
* to giveback() this urb. the races here should be |
* small, but a full fix needs a new "can't submit" |
* urb state. |
*/ |
if (urb->status != -EINPROGRESS) |
continue; |
usb_get_urb (urb); |
spin_unlock (&hcd_data_lock); |
spin_lock (&urb->lock); |
tmp = urb->status; |
if (tmp == -EINPROGRESS) |
urb->status = -ESHUTDOWN; |
spin_unlock (&urb->lock); |
/* kick hcd unless it's already returning this */ |
if (tmp == -EINPROGRESS) { |
tmp = urb->pipe; |
unlink1 (hcd, urb); |
dev_dbg (hcd->controller, |
"shutdown urb %p pipe %08x ep%d%s%s\n", |
urb, tmp, usb_pipeendpoint (tmp), |
(tmp & USB_DIR_IN) ? "in" : "out", |
({ char *s; \ |
switch (usb_pipetype (tmp)) { \ |
case PIPE_CONTROL: s = ""; break; \ |
case PIPE_BULK: s = "-bulk"; break; \ |
case PIPE_INTERRUPT: s = "-intr"; break; \ |
default: s = "-iso"; break; \ |
}; s;})); |
} |
usb_put_urb (urb); |
/* list contents may have changed */ |
goto rescan; |
} |
spin_unlock (&hcd_data_lock); |
local_irq_enable (); |
/* synchronize with the hardware, so old configuration state |
* clears out immediately (and will be freed). |
*/ |
might_sleep (); |
if (hcd->driver->endpoint_disable) |
hcd->driver->endpoint_disable (hcd, dev, endpoint); |
} |
/*-------------------------------------------------------------------------*/ |
/* called by khubd, rmmod, apmd, or other thread for hcd-private cleanup. |
* we're guaranteed that the device is fully quiesced. also, that each |
* endpoint has been hcd_endpoint_disabled. |
*/ |
static int hcd_free_dev (struct usb_device *udev) |
{ |
struct hcd_dev *dev; |
struct usb_hcd *hcd; |
unsigned long flags; |
if (!udev || !udev->hcpriv) |
return -EINVAL; |
if (!udev->bus || !udev->bus->hcpriv) |
return -ENODEV; |
// should udev->devnum == -1 ?? |
dev = udev->hcpriv; |
hcd = udev->bus->hcpriv; |
/* device driver problem with refcounts? */ |
if (!list_empty (&dev->urb_list)) { |
dev_dbg (hcd->controller, "free busy dev, %s devnum %d (bug!)\n", |
hcd->self.bus_name, udev->devnum); |
return -EINVAL; |
} |
spin_lock_irqsave (&hcd_data_lock, flags); |
list_del (&dev->dev_list); |
udev->hcpriv = NULL; |
spin_unlock_irqrestore (&hcd_data_lock, flags); |
kfree (dev); |
return 0; |
} |
/* |
* usb_hcd_operations - adapts usb_bus framework to HCD framework (bus glue) |
* |
* When registering a USB bus through the HCD framework code, use this |
* usb_operations vector. The PCI glue layer does so automatically; only |
* bus glue for non-PCI system busses will need to use this. |
*/ |
struct usb_operations usb_hcd_operations = { |
.allocate = hcd_alloc_dev, |
.get_frame_number = hcd_get_frame_number, |
.submit_urb = hcd_submit_urb, |
.unlink_urb = hcd_unlink_urb, |
.deallocate = hcd_free_dev, |
.buffer_alloc = hcd_buffer_alloc, |
.buffer_free = hcd_buffer_free, |
.disable = hcd_endpoint_disable, |
}; |
EXPORT_SYMBOL (usb_hcd_operations); |
/*-------------------------------------------------------------------------*/ |
/** |
* usb_hcd_giveback_urb - return URB from HCD to device driver |
* @hcd: host controller returning the URB |
* @urb: urb being returned to the USB device driver. |
* @regs: pt_regs, passed down to the URB completion handler |
* Context: in_interrupt() |
* |
* This hands the URB from HCD to its USB device driver, using its |
* completion function. The HCD has freed all per-urb resources |
* (and is done using urb->hcpriv). It also released all HCD locks; |
* the device driver won't cause problems if it frees, modifies, |
* or resubmits this URB. |
*/ |
void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs) |
{ |
urb_unlink (urb); |
// NOTE: a generic device/urb monitoring hook would go here. |
// hcd_monitor_hook(MONITOR_URB_FINISH, urb, dev) |
// It would catch exit/unlink paths for all urbs. |
/* lower level hcd code should use *_dma exclusively */ |
if (hcd->controller->dma_mask) { |
if (usb_pipecontrol (urb->pipe) |
&& !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) |
pci_unmap_single (hcd->pdev, urb->setup_dma, |
sizeof (struct usb_ctrlrequest), |
PCI_DMA_TODEVICE); |
if (urb->transfer_buffer_length != 0 |
&& !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) |
pci_unmap_single (hcd->pdev, urb->transfer_dma, |
urb->transfer_buffer_length, |
usb_pipein (urb->pipe) |
? PCI_DMA_FROMDEVICE |
: PCI_DMA_TODEVICE); |
} |
/* pass ownership to the completion handler */ |
urb->complete (urb, regs); |
usb_put_urb (urb); |
} |
EXPORT_SYMBOL (usb_hcd_giveback_urb); |
/*-------------------------------------------------------------------------*/ |
/** |
* usb_hcd_irq - hook IRQs to HCD framework (bus glue) |
* @irq: the IRQ being raised |
* @__hcd: pointer to the HCD whose IRQ is beinng signaled |
* @r: saved hardware registers |
* |
* When registering a USB bus through the HCD framework code, use this |
* to handle interrupts. The PCI glue layer does so automatically; only |
* bus glue for non-PCI system busses will need to use this. |
*/ |
irqreturn_t usb_hcd_irq (int irq, void *__hcd, struct pt_regs * r) |
{ |
struct usb_hcd *hcd = __hcd; |
int start = hcd->state; |
if (/*unlikely*/ (hcd->state == USB_STATE_HALT)) /* irq sharing? */ |
return IRQ_NONE; |
hcd->saw_irq = 1; |
hcd->driver->irq (hcd, r); |
if (hcd->state != start && hcd->state == USB_STATE_HALT) |
usb_hc_died (hcd); |
return IRQ_HANDLED; |
} |
EXPORT_SYMBOL (usb_hcd_irq); |
/*-------------------------------------------------------------------------*/ |
static void hcd_panic (void *_hcd) |
{ |
struct usb_hcd *hcd = _hcd; |
struct usb_device *hub = hcd->self.root_hub; |
unsigned i; |
/* hc's root hub is removed later removed in hcd->stop() */ |
hub->state = USB_STATE_NOTATTACHED; |
for (i = 0; i < hub->maxchild; i++) { |
if (hub->children [i]) |
usb_disconnect (&hub->children [i]); |
} |
} |
/** |
* usb_hc_died - report abnormal shutdown of a host controller (bus glue) |
* @hcd: pointer to the HCD representing the controller |
* |
* This is called by bus glue to report a USB host controller that died |
* while operations may still have been pending. It's called automatically |
* by the PCI glue, so only glue for non-PCI busses should need to call it. |
*/ |
void usb_hc_died (struct usb_hcd *hcd) |
{ |
dev_err (hcd->controller, "HC died; cleaning up\n"); |
/* clean up old urbs and devices; needs a task context */ |
INIT_WORK (&hcd->work, hcd_panic, hcd); |
(void) schedule_work (&hcd->work); |
} |
EXPORT_SYMBOL (usb_hc_died); |
/shark/trunk/drivers/usb/core/hcd.h |
---|
0,0 → 1,424 |
/* |
* Copyright (c) 2001-2002 by David Brownell |
* |
* 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. |
*/ |
#ifdef __KERNEL__ |
/* This file contains declarations of usbcore internals that are mostly |
* used or exposed by Host Controller Drivers. |
*/ |
/* |
* USB Packet IDs (PIDs) |
*/ |
#define USB_PID_UNDEF_0 0xf0 |
#define USB_PID_OUT 0xe1 |
#define USB_PID_ACK 0xd2 |
#define USB_PID_DATA0 0xc3 |
#define USB_PID_PING 0xb4 /* USB 2.0 */ |
#define USB_PID_SOF 0xa5 |
#define USB_PID_NYET 0x96 /* USB 2.0 */ |
#define USB_PID_DATA2 0x87 /* USB 2.0 */ |
#define USB_PID_SPLIT 0x78 /* USB 2.0 */ |
#define USB_PID_IN 0x69 |
#define USB_PID_NAK 0x5a |
#define USB_PID_DATA1 0x4b |
#define USB_PID_PREAMBLE 0x3c /* Token mode */ |
#define USB_PID_ERR 0x3c /* USB 2.0: handshake mode */ |
#define USB_PID_SETUP 0x2d |
#define USB_PID_STALL 0x1e |
#define USB_PID_MDATA 0x0f /* USB 2.0 */ |
/*-------------------------------------------------------------------------*/ |
/* |
* USB Host Controller Driver (usb_hcd) framework |
* |
* Since "struct usb_bus" is so thin, you can't share much code in it. |
* This framework is a layer over that, and should be more sharable. |
*/ |
/*-------------------------------------------------------------------------*/ |
struct usb_hcd { /* usb_bus.hcpriv points to this */ |
/* |
* housekeeping |
*/ |
struct usb_bus self; /* hcd is-a bus */ |
const char *product_desc; /* product/vendor string */ |
const char *description; /* "ehci-hcd" etc */ |
struct timer_list rh_timer; /* drives root hub */ |
struct list_head dev_list; /* devices on this bus */ |
struct work_struct work; |
/* |
* hardware info/state |
*/ |
struct hc_driver *driver; /* hw-specific hooks */ |
unsigned saw_irq : 1; |
int irq; /* irq allocated */ |
void *regs; /* device memory/io */ |
struct device *controller; /* handle to hardware */ |
/* a few non-PCI controllers exist, mostly for OHCI */ |
struct pci_dev *pdev; /* pci is typical */ |
#ifdef CONFIG_PCI |
int region; /* pci region for regs */ |
u32 pci_state [16]; /* for PM state save */ |
#endif |
#define HCD_BUFFER_POOLS 4 |
struct pci_pool *pool [HCD_BUFFER_POOLS]; |
int state; |
# define __ACTIVE 0x01 |
# define __SUSPEND 0x04 |
# define __TRANSIENT 0x80 |
# define USB_STATE_HALT 0 |
# define USB_STATE_RUNNING (__ACTIVE) |
# define USB_STATE_QUIESCING (__SUSPEND|__TRANSIENT|__ACTIVE) |
# define USB_STATE_RESUMING (__SUSPEND|__TRANSIENT) |
# define USB_STATE_SUSPENDED (__SUSPEND) |
#define HCD_IS_RUNNING(state) ((state) & __ACTIVE) |
#define HCD_IS_SUSPENDED(state) ((state) & __SUSPEND) |
/* more shared queuing code would be good; it should support |
* smarter scheduling, handle transaction translators, etc; |
* input size of periodic table to an interrupt scheduler. |
* (ohci 32, uhci 1024, ehci 256/512/1024). |
*/ |
}; |
/* 2.4 does this a bit differently ... */ |
static inline struct usb_bus *hcd_to_bus (struct usb_hcd *hcd) |
{ |
return &hcd->self; |
} |
struct hcd_dev { /* usb_device.hcpriv points to this */ |
struct list_head dev_list; /* on this hcd */ |
struct list_head urb_list; /* pending on this dev */ |
/* per-configuration HC/HCD state, such as QH or ED */ |
void *ep[32]; |
}; |
// urb.hcpriv is really hardware-specific |
struct hcd_timeout { /* timeouts we allocate */ |
struct list_head timeout_list; |
struct timer_list timer; |
}; |
/*-------------------------------------------------------------------------*/ |
/* |
* FIXME usb_operations should vanish or become hc_driver, |
* when usb_bus and usb_hcd become the same thing. |
*/ |
struct usb_operations { |
int (*allocate)(struct usb_device *); |
int (*deallocate)(struct usb_device *); |
int (*get_frame_number) (struct usb_device *usb_dev); |
int (*submit_urb) (struct urb *urb, int mem_flags); |
int (*unlink_urb) (struct urb *urb); |
/* allocate dma-consistent buffer for URB_DMA_NOMAPPING */ |
void *(*buffer_alloc)(struct usb_bus *bus, size_t size, |
int mem_flags, |
dma_addr_t *dma); |
void (*buffer_free)(struct usb_bus *bus, size_t size, |
void *addr, dma_addr_t dma); |
void (*disable)(struct usb_device *udev, int bEndpointAddress); |
}; |
/* each driver provides one of these, and hardware init support */ |
struct pt_regs; |
struct hc_driver { |
const char *description; /* "ehci-hcd" etc */ |
/* irq handler */ |
void (*irq) (struct usb_hcd *hcd, struct pt_regs *regs); |
int flags; |
#define HCD_MEMORY 0x0001 /* HC regs use memory (else I/O) */ |
#define HCD_USB11 0x0010 /* USB 1.1 */ |
#define HCD_USB2 0x0020 /* USB 2.0 */ |
/* called to init HCD and root hub */ |
int (*reset) (struct usb_hcd *hcd); |
int (*start) (struct usb_hcd *hcd); |
/* called after all devices were suspended */ |
int (*suspend) (struct usb_hcd *hcd, u32 state); |
/* called before any devices get resumed */ |
int (*resume) (struct usb_hcd *hcd); |
/* cleanly make HCD stop writing memory and doing I/O */ |
void (*stop) (struct usb_hcd *hcd); |
/* return current frame number */ |
int (*get_frame_number) (struct usb_hcd *hcd); |
/* memory lifecycle */ |
struct usb_hcd *(*hcd_alloc) (void); |
void (*hcd_free) (struct usb_hcd *hcd); |
/* manage i/o requests, device state */ |
int (*urb_enqueue) (struct usb_hcd *hcd, struct urb *urb, |
int mem_flags); |
int (*urb_dequeue) (struct usb_hcd *hcd, struct urb *urb); |
/* hw synch, freeing endpoint resources that urb_dequeue can't */ |
void (*endpoint_disable)(struct usb_hcd *hcd, |
struct hcd_dev *dev, int bEndpointAddress); |
/* root hub support */ |
int (*hub_status_data) (struct usb_hcd *hcd, char *buf); |
int (*hub_control) (struct usb_hcd *hcd, |
u16 typeReq, u16 wValue, u16 wIndex, |
char *buf, u16 wLength); |
}; |
extern void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs); |
extern void usb_bus_init (struct usb_bus *bus); |
extern void usb_rh_status_dequeue (struct usb_hcd *hcd, struct urb *urb); |
#ifdef CONFIG_PCI |
struct pci_dev; |
struct pci_device_id; |
extern int usb_hcd_pci_probe (struct pci_dev *dev, |
const struct pci_device_id *id); |
extern void usb_hcd_pci_remove (struct pci_dev *dev); |
#ifdef CONFIG_PM |
extern int usb_hcd_pci_suspend (struct pci_dev *dev, u32 state); |
extern int usb_hcd_pci_resume (struct pci_dev *dev); |
#endif /* CONFIG_PM */ |
#endif /* CONFIG_PCI */ |
/* pci-ish (pdev null is ok) buffer alloc/mapping support */ |
int hcd_buffer_create (struct usb_hcd *hcd); |
void hcd_buffer_destroy (struct usb_hcd *hcd); |
void *hcd_buffer_alloc (struct usb_bus *bus, size_t size, |
int mem_flags, dma_addr_t *dma); |
void hcd_buffer_free (struct usb_bus *bus, size_t size, |
void *addr, dma_addr_t dma); |
/* generic bus glue, needed for host controllers that don't use PCI */ |
extern struct usb_operations usb_hcd_operations; |
extern irqreturn_t usb_hcd_irq (int irq, void *__hcd, struct pt_regs *r); |
extern void usb_hc_died (struct usb_hcd *hcd); |
/* -------------------------------------------------------------------------- */ |
/* Enumeration is only for the hub driver, or HCD virtual root hubs */ |
extern int usb_new_device(struct usb_device *dev, struct device *parent); |
extern void usb_choose_address(struct usb_device *dev); |
extern void usb_disconnect(struct usb_device **); |
/* exported to hub driver ONLY to support usb_reset_device () */ |
extern int usb_get_configuration(struct usb_device *dev); |
extern void usb_destroy_configuration(struct usb_device *dev); |
extern int usb_set_address(struct usb_device *dev); |
/* use these only before the device's address has been set */ |
#define usb_snddefctrl(dev) ((PIPE_CONTROL << 30)) |
#define usb_rcvdefctrl(dev) ((PIPE_CONTROL << 30) | USB_DIR_IN) |
/*-------------------------------------------------------------------------*/ |
/* |
* HCD Root Hub support |
*/ |
#include "hub.h" |
/* (shifted) direction/type/recipient from the USB 2.0 spec, table 9.2 */ |
#define DeviceRequest \ |
((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8) |
#define DeviceOutRequest \ |
((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8) |
#define InterfaceRequest \ |
((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) |
#define EndpointRequest \ |
((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) |
#define EndpointOutRequest \ |
((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) |
/* table 9.6 standard features */ |
#define DEVICE_REMOTE_WAKEUP 1 |
#define ENDPOINT_HALT 0 |
/* class requests from the USB 2.0 hub spec, table 11-15 */ |
/* GetBusState and SetHubDescriptor are optional, omitted */ |
#define ClearHubFeature (0x2000 | USB_REQ_CLEAR_FEATURE) |
#define ClearPortFeature (0x2300 | USB_REQ_CLEAR_FEATURE) |
#define GetHubDescriptor (0xa000 | USB_REQ_GET_DESCRIPTOR) |
#define GetHubStatus (0xa000 | USB_REQ_GET_STATUS) |
#define GetPortStatus (0xa300 | USB_REQ_GET_STATUS) |
#define SetHubFeature (0x2000 | USB_REQ_SET_FEATURE) |
#define SetPortFeature (0x2300 | USB_REQ_SET_FEATURE) |
/*-------------------------------------------------------------------------*/ |
/* |
* Generic bandwidth allocation constants/support |
*/ |
#define FRAME_TIME_USECS 1000L |
#define BitTime(bytecount) (7 * 8 * bytecount / 6) /* with integer truncation */ |
/* Trying not to use worst-case bit-stuffing |
of (7/6 * 8 * bytecount) = 9.33 * bytecount */ |
/* bytecount = data payload byte count */ |
#define NS_TO_US(ns) ((ns + 500L) / 1000L) |
/* convert & round nanoseconds to microseconds */ |
extern void usb_claim_bandwidth (struct usb_device *dev, struct urb *urb, |
int bustime, int isoc); |
extern void usb_release_bandwidth (struct usb_device *dev, struct urb *urb, |
int isoc); |
/* |
* Full/low speed bandwidth allocation constants/support. |
*/ |
#define BW_HOST_DELAY 1000L /* nanoseconds */ |
#define BW_HUB_LS_SETUP 333L /* nanoseconds */ |
/* 4 full-speed bit times (est.) */ |
#define FRAME_TIME_BITS 12000L /* frame = 1 millisecond */ |
#define FRAME_TIME_MAX_BITS_ALLOC (90L * FRAME_TIME_BITS / 100L) |
#define FRAME_TIME_MAX_USECS_ALLOC (90L * FRAME_TIME_USECS / 100L) |
extern int usb_check_bandwidth (struct usb_device *dev, struct urb *urb); |
/* |
* Ceiling microseconds (typical) for that many bytes at high speed |
* ISO is a bit less, no ACK ... from USB 2.0 spec, 5.11.3 (and needed |
* to preallocate bandwidth) |
*/ |
#define USB2_HOST_DELAY 5 /* nsec, guess */ |
#define HS_USECS(bytes) NS_TO_US ( ((55 * 8 * 2083)/1000) \ |
+ ((2083UL * (3167 + BitTime (bytes)))/1000) \ |
+ USB2_HOST_DELAY) |
#define HS_USECS_ISO(bytes) NS_TO_US ( ((long)(38 * 8 * 2.083)) \ |
+ ((2083UL * (3167 + BitTime (bytes)))/1000) \ |
+ USB2_HOST_DELAY) |
extern long usb_calc_bus_time (int speed, int is_input, |
int isoc, int bytecount); |
/*-------------------------------------------------------------------------*/ |
extern struct usb_bus *usb_alloc_bus (struct usb_operations *); |
extern int usb_register_bus (struct usb_bus *); |
extern void usb_deregister_bus (struct usb_bus *); |
extern int usb_register_root_hub (struct usb_device *usb_dev, |
struct device *parent_dev); |
/* for portability to 2.4, hcds should call this */ |
static inline int hcd_register_root (struct usb_hcd *hcd) |
{ |
return usb_register_root_hub ( |
hcd_to_bus (hcd)->root_hub, hcd->controller); |
} |
/*-------------------------------------------------------------------------*/ |
/* exported only within usbcore */ |
extern struct list_head usb_bus_list; |
extern struct semaphore usb_bus_list_lock; |
extern struct usb_bus *usb_bus_get (struct usb_bus *bus); |
extern void usb_bus_put (struct usb_bus *bus); |
extern int usb_find_interface_driver (struct usb_device *dev, |
struct usb_interface *interface); |
#define usb_endpoint_halt(dev, ep, out) ((dev)->halted[out] |= (1 << (ep))) |
#define usb_endpoint_out(ep_dir) (!((ep_dir) & USB_DIR_IN)) |
/* |
* USB device fs stuff |
*/ |
#ifdef CONFIG_USB_DEVICEFS |
/* |
* these are expected to be called from the USB core/hub thread |
* with the kernel lock held |
*/ |
extern void usbfs_add_bus(struct usb_bus *bus); |
extern void usbfs_remove_bus(struct usb_bus *bus); |
extern void usbfs_add_device(struct usb_device *dev); |
extern void usbfs_remove_device(struct usb_device *dev); |
extern void usbfs_update_special (void); |
extern int usbfs_init(void); |
extern void usbfs_cleanup(void); |
#else /* CONFIG_USB_DEVICEFS */ |
static inline void usbfs_add_bus(struct usb_bus *bus) {} |
static inline void usbfs_remove_bus(struct usb_bus *bus) {} |
static inline void usbfs_add_device(struct usb_device *dev) {} |
static inline void usbfs_remove_device(struct usb_device *dev) {} |
static inline void usbfs_update_special (void) {} |
static inline int usbfs_init(void) { return 0; } |
static inline void usbfs_cleanup(void) { } |
#endif /* CONFIG_USB_DEVICEFS */ |
/*-------------------------------------------------------------------------*/ |
/* hub.h ... DeviceRemovable in 2.4.2-ac11, gone in 2.4.10 */ |
// bleech -- resurfaced in 2.4.11 or 2.4.12 |
#define bitmap DeviceRemovable |
/*-------------------------------------------------------------------------*/ |
/* random stuff */ |
#define RUN_CONTEXT (in_irq () ? "in_irq" \ |
: (in_interrupt () ? "in_interrupt" : "can sleep")) |
#endif /* __KERNEL__ */ |
/shark/trunk/drivers/usb/core/config.c |
---|
0,0 → 1,487 |
#include <linuxcomp.h> |
#include <linux/usb.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/slab.h> |
#include <asm/byteorder.h> |
#define USB_MAXALTSETTING 128 /* Hard limit */ |
#define USB_MAXENDPOINTS 30 /* Hard limit */ |
/* these maximums are arbitrary */ |
#define USB_MAXCONFIG 8 |
#define USB_MAXINTERFACES 32 |
static int usb_parse_endpoint(struct usb_host_endpoint *endpoint, unsigned char *buffer, int size) |
{ |
unsigned char *buffer0 = buffer; |
struct usb_descriptor_header *header; |
unsigned char *begin; |
int numskipped; |
header = (struct usb_descriptor_header *)buffer; |
if (header->bDescriptorType != USB_DT_ENDPOINT) { |
warn("unexpected descriptor 0x%X, expecting endpoint, 0x%X", |
header->bDescriptorType, USB_DT_ENDPOINT); |
return -EINVAL; |
} |
if (header->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE) |
memcpy(&endpoint->desc, buffer, USB_DT_ENDPOINT_AUDIO_SIZE); |
else if (header->bLength >= USB_DT_ENDPOINT_SIZE) |
memcpy(&endpoint->desc, buffer, USB_DT_ENDPOINT_SIZE); |
else { |
warn("invalid endpoint descriptor"); |
return -EINVAL; |
} |
if ((endpoint->desc.bEndpointAddress & ~USB_ENDPOINT_DIR_MASK) >= 16) { |
warn("invalid endpoint address 0x%X", |
endpoint->desc.bEndpointAddress); |
return -EINVAL; |
} |
le16_to_cpus(&endpoint->desc.wMaxPacketSize); |
buffer += header->bLength; |
size -= header->bLength; |
/* Skip over any Class Specific or Vendor Specific descriptors */ |
begin = buffer; |
numskipped = 0; |
while (size >= sizeof(struct usb_descriptor_header)) { |
header = (struct usb_descriptor_header *)buffer; |
/* If we find another "proper" descriptor then we're done */ |
if ((header->bDescriptorType == USB_DT_ENDPOINT) || |
(header->bDescriptorType == USB_DT_INTERFACE)) |
break; |
dbg("skipping descriptor 0x%X", header->bDescriptorType); |
numskipped++; |
buffer += header->bLength; |
size -= header->bLength; |
} |
if (numskipped) { |
dbg("skipped %d class/vendor specific endpoint descriptors", numskipped); |
endpoint->extra = begin; |
endpoint->extralen = buffer - begin; |
} |
return buffer - buffer0; |
} |
static void usb_release_intf(struct device *dev) |
{ |
struct usb_interface *intf; |
int j; |
intf = to_usb_interface(dev); |
if (intf->altsetting) { |
for (j = 0; j < intf->num_altsetting; j++) { |
struct usb_host_interface *as = &intf->altsetting[j]; |
kfree(as->endpoint); |
} |
kfree(intf->altsetting); |
} |
kfree(intf); |
} |
static int usb_parse_interface(struct usb_host_config *config, unsigned char *buffer, int size) |
{ |
unsigned char *buffer0 = buffer; |
struct usb_interface_descriptor *d; |
int inum, asnum; |
struct usb_interface *interface; |
struct usb_host_interface *ifp; |
int len, numskipped; |
struct usb_descriptor_header *header; |
unsigned char *begin; |
int i, retval; |
d = (struct usb_interface_descriptor *) buffer; |
if (d->bDescriptorType != USB_DT_INTERFACE) { |
warn("unexpected descriptor 0x%X, expecting interface, 0x%X", |
d->bDescriptorType, USB_DT_INTERFACE); |
return -EINVAL; |
} |
inum = d->bInterfaceNumber; |
if (inum >= config->desc.bNumInterfaces) { |
/* Skip to the next interface descriptor */ |
buffer += d->bLength; |
size -= d->bLength; |
while (size >= sizeof(struct usb_descriptor_header)) { |
header = (struct usb_descriptor_header *) buffer; |
if (header->bDescriptorType == USB_DT_INTERFACE) |
break; |
buffer += header->bLength; |
size -= header->bLength; |
} |
return buffer - buffer0; |
} |
interface = config->interface[inum]; |
asnum = d->bAlternateSetting; |
if (asnum >= interface->num_altsetting) { |
warn("invalid alternate setting %d for interface %d", |
asnum, inum); |
return -EINVAL; |
} |
ifp = &interface->altsetting[asnum]; |
if (ifp->desc.bLength) { |
warn("duplicate descriptor for interface %d altsetting %d", |
inum, asnum); |
return -EINVAL; |
} |
memcpy(&ifp->desc, buffer, USB_DT_INTERFACE_SIZE); |
buffer += d->bLength; |
size -= d->bLength; |
/* Skip over any Class Specific or Vendor Specific descriptors */ |
begin = buffer; |
numskipped = 0; |
while (size >= sizeof(struct usb_descriptor_header)) { |
header = (struct usb_descriptor_header *)buffer; |
/* If we find another "proper" descriptor then we're done */ |
if ((header->bDescriptorType == USB_DT_INTERFACE) || |
(header->bDescriptorType == USB_DT_ENDPOINT)) |
break; |
dbg("skipping descriptor 0x%X", header->bDescriptorType); |
numskipped++; |
buffer += header->bLength; |
size -= header->bLength; |
} |
if (numskipped) { |
dbg("skipped %d class/vendor specific interface descriptors", numskipped); |
ifp->extra = begin; |
ifp->extralen = buffer - begin; |
} |
if (ifp->desc.bNumEndpoints > USB_MAXENDPOINTS) { |
warn("too many endpoints for interface %d altsetting %d", |
inum, asnum); |
return -EINVAL; |
} |
len = ifp->desc.bNumEndpoints * sizeof(struct usb_host_endpoint); |
ifp->endpoint = kmalloc(len, GFP_KERNEL); |
if (!ifp->endpoint) { |
err("out of memory"); |
return -ENOMEM; |
} |
memset(ifp->endpoint, 0, len); |
for (i = 0; i < ifp->desc.bNumEndpoints; i++) { |
if (size < USB_DT_ENDPOINT_SIZE) { |
warn("ran out of descriptors while parsing endpoints"); |
return -EINVAL; |
} |
retval = usb_parse_endpoint(ifp->endpoint + i, buffer, size); |
if (retval < 0) |
return retval; |
buffer += retval; |
size -= retval; |
} |
return buffer - buffer0; |
} |
int usb_parse_configuration(struct usb_host_config *config, char *buffer, int size) |
{ |
int nintf, nintf_orig; |
int i, j; |
struct usb_interface *interface; |
char *buffer2; |
int size2; |
struct usb_descriptor_header *header; |
int numskipped, len; |
char *begin; |
int retval; |
memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE); |
if (config->desc.bDescriptorType != USB_DT_CONFIG || |
config->desc.bLength < USB_DT_CONFIG_SIZE) { |
warn("invalid configuration descriptor"); |
return -EINVAL; |
} |
config->desc.wTotalLength = size; |
nintf = nintf_orig = config->desc.bNumInterfaces; |
if (nintf > USB_MAXINTERFACES) { |
warn("too many interfaces (%d max %d)", |
nintf, USB_MAXINTERFACES); |
config->desc.bNumInterfaces = nintf = USB_MAXINTERFACES; |
} |
for (i = 0; i < nintf; ++i) { |
interface = config->interface[i] = |
kmalloc(sizeof(struct usb_interface), GFP_KERNEL); |
dbg("kmalloc IF %p, numif %i", interface, i); |
if (!interface) { |
err("out of memory"); |
return -ENOMEM; |
} |
memset(interface, 0, sizeof(struct usb_interface)); |
interface->dev.release = usb_release_intf; |
device_initialize(&interface->dev); |
} |
/* Go through the descriptors, checking their length and counting the |
* number of altsettings for each interface */ |
buffer2 = buffer; |
size2 = size; |
j = 0; |
while (size2 >= sizeof(struct usb_descriptor_header)) { |
header = (struct usb_descriptor_header *) buffer2; |
if ((header->bLength > size2) || (header->bLength < 2)) { |
warn("invalid descriptor of length %d", header->bLength); |
return -EINVAL; |
} |
if (header->bDescriptorType == USB_DT_INTERFACE) { |
struct usb_interface_descriptor *d; |
if (header->bLength < USB_DT_INTERFACE_SIZE) { |
warn("invalid interface descriptor"); |
return -EINVAL; |
} |
d = (struct usb_interface_descriptor *) header; |
i = d->bInterfaceNumber; |
if (i >= nintf_orig) { |
warn("invalid interface number (%d/%d)", |
i, nintf_orig); |
return -EINVAL; |
} |
if (i < nintf) |
++config->interface[i]->num_altsetting; |
} else if ((header->bDescriptorType == USB_DT_DEVICE || |
header->bDescriptorType == USB_DT_CONFIG) && j) { |
warn("unexpected descriptor type 0x%X", header->bDescriptorType); |
return -EINVAL; |
} |
j = 1; |
buffer2 += header->bLength; |
size2 -= header->bLength; |
} |
/* Allocate the altsetting arrays */ |
for (i = 0; i < config->desc.bNumInterfaces; ++i) { |
interface = config->interface[i]; |
if (interface->num_altsetting > USB_MAXALTSETTING) { |
warn("too many alternate settings for interface %d (%d max %d)\n", |
i, interface->num_altsetting, USB_MAXALTSETTING); |
return -EINVAL; |
} |
if (interface->num_altsetting == 0) { |
warn("no alternate settings for interface %d", i); |
return -EINVAL; |
} |
len = sizeof(*interface->altsetting) * interface->num_altsetting; |
interface->altsetting = kmalloc(len, GFP_KERNEL); |
if (!interface->altsetting) { |
err("couldn't kmalloc interface->altsetting"); |
return -ENOMEM; |
} |
memset(interface->altsetting, 0, len); |
} |
buffer += config->desc.bLength; |
size -= config->desc.bLength; |
/* Skip over any Class Specific or Vendor Specific descriptors */ |
begin = buffer; |
numskipped = 0; |
while (size >= sizeof(struct usb_descriptor_header)) { |
header = (struct usb_descriptor_header *)buffer; |
/* If we find another "proper" descriptor then we're done */ |
if ((header->bDescriptorType == USB_DT_ENDPOINT) || |
(header->bDescriptorType == USB_DT_INTERFACE)) |
break; |
dbg("skipping descriptor 0x%X", header->bDescriptorType); |
numskipped++; |
buffer += header->bLength; |
size -= header->bLength; |
} |
if (numskipped) { |
dbg("skipped %d class/vendor specific configuration descriptors", numskipped); |
config->extra = begin; |
config->extralen = buffer - begin; |
} |
/* Parse all the interface/altsetting descriptors */ |
while (size >= sizeof(struct usb_descriptor_header)) { |
retval = usb_parse_interface(config, buffer, size); |
if (retval < 0) |
return retval; |
buffer += retval; |
size -= retval; |
} |
/* Check for missing altsettings */ |
for (i = 0; i < nintf; ++i) { |
interface = config->interface[i]; |
for (j = 0; j < interface->num_altsetting; ++j) { |
if (!interface->altsetting[j].desc.bLength) { |
warn("missing altsetting %d for interface %d", j, i); |
return -EINVAL; |
} |
} |
} |
return size; |
} |
// hub-only!! ... and only exported for reset/reinit path. |
// otherwise used internally on disconnect/destroy path |
void usb_destroy_configuration(struct usb_device *dev) |
{ |
int c, i; |
if (!dev->config) |
return; |
if (dev->rawdescriptors) { |
for (i = 0; i < dev->descriptor.bNumConfigurations; i++) |
kfree(dev->rawdescriptors[i]); |
kfree(dev->rawdescriptors); |
} |
for (c = 0; c < dev->descriptor.bNumConfigurations; c++) { |
struct usb_host_config *cf = &dev->config[c]; |
for (i = 0; i < cf->desc.bNumInterfaces; i++) { |
struct usb_interface *ifp = cf->interface[i]; |
if (ifp) |
put_device(&ifp->dev); |
} |
} |
kfree(dev->config); |
} |
// hub-only!! ... and only in reset path, or usb_new_device() |
// (used by real hubs and virtual root hubs) |
int usb_get_configuration(struct usb_device *dev) |
{ |
int ncfg = dev->descriptor.bNumConfigurations; |
int result; |
unsigned int cfgno, length; |
unsigned char *buffer; |
unsigned char *bigbuffer; |
struct usb_config_descriptor *desc; |
if (ncfg > USB_MAXCONFIG) { |
warn("too many configurations (%d max %d)", |
ncfg, USB_MAXCONFIG); |
dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG; |
} |
if (ncfg < 1) { |
warn("no configurations"); |
return -EINVAL; |
} |
length = ncfg * sizeof(struct usb_host_config); |
dev->config = kmalloc(length, GFP_KERNEL); |
if (!dev->config) { |
err("out of memory"); |
return -ENOMEM; |
} |
memset(dev->config, 0, length); |
length = ncfg * sizeof(char *); |
dev->rawdescriptors = kmalloc(length, GFP_KERNEL); |
if (!dev->rawdescriptors) { |
err("out of memory"); |
return -ENOMEM; |
} |
memset(dev->rawdescriptors, 0, length); |
buffer = kmalloc(8, GFP_KERNEL); |
if (!buffer) { |
err("unable to allocate memory for configuration descriptors"); |
return -ENOMEM; |
} |
desc = (struct usb_config_descriptor *)buffer; |
for (cfgno = 0; cfgno < ncfg; cfgno++) { |
/* We grab the first 8 bytes so we know how long the whole */ |
/* configuration is */ |
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, buffer, 8); |
if (result < 8) { |
if (result < 0) |
err("unable to get descriptor"); |
else { |
warn("config descriptor too short (expected %i, got %i)", 8, result); |
result = -EINVAL; |
} |
goto err; |
} |
/* Get the full buffer */ |
length = max((int) le16_to_cpu(desc->wTotalLength), USB_DT_CONFIG_SIZE); |
bigbuffer = kmalloc(length, GFP_KERNEL); |
if (!bigbuffer) { |
err("unable to allocate memory for configuration descriptors"); |
result = -ENOMEM; |
goto err; |
} |
/* Now that we know the length, get the whole thing */ |
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, bigbuffer, length); |
if (result < 0) { |
err("couldn't get all of config descriptors"); |
kfree(bigbuffer); |
goto err; |
} |
if (result < length) { |
err("config descriptor too short (expected %i, got %i)", length, result); |
result = -EINVAL; |
kfree(bigbuffer); |
goto err; |
} |
dev->rawdescriptors[cfgno] = bigbuffer; |
result = usb_parse_configuration(&dev->config[cfgno], bigbuffer, length); |
if (result > 0) |
dbg("descriptor data left"); |
else if (result < 0) { |
++cfgno; |
goto err; |
} |
} |
kfree(buffer); |
return 0; |
err: |
kfree(buffer); |
dev->descriptor.bNumConfigurations = cfgno; |
return result; |
} |
/shark/trunk/drivers/usb/core/hcd-pci.c |
---|
0,0 → 1,349 |
/* |
* (C) Copyright David Brownell 2000-2002 |
* |
* 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. |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#ifdef CONFIG_USB_DEBUG |
#define DEBUG |
#else |
#undef DEBUG |
#endif |
#include <linux/kernel.h> |
#include <linux/module.h> |
#include <linux/pci.h> |
#include <asm/io.h> |
#include <asm/irq.h> |
#include <linux/usb.h> |
#include "hcd.h" |
/* PCI-based HCs are normal, but custom bus glue should be ok */ |
/*-------------------------------------------------------------------------*/ |
static void hcd_pci_release(struct usb_bus *bus) |
{ |
struct usb_hcd *hcd = bus->hcpriv; |
if (hcd) |
hcd->driver->hcd_free(hcd); |
} |
/* configure so an HC device and id are always provided */ |
/* always called with process context; sleeping is OK */ |
/** |
* usb_hcd_pci_probe - initialize PCI-based HCDs |
* @dev: USB Host Controller being probed |
* @id: pci hotplug id connecting controller to HCD framework |
* Context: !in_interrupt() |
* |
* Allocates basic PCI resources for this USB host controller, and |
* then invokes the start() method for the HCD associated with it |
* through the hotplug entry's driver_data. |
* |
* Store this function in the HCD's struct pci_driver as probe(). |
*/ |
int usb_hcd_pci_probe (struct pci_dev *dev, const struct pci_device_id *id) |
{ |
struct hc_driver *driver; |
unsigned long resource, len; |
void *base; |
struct usb_hcd *hcd; |
int retval, region; |
char buf [8], *bufp = buf; |
if (usb_disabled()) |
return -ENODEV; |
if (!id || !(driver = (struct hc_driver *) id->driver_data)) |
return -EINVAL; |
if (pci_enable_device (dev) < 0) |
return -ENODEV; |
if (!dev->irq) { |
err ("Found HC with no IRQ. Check BIOS/PCI %s setup!", |
pci_name(dev)); |
return -ENODEV; |
} |
if (driver->flags & HCD_MEMORY) { // EHCI, OHCI |
region = 0; |
resource = pci_resource_start (dev, 0); |
len = pci_resource_len (dev, 0); |
if (!request_mem_region (resource, len, driver->description)) { |
dbg ("controller already in use"); |
return -EBUSY; |
} |
base = ioremap_nocache (resource, len); |
if (base == NULL) { |
dbg ("error mapping memory"); |
retval = -EFAULT; |
clean_1: |
release_mem_region (resource, len); |
err ("init %s fail, %d", pci_name(dev), retval); |
return retval; |
} |
} else { // UHCI |
resource = len = 0; |
for (region = 0; region < PCI_ROM_RESOURCE; region++) { |
if (!(pci_resource_flags (dev, region) & IORESOURCE_IO)) |
continue; |
resource = pci_resource_start (dev, region); |
len = pci_resource_len (dev, region); |
if (request_region (resource, len, |
driver->description)) |
break; |
} |
if (region == PCI_ROM_RESOURCE) { |
dbg ("no i/o regions available"); |
return -EBUSY; |
} |
base = (void *) resource; |
} |
// driver->reset(), later on, will transfer device from |
// control by SMM/BIOS to control by Linux (if needed) |
hcd = driver->hcd_alloc (); |
if (hcd == NULL){ |
dbg ("hcd alloc fail"); |
retval = -ENOMEM; |
clean_2: |
if (driver->flags & HCD_MEMORY) { |
iounmap (base); |
goto clean_1; |
} else { |
release_region (resource, len); |
err ("init %s fail, %d", pci_name(dev), retval); |
return retval; |
} |
} |
// hcd zeroed everything |
hcd->regs = base; |
hcd->region = region; |
pci_set_drvdata (dev, hcd); |
hcd->driver = driver; |
hcd->description = driver->description; |
hcd->pdev = dev; |
hcd->self.bus_name = pci_name(dev); |
if (hcd->product_desc == NULL) |
hcd->product_desc = "USB Host Controller"; |
hcd->self.controller = &dev->dev; |
hcd->controller = hcd->self.controller; |
if ((retval = hcd_buffer_create (hcd)) != 0) { |
clean_3: |
driver->hcd_free (hcd); |
goto clean_2; |
} |
dev_info (hcd->controller, "%s\n", hcd->product_desc); |
/* till now HC has been in an indeterminate state ... */ |
if (driver->reset && (retval = driver->reset (hcd)) < 0) { |
dev_err (hcd->controller, "can't reset\n"); |
goto clean_3; |
} |
hcd->state = USB_STATE_HALT; |
pci_set_master (dev); |
#ifndef __sparc__ |
sprintf26 (buf, "%d", dev->irq); |
#else |
bufp = __irq_itoa(dev->irq); |
#endif |
retval = request_irq (dev->irq, usb_hcd_irq, SA_SHIRQ, |
hcd->description, hcd); |
if (retval != 0) { |
dev_err (hcd->controller, |
"request interrupt %s failed\n", bufp); |
goto clean_3; |
} |
hcd->irq = dev->irq; |
dev_info (hcd->controller, "irq %s, %s %p\n", bufp, |
(driver->flags & HCD_MEMORY) ? "pci mem" : "io base", |
base); |
usb_bus_init (&hcd->self); |
hcd->self.op = &usb_hcd_operations; |
hcd->self.hcpriv = (void *) hcd; |
hcd->self.release = &hcd_pci_release; |
INIT_LIST_HEAD (&hcd->dev_list); |
usb_register_bus (&hcd->self); |
if ((retval = driver->start (hcd)) < 0) |
usb_hcd_pci_remove (dev); |
return retval; |
} |
EXPORT_SYMBOL (usb_hcd_pci_probe); |
/* may be called without controller electrically present */ |
/* may be called with controller, bus, and devices active */ |
/** |
* usb_hcd_pci_remove - shutdown processing for PCI-based HCDs |
* @dev: USB Host Controller being removed |
* Context: !in_interrupt() |
* |
* Reverses the effect of usb_hcd_pci_probe(), first invoking |
* the HCD's stop() method. It is always called from a thread |
* context, normally "rmmod", "apmd", or something similar. |
* |
* Store this function in the HCD's struct pci_driver as remove(). |
*/ |
void usb_hcd_pci_remove (struct pci_dev *dev) |
{ |
struct usb_hcd *hcd; |
struct usb_device *hub; |
hcd = pci_get_drvdata(dev); |
if (!hcd) |
return; |
dev_info (hcd->controller, "remove, state %x\n", hcd->state); |
if (in_interrupt ()) |
BUG (); |
hub = hcd->self.root_hub; |
if (HCD_IS_RUNNING (hcd->state)) |
hcd->state = USB_STATE_QUIESCING; |
dev_dbg (hcd->controller, "roothub graceful disconnect\n"); |
usb_disconnect (&hub); |
hcd->driver->stop (hcd); |
hcd_buffer_destroy (hcd); |
hcd->state = USB_STATE_HALT; |
pci_set_drvdata (dev, 0); |
free_irq (hcd->irq, hcd); |
if (hcd->driver->flags & HCD_MEMORY) { |
iounmap (hcd->regs); |
release_mem_region (pci_resource_start (dev, 0), |
pci_resource_len (dev, 0)); |
} else { |
release_region (pci_resource_start (dev, hcd->region), |
pci_resource_len (dev, hcd->region)); |
} |
usb_deregister_bus (&hcd->self); |
} |
EXPORT_SYMBOL (usb_hcd_pci_remove); |
#ifdef CONFIG_PM |
/** |
* usb_hcd_pci_suspend - power management suspend of a PCI-based HCD |
* @dev: USB Host Controller being suspended |
* @state: state that the controller is going into |
* |
* Store this function in the HCD's struct pci_driver as suspend(). |
*/ |
int usb_hcd_pci_suspend (struct pci_dev *dev, u32 state) |
{ |
struct usb_hcd *hcd; |
int retval = 0; |
hcd = pci_get_drvdata(dev); |
dev_dbg (hcd->controller, "suspend D%d --> D%d\n", |
dev->current_state, state); |
switch (hcd->state) { |
case USB_STATE_HALT: |
dev_dbg (hcd->controller, "halted; hcd not suspended\n"); |
break; |
case USB_STATE_SUSPENDED: |
dev_dbg (hcd->controller, "hcd already suspended\n"); |
break; |
default: |
/* remote wakeup needs hub->suspend() cooperation */ |
// pci_enable_wake (dev, 3, 1); |
pci_save_state (dev, hcd->pci_state); |
/* driver may want to disable DMA etc */ |
hcd->state = USB_STATE_QUIESCING; |
retval = hcd->driver->suspend (hcd, state); |
if (retval) |
dev_dbg (hcd->controller, "suspend fail, retval %d\n", |
retval); |
else |
hcd->state = USB_STATE_SUSPENDED; |
} |
pci_set_power_state (dev, state); |
return retval; |
} |
EXPORT_SYMBOL (usb_hcd_pci_suspend); |
/** |
* usb_hcd_pci_resume - power management resume of a PCI-based HCD |
* @dev: USB Host Controller being resumed |
* |
* Store this function in the HCD's struct pci_driver as resume(). |
*/ |
int usb_hcd_pci_resume (struct pci_dev *dev) |
{ |
struct usb_hcd *hcd; |
int retval; |
hcd = pci_get_drvdata(dev); |
dev_dbg (hcd->controller, "resume from state D%d\n", |
dev->current_state); |
if (hcd->state != USB_STATE_SUSPENDED) { |
dev_dbg (hcd->controller, "can't resume, not suspended!\n"); |
return -EL3HLT; |
} |
hcd->state = USB_STATE_RESUMING; |
pci_set_power_state (dev, 0); |
pci_restore_state (dev, hcd->pci_state); |
/* remote wakeup needs hub->suspend() cooperation */ |
// pci_enable_wake (dev, 3, 0); |
retval = hcd->driver->resume (hcd); |
if (!HCD_IS_RUNNING (hcd->state)) { |
dev_dbg (hcd->controller, "resume fail, retval %d\n", retval); |
usb_hc_died (hcd); |
} |
return retval; |
} |
EXPORT_SYMBOL (usb_hcd_pci_resume); |
#endif /* CONFIG_PM */ |
/shark/trunk/drivers/usb/core/buffer.c |
---|
0,0 → 1,139 |
/* |
* DMA memory management for framework level HCD code (hc_driver) |
* |
* This implementation plugs in through generic "usb_bus" level methods, |
* and works with real PCI, or when "pci device == null" makes sense. |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/module.h> |
#include <linux/kernel.h> |
#include <linux/slab.h> |
#include <linux/pci.h> |
#ifdef CONFIG_USB_DEBUG |
#define DEBUG |
#else |
#undef DEBUG |
#endif |
#include <linux/usb.h> |
#include "hcd.h" |
/* |
* DMA-Coherent Buffers |
*/ |
/* FIXME tune these based on pool statistics ... */ |
static const size_t pool_max [HCD_BUFFER_POOLS] = { |
/* platforms without dma-friendly caches might need to |
* prevent cacheline sharing... |
*/ |
32, |
128, |
512, |
PAGE_SIZE / 2 |
/* bigger --> allocate pages */ |
}; |
/* SETUP primitives */ |
/** |
* hcd_buffer_create - initialize buffer pools |
* @hcd: the bus whose buffer pools are to be initialized |
* Context: !in_interrupt() |
* |
* Call this as part of initializing a host controller that uses the pci dma |
* memory allocators. It initializes some pools of dma-consistent memory that |
* will be shared by all drivers using that controller, or returns a negative |
* errno value on error. |
* |
* Call hcd_buffer_destroy() to clean up after using those pools. |
*/ |
int hcd_buffer_create (struct usb_hcd *hcd) |
{ |
char name [16]; |
int i, size; |
for (i = 0; i < HCD_BUFFER_POOLS; i++) { |
if (!(size = pool_max [i])) |
continue; |
snprintf26(name, sizeof name, "buffer-%d", size); |
hcd->pool [i] = pci_pool_create (name, hcd->pdev, |
size, size, 0); |
if (!hcd->pool [i]) { |
hcd_buffer_destroy (hcd); |
return -ENOMEM; |
} |
} |
return 0; |
} |
EXPORT_SYMBOL (hcd_buffer_create); |
/** |
* hcd_buffer_destroy - deallocate buffer pools |
* @hcd: the bus whose buffer pools are to be destroyed |
* Context: !in_interrupt() |
* |
* This frees the buffer pools created by hcd_buffer_create(). |
*/ |
void hcd_buffer_destroy (struct usb_hcd *hcd) |
{ |
int i; |
for (i = 0; i < HCD_BUFFER_POOLS; i++) { |
struct pci_pool *pool = hcd->pool [i]; |
if (pool) { |
pci_pool_destroy (pool); |
hcd->pool [i] = 0; |
} |
} |
} |
EXPORT_SYMBOL (hcd_buffer_destroy); |
/* sometimes alloc/free could use kmalloc with SLAB_DMA, for |
* better sharing and to leverage mm/slab.c intelligence. |
*/ |
void *hcd_buffer_alloc ( |
struct usb_bus *bus, |
size_t size, |
int mem_flags, |
dma_addr_t *dma |
) |
{ |
struct usb_hcd *hcd = bus->hcpriv; |
int i; |
for (i = 0; i < HCD_BUFFER_POOLS; i++) { |
if (size <= pool_max [i]) |
return pci_pool_alloc (hcd->pool [i], mem_flags, dma); |
} |
return pci_alloc_consistent (hcd->pdev, size, dma); |
} |
void hcd_buffer_free ( |
struct usb_bus *bus, |
size_t size, |
void *addr, |
dma_addr_t dma |
) |
{ |
struct usb_hcd *hcd = bus->hcpriv; |
int i; |
for (i = 0; i < HCD_BUFFER_POOLS; i++) { |
if (size <= pool_max [i]) { |
pci_pool_free (hcd->pool [i], addr, dma); |
return; |
} |
} |
pci_free_consistent (hcd->pdev, size, addr, dma); |
} |
/shark/trunk/drivers/usb/core/usb-debug.c |
---|
0,0 → 1,203 |
/* |
* debug.c - USB debug helper routines. |
* |
* I just want these out of the way where they aren't in your |
* face, but so that you can still use them.. |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/kernel.h> |
#include <linux/mm.h> |
#include <linux/slab.h> |
#ifdef CONFIG_USB_DEBUG |
#define DEBUG |
#else |
#undef DEBUG |
#endif |
#include <linux/usb.h> |
static void usb_show_endpoint(struct usb_host_endpoint *endpoint) |
{ |
usb_show_endpoint_descriptor(&endpoint->desc); |
} |
static void usb_show_interface(struct usb_host_interface *altsetting) |
{ |
int i; |
usb_show_interface_descriptor(&altsetting->desc); |
for (i = 0; i < altsetting->desc.bNumEndpoints; i++) |
usb_show_endpoint(altsetting->endpoint + i); |
} |
static void usb_show_config(struct usb_host_config *config) |
{ |
int i, j; |
struct usb_interface *ifp; |
usb_show_config_descriptor(&config->desc); |
for (i = 0; i < config->desc.bNumInterfaces; i++) { |
ifp = config->interface[i]; |
if (!ifp) |
break; |
printk("\n Interface: %d\n", i); |
for (j = 0; j < ifp->num_altsetting; j++) |
usb_show_interface(ifp->altsetting + j); |
} |
} |
void usb_show_device(struct usb_device *dev) |
{ |
int i; |
usb_show_device_descriptor(&dev->descriptor); |
for (i = 0; i < dev->descriptor.bNumConfigurations; i++) |
usb_show_config(dev->config + i); |
} |
/* |
* Parse and show the different USB descriptors. |
*/ |
void usb_show_device_descriptor(struct usb_device_descriptor *desc) |
{ |
if (!desc) |
{ |
printk("Invalid USB device descriptor (NULL POINTER)\n"); |
return; |
} |
printk(" Length = %2d%s\n", desc->bLength, |
desc->bLength == USB_DT_DEVICE_SIZE ? "" : " (!!!)"); |
printk(" DescriptorType = %02x\n", desc->bDescriptorType); |
printk(" USB version = %x.%02x\n", |
desc->bcdUSB >> 8, desc->bcdUSB & 0xff); |
printk(" Vendor:Product = %04x:%04x\n", |
desc->idVendor, desc->idProduct); |
printk(" MaxPacketSize0 = %d\n", desc->bMaxPacketSize0); |
printk(" NumConfigurations = %d\n", desc->bNumConfigurations); |
printk(" Device version = %x.%02x\n", |
desc->bcdDevice >> 8, desc->bcdDevice & 0xff); |
printk(" Device Class:SubClass:Protocol = %02x:%02x:%02x\n", |
desc->bDeviceClass, desc->bDeviceSubClass, desc->bDeviceProtocol); |
switch (desc->bDeviceClass) { |
case 0: |
printk(" Per-interface classes\n"); |
break; |
case USB_CLASS_AUDIO: |
printk(" Audio device class\n"); |
break; |
case USB_CLASS_COMM: |
printk(" Communications class\n"); |
break; |
case USB_CLASS_HID: |
printk(" Human Interface Devices class\n"); |
break; |
case USB_CLASS_PRINTER: |
printk(" Printer device class\n"); |
break; |
case USB_CLASS_MASS_STORAGE: |
printk(" Mass Storage device class\n"); |
break; |
case USB_CLASS_HUB: |
printk(" Hub device class\n"); |
break; |
case USB_CLASS_VENDOR_SPEC: |
printk(" Vendor class\n"); |
break; |
default: |
printk(" Unknown class\n"); |
} |
} |
void usb_show_config_descriptor(struct usb_config_descriptor *desc) |
{ |
printk("Configuration:\n"); |
printk(" bLength = %4d%s\n", desc->bLength, |
desc->bLength == USB_DT_CONFIG_SIZE ? "" : " (!!!)"); |
printk(" bDescriptorType = %02x\n", desc->bDescriptorType); |
printk(" wTotalLength = %04x\n", desc->wTotalLength); |
printk(" bNumInterfaces = %02x\n", desc->bNumInterfaces); |
printk(" bConfigurationValue = %02x\n", desc->bConfigurationValue); |
printk(" iConfiguration = %02x\n", desc->iConfiguration); |
printk(" bmAttributes = %02x\n", desc->bmAttributes); |
printk(" bMaxPower = %4dmA\n", desc->bMaxPower * 2); |
} |
void usb_show_interface_descriptor(struct usb_interface_descriptor *desc) |
{ |
printk(" Alternate Setting: %2d\n", desc->bAlternateSetting); |
printk(" bLength = %4d%s\n", desc->bLength, |
desc->bLength == USB_DT_INTERFACE_SIZE ? "" : " (!!!)"); |
printk(" bDescriptorType = %02x\n", desc->bDescriptorType); |
printk(" bInterfaceNumber = %02x\n", desc->bInterfaceNumber); |
printk(" bAlternateSetting = %02x\n", desc->bAlternateSetting); |
printk(" bNumEndpoints = %02x\n", desc->bNumEndpoints); |
printk(" bInterface Class:SubClass:Protocol = %02x:%02x:%02x\n", |
desc->bInterfaceClass, desc->bInterfaceSubClass, desc->bInterfaceProtocol); |
printk(" iInterface = %02x\n", desc->iInterface); |
} |
void usb_show_endpoint_descriptor(struct usb_endpoint_descriptor *desc) |
{ |
char *LengthCommentString = (desc->bLength == |
USB_DT_ENDPOINT_AUDIO_SIZE) ? " (Audio)" : (desc->bLength == |
USB_DT_ENDPOINT_SIZE) ? "" : " (!!!)"; |
char *EndpointType[4] = { "Control", "Isochronous", "Bulk", "Interrupt" }; |
printk(" Endpoint:\n"); |
printk(" bLength = %4d%s\n", |
desc->bLength, LengthCommentString); |
printk(" bDescriptorType = %02x\n", desc->bDescriptorType); |
printk(" bEndpointAddress = %02x (%s)\n", desc->bEndpointAddress, |
(desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == |
USB_ENDPOINT_XFER_CONTROL ? "i/o" : |
(desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? "in" : "out"); |
printk(" bmAttributes = %02x (%s)\n", desc->bmAttributes, |
EndpointType[USB_ENDPOINT_XFERTYPE_MASK & desc->bmAttributes]); |
printk(" wMaxPacketSize = %04x\n", desc->wMaxPacketSize); |
printk(" bInterval = %02x\n", desc->bInterval); |
/* Audio extensions to the endpoint descriptor */ |
if (desc->bLength == USB_DT_ENDPOINT_AUDIO_SIZE) { |
printk(" bRefresh = %02x\n", desc->bRefresh); |
printk(" bSynchAddress = %02x\n", desc->bSynchAddress); |
} |
} |
void usb_show_string(struct usb_device *dev, char *id, int index) |
{ |
char *buf; |
if (!index) |
return; |
if (!(buf = kmalloc(256, GFP_KERNEL))) |
return; |
if (usb_string(dev, index, buf, 256) > 0) |
dev_printk(KERN_INFO, &dev->dev, "%s: %s\n", id, buf); |
kfree(buf); |
} |
void usb_dump_urb (struct urb *urb) |
{ |
printk ("urb :%p\n", urb); |
printk ("dev :%p\n", urb->dev); |
printk ("pipe :%08X\n", urb->pipe); |
printk ("status :%d\n", urb->status); |
printk ("transfer_flags :%08X\n", urb->transfer_flags); |
printk ("transfer_buffer :%p\n", urb->transfer_buffer); |
printk ("transfer_buffer_length:%d\n", urb->transfer_buffer_length); |
printk ("actual_length :%d\n", urb->actual_length); |
printk ("setup_packet :%p\n", urb->setup_packet); |
printk ("start_frame :%d\n", urb->start_frame); |
printk ("number_of_packets :%d\n", urb->number_of_packets); |
printk ("interval :%d\n", urb->interval); |
printk ("error_count :%d\n", urb->error_count); |
printk ("context :%p\n", urb->context); |
printk ("complete :%p\n", urb->complete); |
} |
/shark/trunk/drivers/usb/core/hub.c |
---|
0,0 → 1,1383 |
/* |
* USB hub driver. |
* |
* (C) Copyright 1999 Linus Torvalds |
* (C) Copyright 1999 Johannes Erdfelt |
* (C) Copyright 1999 Gregory P. Smith |
* (C) Copyright 2001 Brad Hards (bhards@bigpond.net.au) |
* |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/kernel.h> |
#include <linux/errno.h> |
#include <linux/module.h> |
#include <linux/completion.h> |
#include <linux/sched.h> |
#include <linux/list.h> |
#include <linux/slab.h> |
#include <linux/smp_lock.h> |
#include <linux/ioctl.h> |
#ifdef CONFIG_USB_DEBUG |
#define DEBUG |
#else |
#undef DEBUG |
#endif |
#include <linux/usb.h> |
#include <linux/usbdevice_fs.h> |
#include <linux/suspend.h> |
#include <asm/semaphore.h> |
#include <asm/uaccess.h> |
#include <asm/byteorder.h> |
#include "hcd.h" |
#include "hub.h" |
/* Wakes up khubd */ |
static spinlock_t hub_event_lock = SPIN_LOCK_UNLOCKED; |
static DECLARE_MUTEX(usb_address0_sem); |
static LIST_HEAD(hub_event_list); /* List of hubs needing servicing */ |
static LIST_HEAD(hub_list); /* List of all hubs (for cleanup) */ |
static DECLARE_WAIT_QUEUE_HEAD(khubd_wait); |
static pid_t khubd_pid = 0; /* PID of khubd */ |
static DECLARE_COMPLETION(khubd_exited); |
//#ifdef DEBUG |
static inline char *portspeed (int portstatus) |
{ |
if (portstatus & (1 << USB_PORT_FEAT_HIGHSPEED)) |
return "480 Mb/s"; |
else if (portstatus & (1 << USB_PORT_FEAT_LOWSPEED)) |
return "1.5 Mb/s"; |
else |
return "12 Mb/s"; |
} |
//#endif |
/* for dev_info, dev_dbg, etc */ |
static inline struct device *hubdev (struct usb_device *dev) |
{ |
return &dev->actconfig->interface[0]->dev; |
} |
/* USB 2.0 spec Section 11.24.4.5 */ |
static int get_hub_descriptor(struct usb_device *dev, void *data, int size) |
{ |
return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), |
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB, |
USB_DT_HUB << 8, 0, data, size, HZ * USB_CTRL_GET_TIMEOUT); |
} |
/* |
* USB 2.0 spec Section 11.24.2.1 |
*/ |
static int clear_hub_feature(struct usb_device *dev, int feature) |
{ |
return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), |
USB_REQ_CLEAR_FEATURE, USB_RT_HUB, feature, 0, NULL, 0, HZ); |
} |
/* |
* USB 2.0 spec Section 11.24.2.2 |
* BUG: doesn't handle port indicator selector in high byte of wIndex |
*/ |
static int clear_port_feature(struct usb_device *dev, int port, int feature) |
{ |
return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), |
USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, port, NULL, 0, HZ); |
} |
/* |
* USB 2.0 spec Section 11.24.2.13 |
* BUG: doesn't handle port indicator selector in high byte of wIndex |
*/ |
static int set_port_feature(struct usb_device *dev, int port, int feature) |
{ |
return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), |
USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port, NULL, 0, HZ); |
} |
/* |
* USB 2.0 spec Section 11.24.2.6 |
*/ |
static int get_hub_status(struct usb_device *dev, |
struct usb_hub_status *data) |
{ |
return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), |
USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_HUB, 0, 0, |
data, sizeof(*data), HZ * USB_CTRL_GET_TIMEOUT); |
} |
/* |
* USB 2.0 spec Section 11.24.2.7 |
*/ |
static int get_port_status(struct usb_device *dev, int port, |
struct usb_port_status *data) |
{ |
return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), |
USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port, |
data, sizeof(*data), HZ * USB_CTRL_GET_TIMEOUT); |
} |
/* completion function, fires on port status changes and various faults */ |
static void hub_irq(struct urb *urb, struct pt_regs *regs) |
{ |
struct usb_hub *hub = (struct usb_hub *)urb->context; |
unsigned long flags; |
int status; |
switch (urb->status) { |
case -ENOENT: /* synchronous unlink */ |
case -ECONNRESET: /* async unlink */ |
case -ESHUTDOWN: /* hardware going away */ |
return; |
default: /* presumably an error */ |
/* Cause a hub reset after 10 consecutive errors */ |
dev_dbg (&hub->intf->dev, "transfer --> %d\n", urb->status); |
if ((++hub->nerrors < 10) || hub->error) |
goto resubmit; |
hub->error = urb->status; |
/* FALL THROUGH */ |
/* let khubd handle things */ |
case 0: /* we got data: port status changed */ |
break; |
} |
hub->nerrors = 0; |
/* Something happened, let khubd figure it out */ |
spin_lock_irqsave(&hub_event_lock, flags); |
if (list_empty(&hub->event_list)) { |
list_add(&hub->event_list, &hub_event_list); |
wake_up(&khubd_wait); |
} |
spin_unlock_irqrestore(&hub_event_lock, flags); |
resubmit: |
if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0 |
/* ENODEV means we raced disconnect() */ |
&& status != -ENODEV) |
dev_err (&hub->intf->dev, "resubmit --> %d\n", urb->status); |
} |
/* USB 2.0 spec Section 11.24.2.3 */ |
static inline int |
hub_clear_tt_buffer (struct usb_device *hub, u16 devinfo, u16 tt) |
{ |
return usb_control_msg (hub, usb_rcvctrlpipe (hub, 0), |
HUB_CLEAR_TT_BUFFER, USB_DIR_IN | USB_RECIP_OTHER, |
devinfo, tt, 0, 0, HZ); |
} |
/* |
* enumeration blocks khubd for a long time. we use keventd instead, since |
* long blocking there is the exception, not the rule. accordingly, HCDs |
* talking to TTs must queue control transfers (not just bulk and iso), so |
* both can talk to the same hub concurrently. |
*/ |
static void hub_tt_kevent (void *arg) |
{ |
struct usb_hub *hub = arg; |
unsigned long flags; |
spin_lock_irqsave (&hub->tt.lock, flags); |
while (!list_empty (&hub->tt.clear_list)) { |
struct list_head *temp; |
struct usb_tt_clear *clear; |
struct usb_device *dev; |
int status; |
temp = hub->tt.clear_list.next; |
clear = list_entry (temp, struct usb_tt_clear, clear_list); |
list_del (&clear->clear_list); |
/* drop lock so HCD can concurrently report other TT errors */ |
spin_unlock_irqrestore (&hub->tt.lock, flags); |
dev = interface_to_usbdev (hub->intf); |
status = hub_clear_tt_buffer (dev, clear->devinfo, clear->tt); |
spin_lock_irqsave (&hub->tt.lock, flags); |
if (status) |
err ("usb-%s-%s clear tt %d (%04x) error %d", |
dev->bus->bus_name, dev->devpath, |
clear->tt, clear->devinfo, status); |
kfree (clear); |
} |
spin_unlock_irqrestore (&hub->tt.lock, flags); |
} |
/** |
* usb_hub_tt_clear_buffer - clear control/bulk TT state in high speed hub |
* @dev: the device whose split transaction failed |
* @pipe: identifies the endpoint of the failed transaction |
* |
* High speed HCDs use this to tell the hub driver that some split control or |
* bulk transaction failed in a way that requires clearing internal state of |
* a transaction translator. This is normally detected (and reported) from |
* interrupt context. |
* |
* It may not be possible for that hub to handle additional full (or low) |
* speed transactions until that state is fully cleared out. |
*/ |
void usb_hub_tt_clear_buffer (struct usb_device *dev, int pipe) |
{ |
struct usb_tt *tt = dev->tt; |
unsigned long flags; |
struct usb_tt_clear *clear; |
/* we've got to cope with an arbitrary number of pending TT clears, |
* since each TT has "at least two" buffers that can need it (and |
* there can be many TTs per hub). even if they're uncommon. |
*/ |
if ((clear = kmalloc (sizeof *clear, SLAB_ATOMIC)) == 0) { |
err ("can't save CLEAR_TT_BUFFER state for hub at usb-%s-%s", |
dev->bus->bus_name, tt->hub->devpath); |
/* FIXME recover somehow ... RESET_TT? */ |
return; |
} |
/* info that CLEAR_TT_BUFFER needs */ |
clear->tt = tt->multi ? dev->ttport : 1; |
clear->devinfo = usb_pipeendpoint (pipe); |
clear->devinfo |= dev->devnum << 4; |
clear->devinfo |= usb_pipecontrol (pipe) |
? (USB_ENDPOINT_XFER_CONTROL << 11) |
: (USB_ENDPOINT_XFER_BULK << 11); |
if (usb_pipein (pipe)) |
clear->devinfo |= 1 << 15; |
/* tell keventd to clear state for this TT */ |
spin_lock_irqsave (&tt->lock, flags); |
list_add_tail (&clear->clear_list, &tt->clear_list); |
schedule_work (&tt->kevent); |
spin_unlock_irqrestore (&tt->lock, flags); |
} |
static void hub_power_on(struct usb_hub *hub) |
{ |
struct usb_device *dev; |
int i; |
/* Enable power to the ports */ |
dev_dbg(hubdev(interface_to_usbdev(hub->intf)), |
"enabling power on all ports\n"); |
dev = interface_to_usbdev(hub->intf); |
for (i = 0; i < hub->descriptor->bNbrPorts; i++) |
set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER); |
/* Wait for power to be enabled */ |
wait_ms(hub->descriptor->bPwrOn2PwrGood * 2); |
} |
static int hub_hub_status(struct usb_hub *hub, |
u16 *status, u16 *change) |
{ |
struct usb_device *dev = interface_to_usbdev (hub->intf); |
int ret; |
ret = get_hub_status(dev, &hub->status->hub); |
if (ret < 0) |
dev_err (hubdev (dev), |
"%s failed (err = %d)\n", __FUNCTION__, ret); |
else { |
*status = le16_to_cpu(hub->status->hub.wHubStatus); |
*change = le16_to_cpu(hub->status->hub.wHubChange); |
ret = 0; |
} |
return ret; |
} |
static int hub_configure(struct usb_hub *hub, |
struct usb_endpoint_descriptor *endpoint) |
{ |
struct usb_device *dev = interface_to_usbdev (hub->intf); |
struct device *hub_dev; |
u16 hubstatus, hubchange; |
unsigned int pipe; |
int maxp, ret; |
char *message; |
hub->buffer = usb_buffer_alloc(dev, sizeof(*hub->buffer), GFP_KERNEL, |
&hub->buffer_dma); |
if (!hub->buffer) { |
message = "can't allocate hub irq buffer"; |
ret = -ENOMEM; |
goto fail; |
} |
hub->status = kmalloc(sizeof(*hub->status), GFP_KERNEL); |
if (!hub->status) { |
message = "can't kmalloc hub status buffer"; |
ret = -ENOMEM; |
goto fail; |
} |
hub->descriptor = kmalloc(sizeof(*hub->descriptor), GFP_KERNEL); |
if (!hub->descriptor) { |
message = "can't kmalloc hub descriptor"; |
ret = -ENOMEM; |
goto fail; |
} |
/* Request the entire hub descriptor. |
* hub->descriptor can handle USB_MAXCHILDREN ports, |
* but the hub can/will return fewer bytes here. |
*/ |
ret = get_hub_descriptor(dev, hub->descriptor, |
sizeof(*hub->descriptor)); |
if (ret < 0) { |
message = "can't read hub descriptor"; |
goto fail; |
} else if (hub->descriptor->bNbrPorts > USB_MAXCHILDREN) { |
message = "hub has too many ports!"; |
ret = -ENODEV; |
goto fail; |
} |
hub_dev = hubdev(dev); |
dev->maxchild = hub->descriptor->bNbrPorts; |
dev_info (hub_dev, "%d port%s detected\n", dev->maxchild, |
(dev->maxchild == 1) ? "" : "s"); |
le16_to_cpus(&hub->descriptor->wHubCharacteristics); |
if (hub->descriptor->wHubCharacteristics & HUB_CHAR_COMPOUND) { |
int i; |
char portstr [USB_MAXCHILDREN + 1]; |
for (i = 0; i < dev->maxchild; i++) |
portstr[i] = hub->descriptor->DeviceRemovable |
[((i + 1) / 8)] & (1 << ((i + 1) % 8)) |
? 'F' : 'R'; |
portstr[dev->maxchild] = 0; |
dev_dbg(hub_dev, "compound device; port removable status: %s\n", portstr); |
} else |
dev_dbg(hub_dev, "standalone hub\n"); |
switch (hub->descriptor->wHubCharacteristics & HUB_CHAR_LPSM) { |
case 0x00: |
dev_dbg(hub_dev, "ganged power switching\n"); |
break; |
case 0x01: |
dev_dbg(hub_dev, "individual port power switching\n"); |
break; |
case 0x02: |
case 0x03: |
dev_dbg(hub_dev, "unknown reserved power switching mode\n"); |
break; |
} |
switch (hub->descriptor->wHubCharacteristics & HUB_CHAR_OCPM) { |
case 0x00: |
dev_dbg(hub_dev, "global over-current protection\n"); |
break; |
case 0x08: |
dev_dbg(hub_dev, "individual port over-current protection\n"); |
break; |
case 0x10: |
case 0x18: |
dev_dbg(hub_dev, "no over-current protection\n"); |
break; |
} |
spin_lock_init (&hub->tt.lock); |
INIT_LIST_HEAD (&hub->tt.clear_list); |
INIT_WORK (&hub->tt.kevent, hub_tt_kevent, hub); |
switch (dev->descriptor.bDeviceProtocol) { |
case 0: |
break; |
case 1: |
dev_dbg(hub_dev, "Single TT\n"); |
hub->tt.hub = dev; |
break; |
case 2: |
dev_dbg(hub_dev, "TT per port\n"); |
hub->tt.hub = dev; |
hub->tt.multi = 1; |
break; |
default: |
dev_dbg(hub_dev, "Unrecognized hub protocol %d\n", |
dev->descriptor.bDeviceProtocol); |
break; |
} |
switch (hub->descriptor->wHubCharacteristics & HUB_CHAR_TTTT) { |
case 0x00: |
if (dev->descriptor.bDeviceProtocol != 0) |
dev_dbg(hub_dev, "TT requires at most 8 FS bit times\n"); |
break; |
case 0x20: |
dev_dbg(hub_dev, "TT requires at most 16 FS bit times\n"); |
break; |
case 0x40: |
dev_dbg(hub_dev, "TT requires at most 24 FS bit times\n"); |
break; |
case 0x60: |
dev_dbg(hub_dev, "TT requires at most 32 FS bit times\n"); |
break; |
} |
dev_dbg(hub_dev, "Port indicators are %s supported\n", |
(hub->descriptor->wHubCharacteristics & HUB_CHAR_PORTIND) |
? "" : "not"); |
dev_dbg(hub_dev, "power on to power good time: %dms\n", |
hub->descriptor->bPwrOn2PwrGood * 2); |
dev_dbg(hub_dev, "hub controller current requirement: %dmA\n", |
hub->descriptor->bHubContrCurrent); |
ret = hub_hub_status(hub, &hubstatus, &hubchange); |
if (ret < 0) { |
message = "can't get hub status"; |
goto fail; |
} |
dev_dbg(hub_dev, "local power source is %s\n", |
(hubstatus & HUB_STATUS_LOCAL_POWER) |
? "lost (inactive)" : "good"); |
dev_dbg(hub_dev, "%sover-current condition exists\n", |
(hubstatus & HUB_STATUS_OVERCURRENT) ? "" : "no "); |
/* Start the interrupt endpoint */ |
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); |
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); |
if (maxp > sizeof(*hub->buffer)) |
maxp = sizeof(*hub->buffer); |
hub->urb = usb_alloc_urb(0, GFP_KERNEL); |
if (!hub->urb) { |
message = "couldn't allocate interrupt urb"; |
ret = -ENOMEM; |
goto fail; |
} |
usb_fill_int_urb(hub->urb, dev, pipe, *hub->buffer, maxp, hub_irq, |
hub, endpoint->bInterval); |
hub->urb->transfer_dma = hub->buffer_dma; |
hub->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; |
ret = usb_submit_urb(hub->urb, GFP_KERNEL); |
if (ret) { |
message = "couldn't submit status urb"; |
goto fail; |
} |
/* Wake up khubd */ |
wake_up(&khubd_wait); |
hub_power_on(hub); |
return 0; |
fail: |
dev_err (&hub->intf->dev, "config failed, %s (err %d)\n", |
message, ret); |
/* hub_disconnect() frees urb and descriptor */ |
return ret; |
} |
static void hub_disconnect(struct usb_interface *intf) |
{ |
struct usb_hub *hub = usb_get_intfdata (intf); |
unsigned long flags; |
if (!hub) |
return; |
usb_set_intfdata (intf, NULL); |
spin_lock_irqsave(&hub_event_lock, flags); |
/* Delete it and then reset it */ |
list_del(&hub->event_list); |
INIT_LIST_HEAD(&hub->event_list); |
list_del(&hub->hub_list); |
INIT_LIST_HEAD(&hub->hub_list); |
spin_unlock_irqrestore(&hub_event_lock, flags); |
down(&hub->khubd_sem); /* Wait for khubd to leave this hub alone. */ |
up(&hub->khubd_sem); |
/* assuming we used keventd, it must quiesce too */ |
if (hub->tt.hub) |
flush_scheduled_work (); |
if (hub->urb) { |
usb_unlink_urb(hub->urb); |
usb_free_urb(hub->urb); |
hub->urb = NULL; |
} |
if (hub->descriptor) { |
kfree(hub->descriptor); |
hub->descriptor = NULL; |
} |
if (hub->status) { |
kfree(hub->status); |
hub->status = NULL; |
} |
if (hub->buffer) { |
usb_buffer_free(interface_to_usbdev(intf), |
sizeof(*hub->buffer), hub->buffer, |
hub->buffer_dma); |
hub->buffer = NULL; |
} |
/* Free the memory */ |
kfree(hub); |
} |
static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) |
{ |
struct usb_host_interface *desc; |
struct usb_endpoint_descriptor *endpoint; |
struct usb_device *dev; |
struct usb_hub *hub; |
unsigned long flags; |
desc = intf->altsetting + intf->act_altsetting; |
dev = interface_to_usbdev(intf); |
/* Some hubs have a subclass of 1, which AFAICT according to the */ |
/* specs is not defined, but it works */ |
if ((desc->desc.bInterfaceSubClass != 0) && |
(desc->desc.bInterfaceSubClass != 1)) { |
descriptor_error: |
dev_err (&intf->dev, "bad descriptor, ignoring hub\n"); |
return -EIO; |
} |
/* Multiple endpoints? What kind of mutant ninja-hub is this? */ |
if (desc->desc.bNumEndpoints != 1) { |
goto descriptor_error; |
} |
endpoint = &desc->endpoint[0].desc; |
/* Output endpoint? Curiouser and curiouser.. */ |
if (!(endpoint->bEndpointAddress & USB_DIR_IN)) { |
goto descriptor_error; |
} |
/* If it's not an interrupt endpoint, we'd better punt! */ |
if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) |
!= USB_ENDPOINT_XFER_INT) { |
goto descriptor_error; |
return -EIO; |
} |
/* We found a hub */ |
dev_info (hubdev (dev), "USB hub found\n"); |
hub = kmalloc(sizeof(*hub), GFP_KERNEL); |
if (!hub) { |
err("couldn't kmalloc hub struct"); |
return -ENOMEM; |
} |
memset(hub, 0, sizeof(*hub)); |
INIT_LIST_HEAD(&hub->event_list); |
hub->intf = intf; |
init_MUTEX(&hub->khubd_sem); |
/* Record the new hub's existence */ |
spin_lock_irqsave(&hub_event_lock, flags); |
INIT_LIST_HEAD(&hub->hub_list); |
list_add(&hub->hub_list, &hub_list); |
spin_unlock_irqrestore(&hub_event_lock, flags); |
usb_set_intfdata (intf, hub); |
if (hub_configure(hub, endpoint) >= 0) |
return 0; |
hub_disconnect (intf); |
return -ENODEV; |
} |
static int |
hub_ioctl(struct usb_interface *intf, unsigned int code, void *user_data) |
{ |
struct usb_device *hub = interface_to_usbdev (intf); |
/* assert ifno == 0 (part of hub spec) */ |
switch (code) { |
case USBDEVFS_HUB_PORTINFO: { |
struct usbdevfs_hub_portinfo *info = user_data; |
unsigned long flags; |
int i; |
spin_lock_irqsave(&hub_event_lock, flags); |
if (hub->devnum <= 0) |
info->nports = 0; |
else { |
info->nports = hub->maxchild; |
for (i = 0; i < info->nports; i++) { |
if (hub->children[i] == NULL) |
info->port[i] = 0; |
else |
info->port[i] = |
hub->children[i]->devnum; |
} |
} |
spin_unlock_irqrestore(&hub_event_lock, flags); |
return info->nports + 1; |
} |
default: |
return -ENOSYS; |
} |
} |
static int hub_reset(struct usb_hub *hub) |
{ |
struct usb_device *dev = interface_to_usbdev(hub->intf); |
int i; |
/* Disconnect any attached devices */ |
for (i = 0; i < hub->descriptor->bNbrPorts; i++) { |
if (dev->children[i]) |
usb_disconnect(&dev->children[i]); |
} |
/* Attempt to reset the hub */ |
if (hub->urb) |
usb_unlink_urb(hub->urb); |
else |
return -1; |
if (usb_reset_device(dev)) |
return -1; |
hub->urb->dev = dev; |
if (usb_submit_urb(hub->urb, GFP_KERNEL)) |
return -1; |
hub_power_on(hub); |
return 0; |
} |
static void hub_start_disconnect(struct usb_device *dev) |
{ |
struct usb_device *parent = dev->parent; |
int i; |
/* Find the device pointer to disconnect */ |
if (parent) { |
for (i = 0; i < parent->maxchild; i++) { |
if (parent->children[i] == dev) { |
usb_disconnect(&parent->children[i]); |
return; |
} |
} |
} |
err("cannot disconnect hub %s", dev->devpath); |
} |
static int hub_port_status(struct usb_device *dev, int port, |
u16 *status, u16 *change) |
{ |
struct usb_hub *hub = usb_get_intfdata(dev->actconfig->interface[0]); |
int ret; |
if (!hub) |
return -ENODEV; |
ret = get_port_status(dev, port + 1, &hub->status->port); |
if (ret < 0) |
dev_err (hubdev (dev), |
"%s failed (err = %d)\n", __FUNCTION__, ret); |
else { |
*status = le16_to_cpu(hub->status->port.wPortStatus); |
*change = le16_to_cpu(hub->status->port.wPortChange); |
ret = 0; |
} |
return ret; |
} |
#define HUB_RESET_TRIES 5 |
#define HUB_PROBE_TRIES 2 |
#define HUB_ROOT_RESET_TIME 50 /* times are in msec */ |
#define HUB_SHORT_RESET_TIME 10 |
#define HUB_LONG_RESET_TIME 200 |
#define HUB_RESET_TIMEOUT 500 |
/* return: -1 on error, 0 on success, 1 on disconnect. */ |
static int hub_port_wait_reset(struct usb_device *hub, int port, |
struct usb_device *dev, unsigned int delay) |
{ |
int delay_time, ret; |
u16 portstatus; |
u16 portchange; |
for (delay_time = 0; |
delay_time < HUB_RESET_TIMEOUT; |
delay_time += delay) { |
/* wait to give the device a chance to reset */ |
wait_ms(delay); |
/* read and decode port status */ |
ret = hub_port_status(hub, port, &portstatus, &portchange); |
if (ret < 0) { |
return -1; |
} |
/* Device went away? */ |
if (!(portstatus & USB_PORT_STAT_CONNECTION)) |
return 1; |
/* bomb out completely if something weird happened */ |
if ((portchange & USB_PORT_STAT_C_CONNECTION)) |
return -1; |
/* if we`ve finished resetting, then break out of the loop */ |
if (!(portstatus & USB_PORT_STAT_RESET) && |
(portstatus & USB_PORT_STAT_ENABLE)) { |
if (portstatus & USB_PORT_STAT_HIGH_SPEED) |
dev->speed = USB_SPEED_HIGH; |
else if (portstatus & USB_PORT_STAT_LOW_SPEED) |
dev->speed = USB_SPEED_LOW; |
else |
dev->speed = USB_SPEED_FULL; |
return 0; |
} |
/* switch to the long delay after two short delay failures */ |
if (delay_time >= 2 * HUB_SHORT_RESET_TIME) |
delay = HUB_LONG_RESET_TIME; |
dev_dbg (hubdev (hub), |
"port %d not reset yet, waiting %dms\n", |
port + 1, delay); |
} |
return -1; |
} |
/* return: -1 on error, 0 on success, 1 on disconnect. */ |
static int hub_port_reset(struct usb_device *hub, int port, |
struct usb_device *dev, unsigned int delay) |
{ |
int i, status; |
/* Reset the port */ |
for (i = 0; i < HUB_RESET_TRIES; i++) { |
set_port_feature(hub, port + 1, USB_PORT_FEAT_RESET); |
/* return on disconnect or reset */ |
status = hub_port_wait_reset(hub, port, dev, delay); |
if (status != -1) { |
clear_port_feature(hub, |
port + 1, USB_PORT_FEAT_C_RESET); |
dev->state = status |
? USB_STATE_NOTATTACHED |
: USB_STATE_DEFAULT; |
return status; |
} |
dev_dbg (hubdev (hub), |
"port %d not enabled, trying reset again...\n", |
port + 1); |
delay = HUB_LONG_RESET_TIME; |
} |
dev_err (hubdev (hub), |
"Cannot enable port %i. Maybe the USB cable is bad?\n", |
port + 1); |
return -1; |
} |
int hub_port_disable(struct usb_device *hub, int port) |
{ |
int ret; |
ret = clear_port_feature(hub, port + 1, USB_PORT_FEAT_ENABLE); |
if (ret) |
dev_err(hubdev(hub), "cannot disable port %d (err = %d)\n", |
port + 1, ret); |
return ret; |
} |
/* USB 2.0 spec, 7.1.7.3 / fig 7-29: |
* |
* Between connect detection and reset signaling there must be a delay |
* of 100ms at least for debounce and power-settling. The corresponding |
* timer shall restart whenever the downstream port detects a disconnect. |
* |
* Apparently there are some bluetooth and irda-dongles and a number |
* of low-speed devices which require longer delays of about 200-400ms. |
* Not covered by the spec - but easy to deal with. |
* |
* This implementation uses 400ms minimum debounce timeout and checks |
* every 25ms for transient disconnects to restart the delay. |
*/ |
#define HUB_DEBOUNCE_TIMEOUT 400 |
#define HUB_DEBOUNCE_STEP 25 |
#define HUB_DEBOUNCE_STABLE 4 |
/* return: -1 on error, 0 on success, 1 on disconnect. */ |
static int hub_port_debounce(struct usb_device *hub, int port) |
{ |
int ret; |
int delay_time, stable_count; |
u16 portchange, portstatus; |
unsigned connection; |
connection = 0; |
stable_count = 0; |
for (delay_time = 0; delay_time < HUB_DEBOUNCE_TIMEOUT; delay_time += HUB_DEBOUNCE_STEP) { |
wait_ms(HUB_DEBOUNCE_STEP); |
ret = hub_port_status(hub, port, &portstatus, &portchange); |
if (ret < 0) |
return -1; |
if ((portstatus & USB_PORT_STAT_CONNECTION) == connection) { |
if (connection) { |
if (++stable_count == HUB_DEBOUNCE_STABLE) |
break; |
} |
} else { |
stable_count = 0; |
} |
connection = portstatus & USB_PORT_STAT_CONNECTION; |
if ((portchange & USB_PORT_STAT_C_CONNECTION)) { |
clear_port_feature(hub, port+1, USB_PORT_FEAT_C_CONNECTION); |
} |
} |
dev_dbg (hubdev (hub), |
"debounce: port %d: delay %dms stable %d status 0x%x\n", |
port + 1, delay_time, stable_count, portstatus); |
return ((portstatus&USB_PORT_STAT_CONNECTION)) ? 0 : 1; |
} |
static void hub_port_connect_change(struct usb_hub *hubstate, int port, |
u16 portstatus, u16 portchange) |
{ |
struct usb_device *hub = interface_to_usbdev(hubstate->intf); |
struct usb_device *dev; |
unsigned int delay = HUB_SHORT_RESET_TIME; |
int i; |
dev_dbg (&hubstate->intf->dev, |
"port %d, status %x, change %x, %s\n", |
port + 1, portstatus, portchange, portspeed (portstatus)); |
/* Clear the connection change status */ |
clear_port_feature(hub, port + 1, USB_PORT_FEAT_C_CONNECTION); |
/* Disconnect any existing devices under this port */ |
if (hub->children[port]) |
usb_disconnect(&hub->children[port]); |
/* Return now if nothing is connected */ |
if (!(portstatus & USB_PORT_STAT_CONNECTION)) { |
if (portstatus & USB_PORT_STAT_ENABLE) |
hub_port_disable(hub, port); |
return; |
} |
if (hub_port_debounce(hub, port)) { |
dev_err (&hubstate->intf->dev, |
"connect-debounce failed, port %d disabled\n", |
port+1); |
hub_port_disable(hub, port); |
return; |
} |
/* root hub ports have a slightly longer reset period |
* (from USB 2.0 spec, section 7.1.7.5) |
*/ |
if (!hub->parent) |
delay = HUB_ROOT_RESET_TIME; |
/* Some low speed devices have problems with the quick delay, so */ |
/* be a bit pessimistic with those devices. RHbug #23670 */ |
if (portstatus & USB_PORT_STAT_LOW_SPEED) |
delay = HUB_LONG_RESET_TIME; |
down(&usb_address0_sem); |
for (i = 0; i < HUB_PROBE_TRIES; i++) { |
struct usb_device *pdev; |
int len; |
/* Allocate a new device struct */ |
dev = usb_alloc_dev(hub, hub->bus); |
if (!dev) { |
dev_err (&hubstate->intf->dev, |
"couldn't allocate usb_device\n"); |
break; |
} |
dev->state = USB_STATE_POWERED; |
/* Reset the device, and detect its speed */ |
if (hub_port_reset(hub, port, dev, delay)) { |
usb_put_dev(dev); |
break; |
} |
/* Find a new address for it */ |
usb_choose_address(dev); |
/* Set up TT records, if needed */ |
if (hub->tt) { |
dev->tt = hub->tt; |
dev->ttport = hub->ttport; |
} else if (dev->speed != USB_SPEED_HIGH |
&& hub->speed == USB_SPEED_HIGH) { |
dev->tt = &hubstate->tt; |
dev->ttport = port + 1; |
} |
/* Save readable and stable topology id, distinguishing |
* devices by location for diagnostics, tools, etc. The |
* string is a path along hub ports, from the root. Each |
* device's id will be stable until USB is re-cabled, and |
* hubs are often labeled with these port numbers. |
* |
* Initial size: ".NN" times five hubs + NUL = 16 bytes max |
* (quite rare, since most hubs have 4-6 ports). |
*/ |
pdev = dev->parent; |
if (pdev->devpath [0] != '0') /* parent not root? */ |
len = snprintf26(dev->devpath, sizeof dev->devpath, |
"%s.%d", pdev->devpath, port + 1); |
/* root == "0", root port 2 == "2", port 3 that hub "2.3" */ |
else |
len = snprintf26(dev->devpath, sizeof dev->devpath, |
"%d", port + 1); |
if (len == sizeof dev->devpath) |
dev_err (&hubstate->intf->dev, |
"devpath size! usb/%03d/%03d path %s\n", |
dev->bus->busnum, dev->devnum, dev->devpath); |
dev_info (&hubstate->intf->dev, |
"new USB device on port %d, assigned address %d\n", |
port + 1, dev->devnum); |
/* put the device in the global device tree. the hub port |
* is the "bus_id"; hubs show in hierarchy like bridges |
*/ |
dev->dev.parent = dev->parent->dev.parent->parent; |
/* Run it through the hoops (find a driver, etc) */ |
if (!usb_new_device(dev, &hub->dev)) { |
hub->children[port] = dev; |
goto done; |
} |
/* Free the configuration if there was an error */ |
usb_put_dev(dev); |
/* Switch to a long reset time */ |
delay = HUB_LONG_RESET_TIME; |
} |
hub_port_disable(hub, port); |
done: |
up(&usb_address0_sem); |
} |
static void hub_events(void) |
{ |
unsigned long flags; |
struct list_head *tmp; |
struct usb_device *dev; |
struct usb_hub *hub; |
u16 hubstatus; |
u16 hubchange; |
u16 portstatus; |
u16 portchange; |
int i, ret; |
/* |
* We restart the list every time to avoid a deadlock with |
* deleting hubs downstream from this one. This should be |
* safe since we delete the hub from the event list. |
* Not the most efficient, but avoids deadlocks. |
*/ |
while (1) { |
spin_lock_irqsave(&hub_event_lock, flags); |
if (list_empty(&hub_event_list)) |
break; |
/* Grab the next entry from the beginning of the list */ |
tmp = hub_event_list.next; |
hub = list_entry(tmp, struct usb_hub, event_list); |
dev = interface_to_usbdev(hub->intf); |
list_del_init(tmp); |
//** if (unlikely(down_trylock(&hub->khubd_sem))) |
//** BUG(); /* never blocks, we were on list */ |
spin_unlock_irqrestore(&hub_event_lock, flags); |
if (hub->error) { |
dev_dbg (&hub->intf->dev, "resetting for error %d\n", |
hub->error); |
if (hub_reset(hub)) { |
dev_dbg (&hub->intf->dev, |
"can't reset; disconnecting\n"); |
up(&hub->khubd_sem); |
hub_start_disconnect(dev); |
continue; |
} |
hub->nerrors = 0; |
hub->error = 0; |
} |
for (i = 0; i < hub->descriptor->bNbrPorts; i++) { |
ret = hub_port_status(dev, i, &portstatus, &portchange); |
if (ret < 0) { |
continue; |
} |
if (portchange & USB_PORT_STAT_C_CONNECTION) { |
hub_port_connect_change(hub, i, portstatus, portchange); |
} else if (portchange & USB_PORT_STAT_C_ENABLE) { |
dev_dbg (hubdev (dev), |
"port %d enable change, status %x\n", |
i + 1, portstatus); |
clear_port_feature(dev, |
i + 1, USB_PORT_FEAT_C_ENABLE); |
/* |
* EM interference sometimes causes badly |
* shielded USB devices to be shutdown by |
* the hub, this hack enables them again. |
* Works at least with mouse driver. |
*/ |
if (!(portstatus & USB_PORT_STAT_ENABLE) |
&& (portstatus & USB_PORT_STAT_CONNECTION) |
&& (dev->children[i])) { |
dev_err (&hub->intf->dev, |
"port %i " |
"disabled by hub (EMI?), " |
"re-enabling...", |
i + 1); |
hub_port_connect_change(hub, |
i, portstatus, portchange); |
} |
} |
if (portchange & USB_PORT_STAT_C_SUSPEND) { |
dev_dbg (&hub->intf->dev, |
"suspend change on port %d\n", |
i + 1); |
clear_port_feature(dev, |
i + 1, USB_PORT_FEAT_C_SUSPEND); |
} |
if (portchange & USB_PORT_STAT_C_OVERCURRENT) { |
dev_err (&hub->intf->dev, |
"over-current change on port %d\n", |
i + 1); |
clear_port_feature(dev, |
i + 1, USB_PORT_FEAT_C_OVER_CURRENT); |
hub_power_on(hub); |
} |
if (portchange & USB_PORT_STAT_C_RESET) { |
dev_dbg (&hub->intf->dev, |
"reset change on port %d\n", |
i + 1); |
clear_port_feature(dev, |
i + 1, USB_PORT_FEAT_C_RESET); |
} |
} /* end for i */ |
/* deal with hub status changes */ |
if (hub_hub_status(hub, &hubstatus, &hubchange) < 0) |
dev_err (&hub->intf->dev, "get_hub_status failed\n"); |
else { |
if (hubchange & HUB_CHANGE_LOCAL_POWER) { |
dev_dbg (&hub->intf->dev, "power change\n"); |
clear_hub_feature(dev, C_HUB_LOCAL_POWER); |
} |
if (hubchange & HUB_CHANGE_OVERCURRENT) { |
dev_dbg (&hub->intf->dev, "overcurrent change\n"); |
wait_ms(500); /* Cool down */ |
clear_hub_feature(dev, C_HUB_OVER_CURRENT); |
hub_power_on(hub); |
} |
} |
up(&hub->khubd_sem); |
} /* end while (1) */ |
spin_unlock_irqrestore(&hub_event_lock, flags); |
} |
static int hub_thread(void *__hub) |
{ |
/* |
* This thread doesn't need any user-level access, |
* so get rid of all our resources |
*/ |
daemonize("khubd"); |
allow_signal(SIGKILL); |
/* Send me a signal to get me die (for debugging) */ |
do { |
hub_events(); |
wait_event_interruptible(khubd_wait, !list_empty(&hub_event_list)); |
// if (current->flags & PF_FREEZE) |
// refrigerator(PF_IOTHREAD); |
} while (!signal_pending(current)); |
printk(KERN_DEBUG "File: %s @hub_thread_exit\n", __FILE__); |
dbg("hub_thread exiting"); |
complete_and_exit(&khubd_exited, 0); |
return 0; |
} |
static struct usb_device_id hub_id_table [] = { |
{ .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS, |
.bDeviceClass = USB_CLASS_HUB}, |
{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, |
.bInterfaceClass = USB_CLASS_HUB}, |
{ } /* Terminating entry */ |
}; |
MODULE_DEVICE_TABLE (usb, hub_id_table); |
static struct usb_driver hub_driver = { |
.owner = THIS_MODULE, |
.name = "hub", |
.probe = hub_probe, |
.disconnect = hub_disconnect, |
.ioctl = hub_ioctl, |
.id_table = hub_id_table, |
}; |
/* |
* This should be a separate module. |
*/ |
int usb_hub_init(void) |
{ |
pid_t pid; |
if (usb_register(&hub_driver) < 0) { |
err("Unable to register USB hub driver"); |
return -1; |
} |
pid = kernel_thread(hub_thread, NULL, CLONE_KERNEL); |
if (pid >= 0) { |
khubd_pid = pid; |
return 0; |
} |
/* Fall through if kernel_thread failed */ |
usb_deregister(&hub_driver); |
err("failed to start hub_thread"); |
return -1; |
} |
void usb_hub_cleanup(void) |
{ |
int ret; |
/* Kill the thread */ |
ret = kill_proc(khubd_pid, SIGKILL, 1); |
wait_for_completion(&khubd_exited); |
/* |
* Hub resources are freed for us by usb_deregister. It calls |
* usb_driver_purge on every device which in turn calls that |
* devices disconnect function if it is using this driver. |
* The hub_disconnect function takes care of releasing the |
* individual hub resources. -greg |
*/ |
usb_deregister(&hub_driver); |
} /* usb_hub_cleanup() */ |
/* |
* WARNING - If a driver calls usb_reset_device, you should simulate a |
* disconnect() and probe() for other interfaces you doesn't claim. This |
* is left up to the driver writer right now. This insures other drivers |
* have a chance to re-setup their interface. |
* |
* Take a look at proc_resetdevice in devio.c for some sample code to |
* do this. |
* Use this only from within your probe function, otherwise use |
* usb_reset_device() below, which ensure proper locking |
*/ |
int usb_physical_reset_device(struct usb_device *dev) |
{ |
struct usb_device *parent = dev->parent; |
struct usb_device_descriptor *descriptor; |
int i, ret, port = -1; |
if (!parent) { |
err("attempting to reset root hub!"); |
return -EINVAL; |
} |
for (i = 0; i < parent->maxchild; i++) |
if (parent->children[i] == dev) { |
port = i; |
break; |
} |
if (port < 0) |
return -ENOENT; |
descriptor = kmalloc(sizeof *descriptor, GFP_NOIO); |
if (!descriptor) { |
return -ENOMEM; |
} |
down(&usb_address0_sem); |
/* Send a reset to the device */ |
if (hub_port_reset(parent, port, dev, HUB_SHORT_RESET_TIME)) { |
hub_port_disable(parent, port); |
up(&usb_address0_sem); |
kfree(descriptor); |
return(-ENODEV); |
} |
/* Reprogram the Address */ |
ret = usb_set_address(dev); |
if (ret < 0) { |
err("USB device not accepting new address (error=%d)", ret); |
hub_port_disable(parent, port); |
up(&usb_address0_sem); |
kfree(descriptor); |
return ret; |
} |
/* Let the SET_ADDRESS settle */ |
wait_ms(10); |
up(&usb_address0_sem); |
/* |
* Now we fetch the configuration descriptors for the device and |
* see if anything has changed. If it has, we dump the current |
* parsed descriptors and reparse from scratch. Then we leave |
* the device alone for the caller to finish setting up. |
* |
* If nothing changed, we reprogram the configuration and then |
* the alternate settings. |
*/ |
ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, descriptor, |
sizeof(*descriptor)); |
if (ret < 0) { |
kfree(descriptor); |
return ret; |
} |
le16_to_cpus(&descriptor->bcdUSB); |
le16_to_cpus(&descriptor->idVendor); |
le16_to_cpus(&descriptor->idProduct); |
le16_to_cpus(&descriptor->bcdDevice); |
if (memcmp(&dev->descriptor, descriptor, sizeof(*descriptor))) { |
kfree(descriptor); |
usb_destroy_configuration(dev); |
ret = usb_get_device_descriptor(dev); |
if (ret < sizeof(dev->descriptor)) { |
if (ret < 0) |
err("unable to get device %s descriptor " |
"(error=%d)", dev->devpath, ret); |
else |
err("USB device %s descriptor short read " |
"(expected %Zi, got %i)", |
dev->devpath, |
sizeof(dev->descriptor), ret); |
clear_bit(dev->devnum, dev->bus->devmap.devicemap); |
dev->devnum = -1; |
return -EIO; |
} |
ret = usb_get_configuration(dev); |
if (ret < 0) { |
err("unable to get configuration (error=%d)", ret); |
usb_destroy_configuration(dev); |
clear_bit(dev->devnum, dev->bus->devmap.devicemap); |
dev->devnum = -1; |
return 1; |
} |
usb_set_configuration(dev, dev->config[0].desc.bConfigurationValue); |
return 1; |
} |
kfree(descriptor); |
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), |
USB_REQ_SET_CONFIGURATION, 0, |
dev->actconfig->desc.bConfigurationValue, 0, |
NULL, 0, HZ * USB_CTRL_SET_TIMEOUT); |
if (ret < 0) { |
err("failed to set dev %s active configuration (error=%d)", |
dev->devpath, ret); |
return ret; |
} |
dev->state = USB_STATE_CONFIGURED; |
for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { |
struct usb_interface *intf = dev->actconfig->interface[i]; |
struct usb_interface_descriptor *as; |
as = &intf->altsetting[intf->act_altsetting].desc; |
ret = usb_set_interface(dev, as->bInterfaceNumber, |
as->bAlternateSetting); |
if (ret < 0) { |
err("failed to set active alternate setting " |
"for dev %s interface %d (error=%d)", |
dev->devpath, i, ret); |
return ret; |
} |
} |
return 0; |
} |
int usb_reset_device(struct usb_device *udev) |
{ |
struct device *gdev = &udev->dev; |
int r; |
// down_read(&gdev->bus->subsys.rwsem); |
r = usb_physical_reset_device(udev); |
// up_read(&gdev->bus->subsys.rwsem); |
return r; |
} |
/shark/trunk/drivers/usb/core/file.c |
---|
0,0 → 1,242 |
/* |
* drivers/usb/file.c |
* |
* (C) Copyright Linus Torvalds 1999 |
* (C) Copyright Johannes Erdfelt 1999-2001 |
* (C) Copyright Andreas Gal 1999 |
* (C) Copyright Gregory P. Smith 1999 |
* (C) Copyright Deti Fliegl 1999 (new USB architecture) |
* (C) Copyright Randy Dunlap 2000 |
* (C) Copyright David Brownell 2000-2001 (kernel hotplug, usb_device_id, |
more docs, etc) |
* (C) Copyright Yggdrasil Computing, Inc. 2000 |
* (usb_device_id matching changes by Adam J. Richter) |
* (C) Copyright Greg Kroah-Hartman 2002-2003 |
* |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/module.h> |
#include <linux/devfs_fs_kernel.h> |
#include <linux/spinlock.h> |
#include <linux/errno.h> |
#ifdef CONFIG_USB_DEBUG |
#define DEBUG |
#else |
#undef DEBUG |
#endif |
#include <linux/usb.h> |
#define MAX_USB_MINORS 256 |
static struct file_operations *usb_minors[MAX_USB_MINORS]; |
static spinlock_t minor_lock = SPIN_LOCK_UNLOCKED; |
static int usb_open(struct inode * inode, struct file * file) |
{ |
int minor = iminor(inode); |
struct file_operations *c; |
int err = -ENODEV; |
struct file_operations *old_fops, *new_fops = NULL; |
spin_lock (&minor_lock); |
c = usb_minors[minor]; |
if (!c || !(new_fops = fops_get(c))) { |
spin_unlock(&minor_lock); |
return err; |
} |
spin_unlock(&minor_lock); |
old_fops = file->f_op; |
file->f_op = new_fops; |
/* Curiouser and curiouser... NULL ->open() as "no device" ? */ |
if (file->f_op->open) |
err = file->f_op->open(inode,file); |
if (err) { |
fops_put(file->f_op); |
file->f_op = fops_get(old_fops); |
} |
fops_put(old_fops); |
return err; |
} |
static struct file_operations usb_fops = { |
.owner = THIS_MODULE, |
.open = usb_open, |
}; |
static void release_usb_class_dev(struct class_device *class_dev) |
{ |
dbg("%s - %s", __FUNCTION__, class_dev->class_id); |
kfree(class_dev); |
} |
static struct class usb_class = { |
.name = "usb", |
.release = &release_usb_class_dev, |
}; |
int usb_major_init(void) |
{ |
if (register_chrdev(USB_MAJOR, "usb", &usb_fops)) { |
err("unable to get major %d for usb devices", USB_MAJOR); |
return -EBUSY; |
} |
devfs_mk_dir("usb"); |
class_register(&usb_class); |
return 0; |
} |
void usb_major_cleanup(void) |
{ |
class_unregister(&usb_class); |
devfs_remove("usb"); |
unregister_chrdev(USB_MAJOR, "usb"); |
} |
static ssize_t show_dev(struct class_device *class_dev, char *buf) |
{ |
int minor = (int)(long)class_get_devdata(class_dev); |
return print_dev_t(buf, MKDEV(USB_MAJOR, minor)); |
} |
static CLASS_DEVICE_ATTR(dev, S_IRUGO, show_dev, NULL); |
/** |
* usb_register_dev - register a USB device, and ask for a minor number |
* @intf: pointer to the usb_interface that is being registered |
* @class_driver: pointer to the usb_class_driver for this device |
* |
* This should be called by all USB drivers that use the USB major number. |
* If CONFIG_USB_DYNAMIC_MINORS is enabled, the minor number will be |
* dynamically allocated out of the list of available ones. If it is not |
* enabled, the minor number will be based on the next available free minor, |
* starting at the class_driver->minor_base. |
* |
* This function also creates the devfs file for the usb device, if devfs |
* is enabled, and creates a usb class device in the sysfs tree. |
* |
* usb_deregister_dev() must be called when the driver is done with |
* the minor numbers given out by this function. |
* |
* Returns -EINVAL if something bad happens with trying to register a |
* device, and 0 on success. |
*/ |
int usb_register_dev(struct usb_interface *intf, |
struct usb_class_driver *class_driver) |
{ |
int retval = -EINVAL; |
int minor_base = class_driver->minor_base; |
int minor = 0; |
char name[BUS_ID_SIZE]; |
struct class_device *class_dev; |
char *temp; |
#ifdef CONFIG_USB_DYNAMIC_MINORS |
/* |
* We don't care what the device tries to start at, we want to start |
* at zero to pack the devices into the smallest available space with |
* no holes in the minor range. |
*/ |
minor_base = 0; |
#endif |
intf->minor = -1; |
dbg ("looking for a minor, starting at %d", minor_base); |
if (class_driver->fops == NULL) |
goto exit; |
spin_lock (&minor_lock); |
for (minor = minor_base; minor < MAX_USB_MINORS; ++minor) { |
if (usb_minors[minor]) |
continue; |
usb_minors[minor] = class_driver->fops; |
retval = 0; |
break; |
} |
spin_unlock (&minor_lock); |
if (retval) |
goto exit; |
intf->minor = minor; |
/* handle the devfs registration */ |
snprintf26(name, BUS_ID_SIZE, class_driver->name, minor - minor_base); |
devfs_mk_cdev(MKDEV(USB_MAJOR, minor), class_driver->mode, name); |
/* create a usb class device for this usb interface */ |
class_dev = kmalloc(sizeof(*class_dev), GFP_KERNEL); |
if (class_dev) { |
memset(class_dev, 0x00, sizeof(struct class_device)); |
class_dev->class = &usb_class; |
class_dev->dev = &intf->dev; |
temp = strrchr(name, '/'); |
if (temp && (temp[1] != 0x00)) |
++temp; |
else |
temp = name; |
snprintf26(class_dev->class_id, BUS_ID_SIZE, "%s", temp); |
class_set_devdata(class_dev, (void *)(long)intf->minor); |
class_device_register(class_dev); |
class_device_create_file(class_dev, &class_device_attr_dev); |
intf->class_dev = class_dev; |
} |
exit: |
return retval; |
} |
EXPORT_SYMBOL(usb_register_dev); |
/** |
* usb_deregister_dev - deregister a USB device's dynamic minor. |
* @intf: pointer to the usb_interface that is being deregistered |
* @class_driver: pointer to the usb_class_driver for this device |
* |
* Used in conjunction with usb_register_dev(). This function is called |
* when the USB driver is finished with the minor numbers gotten from a |
* call to usb_register_dev() (usually when the device is disconnected |
* from the system.) |
* |
* This function also cleans up the devfs file for the usb device, if devfs |
* is enabled, and removes the usb class device from the sysfs tree. |
* |
* This should be called by all drivers that use the USB major number. |
*/ |
void usb_deregister_dev(struct usb_interface *intf, |
struct usb_class_driver *class_driver) |
{ |
int minor_base = class_driver->minor_base; |
char name[BUS_ID_SIZE]; |
#ifdef CONFIG_USB_DYNAMIC_MINORS |
minor_base = 0; |
#endif |
if (intf->minor == -1) |
return; |
dbg ("removing %d minor", intf->minor); |
spin_lock (&minor_lock); |
usb_minors[intf->minor] = NULL; |
spin_unlock (&minor_lock); |
snprintf26(name, BUS_ID_SIZE, class_driver->name, intf->minor - minor_base); |
devfs_remove (name); |
if (intf->class_dev) { |
class_device_unregister(intf->class_dev); |
intf->class_dev = NULL; |
} |
intf->minor = -1; |
} |
EXPORT_SYMBOL(usb_deregister_dev); |
/shark/trunk/drivers/usb/core/devices.c |
---|
0,0 → 1,673 |
/* |
* devices.c |
* (C) Copyright 1999 Randy Dunlap. |
* (C) Copyright 1999,2000 Thomas Sailer <sailer@ife.ee.ethz.ch>. (proc file per device) |
* (C) Copyright 1999 Deti Fliegl (new USB architecture) |
* |
* 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 |
* |
************************************************************* |
* |
* <mountpoint>/devices contains USB topology, device, config, class, |
* interface, & endpoint data. |
* |
* I considered using /proc/bus/usb/devices/device# for each device |
* as it is attached or detached, but I didn't like this for some |
* reason -- maybe it's just too deep of a directory structure. |
* I also don't like looking in multiple places to gather and view |
* the data. Having only one file for ./devices also prevents race |
* conditions that could arise if a program was reading device info |
* for devices that are being removed (unplugged). (That is, the |
* program may find a directory for devnum_12 then try to open it, |
* but it was just unplugged, so the directory is now deleted. |
* But programs would just have to be prepared for situations like |
* this in any plug-and-play environment.) |
* |
* 1999-12-16: Thomas Sailer <sailer@ife.ee.ethz.ch> |
* Converted the whole proc stuff to real |
* read methods. Now not the whole device list needs to fit |
* into one page, only the device list for one bus. |
* Added a poll method to /proc/bus/usb/devices, to wake |
* up an eventual usbd |
* 2000-01-04: Thomas Sailer <sailer@ife.ee.ethz.ch> |
* Turned into its own filesystem |
* 2000-07-05: Ashley Montanaro <ashley@compsoc.man.ac.uk> |
* Converted file reading routine to dump to buffer once |
* per device, not per bus |
* |
* $Id: devices.c,v 1.1 2004-09-13 15:04:47 giacomo Exp $ |
*/ |
#include <linux/fs.h> |
#include <linux/mm.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/usb.h> |
#include <linux/smp_lock.h> |
#include <linux/usbdevice_fs.h> |
#include <asm/uaccess.h> |
#include "hcd.h" |
#define MAX_TOPO_LEVEL 6 |
/* Define ALLOW_SERIAL_NUMBER if you want to see the serial number of devices */ |
#define ALLOW_SERIAL_NUMBER |
static char *format_topo = |
/* T: Bus=dd Lev=dd Prnt=dd Port=dd Cnt=dd Dev#=ddd Spd=ddd MxCh=dd */ |
"\nT: Bus=%2.2d Lev=%2.2d Prnt=%2.2d Port=%2.2d Cnt=%2.2d Dev#=%3d Spd=%3s MxCh=%2d\n"; |
static char *format_string_manufacturer = |
/* S: Manufacturer=xxxx */ |
"S: Manufacturer=%.100s\n"; |
static char *format_string_product = |
/* S: Product=xxxx */ |
"S: Product=%.100s\n"; |
#ifdef ALLOW_SERIAL_NUMBER |
static char *format_string_serialnumber = |
/* S: SerialNumber=xxxx */ |
"S: SerialNumber=%.100s\n"; |
#endif |
static char *format_bandwidth = |
/* B: Alloc=ddd/ddd us (xx%), #Int=ddd, #Iso=ddd */ |
"B: Alloc=%3d/%3d us (%2d%%), #Int=%3d, #Iso=%3d\n"; |
static char *format_device1 = |
/* D: Ver=xx.xx Cls=xx(sssss) Sub=xx Prot=xx MxPS=dd #Cfgs=dd */ |
"D: Ver=%2x.%02x Cls=%02x(%-5s) Sub=%02x Prot=%02x MxPS=%2d #Cfgs=%3d\n"; |
static char *format_device2 = |
/* P: Vendor=xxxx ProdID=xxxx Rev=xx.xx */ |
"P: Vendor=%04x ProdID=%04x Rev=%2x.%02x\n"; |
static char *format_config = |
/* C: #Ifs=dd Cfg#=dd Atr=xx MPwr=dddmA */ |
"C:%c #Ifs=%2d Cfg#=%2d Atr=%02x MxPwr=%3dmA\n"; |
static char *format_iface = |
/* I: If#=dd Alt=dd #EPs=dd Cls=xx(sssss) Sub=xx Prot=xx Driver=xxxx*/ |
"I: If#=%2d Alt=%2d #EPs=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x Driver=%s\n"; |
static char *format_endpt = |
/* E: Ad=xx(s) Atr=xx(ssss) MxPS=dddd Ivl=D?s */ |
"E: Ad=%02x(%c) Atr=%02x(%-4s) MxPS=%4d Ivl=%d%cs\n"; |
/* |
* Need access to the driver and USB bus lists. |
* extern struct list_head usb_bus_list; |
* However, these will come from functions that return ptrs to each of them. |
*/ |
static DECLARE_WAIT_QUEUE_HEAD(deviceconndiscwq); |
static unsigned int conndiscevcnt = 0; |
/* this struct stores the poll state for <mountpoint>/devices pollers */ |
struct usb_device_status { |
unsigned int lastev; |
}; |
struct class_info { |
int class; |
char *class_name; |
}; |
static const struct class_info clas_info[] = |
{ /* max. 5 chars. per name string */ |
{USB_CLASS_PER_INTERFACE, ">ifc"}, |
{USB_CLASS_AUDIO, "audio"}, |
{USB_CLASS_COMM, "comm."}, |
{USB_CLASS_HID, "HID"}, |
{USB_CLASS_HUB, "hub"}, |
{USB_CLASS_PHYSICAL, "PID"}, |
{USB_CLASS_PRINTER, "print"}, |
{USB_CLASS_MASS_STORAGE, "stor."}, |
{USB_CLASS_CDC_DATA, "data"}, |
{USB_CLASS_APP_SPEC, "app."}, |
{USB_CLASS_VENDOR_SPEC, "vend."}, |
{USB_CLASS_STILL_IMAGE, "still"}, |
{USB_CLASS_CSCID, "scard"}, |
{USB_CLASS_CONTENT_SEC, "c-sec"}, |
{-1, "unk."} /* leave as last */ |
}; |
/*****************************************************************/ |
void usbdevfs_conn_disc_event(void) |
{ |
conndiscevcnt++; |
wake_up(&deviceconndiscwq); |
} |
static const char *class_decode(const int class) |
{ |
int ix; |
for (ix = 0; clas_info[ix].class != -1; ix++) |
if (clas_info[ix].class == class) |
break; |
return (clas_info[ix].class_name); |
} |
static char *usb_dump_endpoint_descriptor ( |
int speed, |
char *start, |
char *end, |
const struct usb_endpoint_descriptor *desc |
) |
{ |
char dir, unit, *type; |
unsigned interval, in, bandwidth = 1; |
if (start > end) |
return start; |
in = (desc->bEndpointAddress & USB_DIR_IN); |
dir = in ? 'I' : 'O'; |
if (speed == USB_SPEED_HIGH) { |
switch (desc->wMaxPacketSize & (0x03 << 11)) { |
case 1 << 11: bandwidth = 2; break; |
case 2 << 11: bandwidth = 3; break; |
} |
} |
/* this isn't checking for illegal values */ |
switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { |
case USB_ENDPOINT_XFER_CONTROL: |
type = "Ctrl"; |
if (speed == USB_SPEED_HIGH) /* uframes per NAK */ |
interval = desc->bInterval; |
else |
interval = 0; |
dir = 'B'; /* ctrl is bidirectional */ |
break; |
case USB_ENDPOINT_XFER_ISOC: |
type = "Isoc"; |
interval = 1 << (desc->bInterval - 1); |
break; |
case USB_ENDPOINT_XFER_BULK: |
type = "Bulk"; |
if (speed == USB_SPEED_HIGH && !in) /* uframes per NAK */ |
interval = desc->bInterval; |
else |
interval = 0; |
break; |
case USB_ENDPOINT_XFER_INT: |
type = "Int."; |
if (speed == USB_SPEED_HIGH) { |
interval = 1 << (desc->bInterval - 1); |
} else |
interval = desc->bInterval; |
break; |
default: /* "can't happen" */ |
return start; |
} |
interval *= (speed == USB_SPEED_HIGH) ? 125 : 1000; |
if (interval % 1000) |
unit = 'u'; |
else { |
unit = 'm'; |
interval /= 1000; |
} |
start += sprintf26(start, format_endpt, desc->bEndpointAddress, dir, |
desc->bmAttributes, type, |
(desc->wMaxPacketSize & 0x07ff) * bandwidth, |
interval, unit); |
return start; |
} |
static char *usb_dump_interface_descriptor(char *start, char *end, const struct usb_interface *iface, int setno) |
{ |
struct usb_interface_descriptor *desc = &iface->altsetting[setno].desc; |
if (start > end) |
return start; |
lock_kernel(); /* driver might be unloaded */ |
start += sprintf26(start, format_iface, |
desc->bInterfaceNumber, |
desc->bAlternateSetting, |
desc->bNumEndpoints, |
desc->bInterfaceClass, |
class_decode(desc->bInterfaceClass), |
desc->bInterfaceSubClass, |
desc->bInterfaceProtocol, |
iface->driver ? iface->driver->name : "(none)"); |
unlock_kernel(); |
return start; |
} |
static char *usb_dump_interface( |
int speed, |
char *start, |
char *end, |
const struct usb_interface *iface, |
int setno |
) { |
struct usb_host_interface *desc = &iface->altsetting[setno]; |
int i; |
start = usb_dump_interface_descriptor(start, end, iface, setno); |
for (i = 0; i < desc->desc.bNumEndpoints; i++) { |
if (start > end) |
return start; |
start = usb_dump_endpoint_descriptor(speed, |
start, end, &desc->endpoint[i].desc); |
} |
return start; |
} |
/* TBD: |
* 0. TBDs |
* 1. marking active config and ifaces (code lists all, but should mark |
* which ones are active, if any) |
* 2. add <halted> status to each endpoint line |
*/ |
static char *usb_dump_config_descriptor(char *start, char *end, const struct usb_config_descriptor *desc, int active) |
{ |
if (start > end) |
return start; |
start += sprintf26(start, format_config, |
active ? '*' : ' ', /* mark active/actual/current cfg. */ |
desc->bNumInterfaces, |
desc->bConfigurationValue, |
desc->bmAttributes, |
desc->bMaxPower * 2); |
return start; |
} |
static char *usb_dump_config ( |
int speed, |
char *start, |
char *end, |
const struct usb_host_config *config, |
int active |
) |
{ |
int i, j; |
struct usb_interface *interface; |
if (start > end) |
return start; |
if (!config) /* getting these some in 2.3.7; none in 2.3.6 */ |
return start + sprintf26(start, "(null Cfg. desc.)\n"); |
start = usb_dump_config_descriptor(start, end, &config->desc, active); |
for (i = 0; i < config->desc.bNumInterfaces; i++) { |
interface = config->interface[i]; |
if (!interface) |
break; |
for (j = 0; j < interface->num_altsetting; j++) { |
if (start > end) |
return start; |
start = usb_dump_interface(speed, |
start, end, interface, j); |
} |
} |
return start; |
} |
/* |
* Dump the different USB descriptors. |
*/ |
static char *usb_dump_device_descriptor(char *start, char *end, const struct usb_device_descriptor *desc) |
{ |
if (start > end) |
return start; |
start += sprintf26 (start, format_device1, |
desc->bcdUSB >> 8, desc->bcdUSB & 0xff, |
desc->bDeviceClass, |
class_decode (desc->bDeviceClass), |
desc->bDeviceSubClass, |
desc->bDeviceProtocol, |
desc->bMaxPacketSize0, |
desc->bNumConfigurations); |
if (start > end) |
return start; |
start += sprintf26(start, format_device2, |
desc->idVendor, desc->idProduct, |
desc->bcdDevice >> 8, desc->bcdDevice & 0xff); |
return start; |
} |
/* |
* Dump the different strings that this device holds. |
*/ |
static char *usb_dump_device_strings (char *start, char *end, struct usb_device *dev) |
{ |
char *buf; |
if (start > end) |
return start; |
buf = kmalloc(128, GFP_KERNEL); |
if (!buf) |
return start; |
if (dev->descriptor.iManufacturer) { |
if (usb_string(dev, dev->descriptor.iManufacturer, buf, 128) > 0) |
start += sprintf26(start, format_string_manufacturer, buf); |
} |
if (start > end) |
goto out; |
if (dev->descriptor.iProduct) { |
if (usb_string(dev, dev->descriptor.iProduct, buf, 128) > 0) |
start += sprintf26(start, format_string_product, buf); |
} |
if (start > end) |
goto out; |
#ifdef ALLOW_SERIAL_NUMBER |
if (dev->descriptor.iSerialNumber) { |
if (usb_string(dev, dev->descriptor.iSerialNumber, buf, 128) > 0) |
start += sprintf26(start, format_string_serialnumber, buf); |
} |
#endif |
out: |
kfree(buf); |
return start; |
} |
static char *usb_dump_desc(char *start, char *end, struct usb_device *dev) |
{ |
int i; |
if (start > end) |
return start; |
start = usb_dump_device_descriptor(start, end, &dev->descriptor); |
if (start > end) |
return start; |
start = usb_dump_device_strings (start, end, dev); |
for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { |
if (start > end) |
return start; |
start = usb_dump_config(dev->speed, |
start, end, dev->config + i, |
/* active ? */ |
(dev->config + i) == dev->actconfig); |
} |
return start; |
} |
#ifdef PROC_EXTRA /* TBD: may want to add this code later */ |
static char *usb_dump_hub_descriptor(char *start, char *end, const struct usb_hub_descriptor * desc) |
{ |
int leng = USB_DT_HUB_NONVAR_SIZE; |
unsigned char *ptr = (unsigned char *)desc; |
if (start > end) |
return start; |
start += sprintf26(start, "Interface:"); |
while (leng && start <= end) { |
start += sprintf26(start, " %02x", *ptr); |
ptr++; leng--; |
} |
*start++ = '\n'; |
return start; |
} |
static char *usb_dump_string(char *start, char *end, const struct usb_device *dev, char *id, int index) |
{ |
if (start > end) |
return start; |
start += sprintf26(start, "Interface:"); |
if (index <= dev->maxstring && dev->stringindex && dev->stringindex[index]) |
start += sprintf26(start, "%s: %.100s ", id, dev->stringindex[index]); |
return start; |
} |
#endif /* PROC_EXTRA */ |
/*****************************************************************/ |
/* This is a recursive function. Parameters: |
* buffer - the user-space buffer to write data into |
* nbytes - the maximum number of bytes to write |
* skip_bytes - the number of bytes to skip before writing anything |
* file_offset - the offset into the devices file on completion |
*/ |
static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, loff_t *skip_bytes, loff_t *file_offset, |
struct usb_device *usbdev, struct usb_bus *bus, int level, int index, int count) |
{ |
int chix; |
int ret, cnt = 0; |
int parent_devnum = 0; |
char *pages_start, *data_end, *speed; |
unsigned int length; |
ssize_t total_written = 0; |
/* don't bother with anything else if we're not writing any data */ |
if (*nbytes <= 0) |
return 0; |
if (level > MAX_TOPO_LEVEL) |
return total_written; |
/* allocate 2^1 pages = 8K (on i386); should be more than enough for one device */ |
if (!(pages_start = (char*) __get_free_pages(GFP_KERNEL,1))) |
return -ENOMEM; |
if (usbdev->parent && usbdev->parent->devnum != -1) |
parent_devnum = usbdev->parent->devnum; |
/* |
* So the root hub's parent is 0 and any device that is |
* plugged into the root hub has a parent of 0. |
*/ |
switch (usbdev->speed) { |
case USB_SPEED_LOW: |
speed = "1.5"; break; |
case USB_SPEED_UNKNOWN: /* usb 1.1 root hub code */ |
case USB_SPEED_FULL: |
speed = "12 "; break; |
case USB_SPEED_HIGH: |
speed = "480"; break; |
default: |
speed = "?? "; |
} |
data_end = pages_start + sprintf26(pages_start, format_topo, |
bus->busnum, level, parent_devnum, |
index, count, usbdev->devnum, |
speed, usbdev->maxchild); |
/* |
* level = topology-tier level; |
* parent_devnum = parent device number; |
* index = parent's connector number; |
* count = device count at this level |
*/ |
/* If this is the root hub, display the bandwidth information */ |
if (level == 0) { |
int max; |
/* high speed reserves 80%, full/low reserves 90% */ |
if (usbdev->speed == USB_SPEED_HIGH) |
max = 800; |
else |
max = FRAME_TIME_MAX_USECS_ALLOC; |
/* report "average" periodic allocation over a microsecond. |
* the schedules are actually bursty, HCDs need to deal with |
* that and just compute/report this average. |
*/ |
data_end += sprintf26(data_end, format_bandwidth, |
bus->bandwidth_allocated, max, |
(100 * bus->bandwidth_allocated + max / 2) |
/ max, |
bus->bandwidth_int_reqs, |
bus->bandwidth_isoc_reqs); |
} |
data_end = usb_dump_desc(data_end, pages_start + (2 * PAGE_SIZE) - 256, usbdev); |
if (data_end > (pages_start + (2 * PAGE_SIZE) - 256)) |
data_end += sprintf26(data_end, "(truncated)\n"); |
length = data_end - pages_start; |
/* if we can start copying some data to the user */ |
if (length > *skip_bytes) { |
length -= *skip_bytes; |
if (length > *nbytes) |
length = *nbytes; |
if (copy_to_user(*buffer, pages_start + *skip_bytes, length)) { |
free_pages((unsigned long)pages_start, 1); |
if (total_written == 0) |
return -EFAULT; |
return total_written; |
} |
*nbytes -= length; |
*file_offset += length; |
total_written += length; |
*buffer += length; |
*skip_bytes = 0; |
} else |
*skip_bytes -= length; |
free_pages((unsigned long)pages_start, 1); |
/* Now look at all of this device's children. */ |
for (chix = 0; chix < usbdev->maxchild; chix++) { |
if (usbdev->children[chix]) { |
ret = usb_device_dump(buffer, nbytes, skip_bytes, file_offset, usbdev->children[chix], |
bus, level + 1, chix, ++cnt); |
if (ret == -EFAULT) |
return total_written; |
total_written += ret; |
} |
} |
return total_written; |
} |
static ssize_t usb_device_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) |
{ |
struct list_head *buslist; |
struct usb_bus *bus; |
ssize_t ret, total_written = 0; |
loff_t skip_bytes = *ppos; |
if (*ppos < 0) |
return -EINVAL; |
if (nbytes <= 0) |
return 0; |
if (!access_ok(VERIFY_WRITE, buf, nbytes)) |
return -EFAULT; |
/* enumerate busses */ |
down (&usb_bus_list_lock); |
for (buslist = usb_bus_list.next; buslist != &usb_bus_list; buslist = buslist->next) { |
/* print devices for this bus */ |
bus = list_entry(buslist, struct usb_bus, bus_list); |
/* recurse through all children of the root hub */ |
ret = usb_device_dump(&buf, &nbytes, &skip_bytes, ppos, bus->root_hub, bus, 0, 0, 0); |
if (ret < 0) { |
up(&usb_bus_list_lock); |
return ret; |
} |
total_written += ret; |
} |
up (&usb_bus_list_lock); |
return total_written; |
} |
/* Kernel lock for "lastev" protection */ |
static unsigned int usb_device_poll(struct file *file, struct poll_table_struct *wait) |
{ |
struct usb_device_status *st = (struct usb_device_status *)file->private_data; |
unsigned int mask = 0; |
lock_kernel(); |
if (!st) { |
st = kmalloc(sizeof(struct usb_device_status), GFP_KERNEL); |
if (!st) { |
unlock_kernel(); |
return POLLIN; |
} |
/* we may have dropped BKL - need to check for having lost the race */ |
if (file->private_data) { |
kfree(st); |
goto lost_race; |
} |
/* |
* need to prevent the module from being unloaded, since |
* proc_unregister does not call the release method and |
* we would have a memory leak |
*/ |
st->lastev = conndiscevcnt; |
file->private_data = st; |
mask = POLLIN; |
} |
lost_race: |
if (file->f_mode & FMODE_READ) |
poll_wait(file, &deviceconndiscwq, wait); |
if (st->lastev != conndiscevcnt) |
mask |= POLLIN; |
st->lastev = conndiscevcnt; |
unlock_kernel(); |
return mask; |
} |
static int usb_device_open(struct inode *inode, struct file *file) |
{ |
file->private_data = NULL; |
return 0; |
} |
static int usb_device_release(struct inode *inode, struct file *file) |
{ |
if (file->private_data) { |
kfree(file->private_data); |
file->private_data = NULL; |
} |
return 0; |
} |
static loff_t usb_device_lseek(struct file * file, loff_t offset, int orig) |
{ |
loff_t ret; |
lock_kernel(); |
switch (orig) { |
case 0: |
file->f_pos = offset; |
ret = file->f_pos; |
break; |
case 1: |
file->f_pos += offset; |
ret = file->f_pos; |
break; |
case 2: |
default: |
ret = -EINVAL; |
} |
unlock_kernel(); |
return ret; |
} |
struct file_operations usbdevfs_devices_fops = { |
.llseek = usb_device_lseek, |
.read = usb_device_read, |
.poll = usb_device_poll, |
.open = usb_device_open, |
.release = usb_device_release, |
}; |
/shark/trunk/drivers/usb/core/unused/inode.c |
---|
0,0 → 1,779 |
/*****************************************************************************/ |
/* |
* inode.c -- Inode/Dentry functions for the USB device file system. |
* |
* Copyright (C) 2000 Thomas Sailer (sailer@ife.ee.ethz.ch) |
* Copyright (C) 2001,2002 Greg Kroah-Hartman (greg@kroah.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. |
* |
* History: |
* 0.1 04.01.2000 Created |
* 0.2 10.12.2001 converted to use the vfs layer better |
*/ |
/*****************************************************************************/ |
#include <linux/config.h> |
#include <linux/module.h> |
#include <linux/fs.h> |
#include <linux/mount.h> |
#include <linux/pagemap.h> |
#include <linux/init.h> |
#include <linux/proc_fs.h> |
#include <linux/usb.h> |
#include <linux/namei.h> |
#include <linux/usbdevice_fs.h> |
#include <linux/smp_lock.h> |
#include <linux/parser.h> |
#include <asm/byteorder.h> |
static struct super_operations usbfs_ops; |
static struct file_operations default_file_operations; |
static struct inode_operations usbfs_dir_inode_operations; |
static struct vfsmount *usbdevfs_mount; |
static struct vfsmount *usbfs_mount; |
static int usbdevfs_mount_count; /* = 0 */ |
static int usbfs_mount_count; /* = 0 */ |
static struct dentry *devices_usbdevfs_dentry; |
static struct dentry *devices_usbfs_dentry; |
static int num_buses; /* = 0 */ |
static uid_t devuid; /* = 0 */ |
static uid_t busuid; /* = 0 */ |
static uid_t listuid; /* = 0 */ |
static gid_t devgid; /* = 0 */ |
static gid_t busgid; /* = 0 */ |
static gid_t listgid; /* = 0 */ |
static umode_t devmode = S_IWUSR | S_IRUGO; |
static umode_t busmode = S_IXUGO | S_IRUGO; |
static umode_t listmode = S_IRUGO; |
enum { |
Opt_devuid, Opt_devgid, Opt_devmode, |
Opt_busuid, Opt_busgid, Opt_busmode, |
Opt_listuid, Opt_listgid, Opt_listmode, |
Opt_err, |
}; |
static match_table_t tokens = { |
{Opt_devuid, "devuid=%u"}, |
{Opt_devgid, "devgid=%u"}, |
{Opt_devmode, "devmode=%o"}, |
{Opt_busuid, "busuid=%u"}, |
{Opt_busgid, "busgid=%u"}, |
{Opt_busmode, "busmode=%o"}, |
{Opt_listuid, "listuid=%u"}, |
{Opt_listgid, "listgid=%u"}, |
{Opt_listmode, "listmode=%o"}, |
{Opt_err, NULL} |
}; |
static int parse_options(struct super_block *s, char *data) |
{ |
char *p; |
int option; |
while ((p = strsep(&data, ",")) != NULL) { |
substring_t args[MAX_OPT_ARGS]; |
int token; |
if (!*p) |
continue; |
token = match_token(p, tokens, args); |
switch (token) { |
case Opt_devuid: |
if (match_int(&args[0], &option)) |
return -EINVAL; |
devuid = option; |
break; |
case Opt_devgid: |
if (match_int(&args[0], &option)) |
return -EINVAL; |
devgid = option; |
break; |
case Opt_devmode: |
if (match_octal(&args[0], &option)) |
return -EINVAL; |
devmode = option & S_IRWXUGO; |
break; |
case Opt_busuid: |
if (match_int(&args[0], &option)) |
return -EINVAL; |
busuid = option; |
break; |
case Opt_busgid: |
if (match_int(&args[0], &option)) |
return -EINVAL; |
busgid = option; |
break; |
case Opt_busmode: |
if (match_octal(&args[0], &option)) |
return -EINVAL; |
busmode = option & S_IRWXUGO; |
break; |
case Opt_listuid: |
if (match_int(&args[0], &option)) |
return -EINVAL; |
listuid = option; |
break; |
case Opt_listgid: |
if (match_int(&args[0], &option)) |
return -EINVAL; |
listgid = option; |
break; |
case Opt_listmode: |
if (match_octal(&args[0], &option)) |
return -EINVAL; |
listmode = option & S_IRWXUGO; |
break; |
default: |
err("usbfs: unrecognised mount option \"%s\" " |
"or missing value\n", p); |
return -EINVAL; |
} |
} |
return 0; |
} |
static struct inode *usbfs_get_inode (struct super_block *sb, int mode, dev_t dev) |
{ |
struct inode *inode = new_inode(sb); |
if (inode) { |
inode->i_mode = mode; |
inode->i_uid = current->fsuid; |
inode->i_gid = current->fsgid; |
inode->i_blksize = PAGE_CACHE_SIZE; |
inode->i_blocks = 0; |
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; |
switch (mode & S_IFMT) { |
default: |
init_special_inode(inode, mode, dev); |
break; |
case S_IFREG: |
inode->i_fop = &default_file_operations; |
break; |
case S_IFDIR: |
inode->i_op = &usbfs_dir_inode_operations; |
inode->i_fop = &simple_dir_operations; |
/* directory inodes start off with i_nlink == 2 (for "." entry) */ |
inode->i_nlink++; |
break; |
} |
} |
return inode; |
} |
/* SMP-safe */ |
static int usbfs_mknod (struct inode *dir, struct dentry *dentry, int mode, |
dev_t dev) |
{ |
struct inode *inode = usbfs_get_inode(dir->i_sb, mode, dev); |
int error = -EPERM; |
if (dentry->d_inode) |
return -EEXIST; |
if (inode) { |
d_instantiate(dentry, inode); |
dget(dentry); |
error = 0; |
} |
return error; |
} |
static int usbfs_mkdir (struct inode *dir, struct dentry *dentry, int mode) |
{ |
int res; |
mode = (mode & (S_IRWXUGO | S_ISVTX)) | S_IFDIR; |
res = usbfs_mknod (dir, dentry, mode, 0); |
if (!res) |
dir->i_nlink++; |
return res; |
} |
static int usbfs_create (struct inode *dir, struct dentry *dentry, int mode) |
{ |
mode = (mode & S_IALLUGO) | S_IFREG; |
return usbfs_mknod (dir, dentry, mode, 0); |
} |
static inline int usbfs_positive (struct dentry *dentry) |
{ |
return dentry->d_inode && !d_unhashed(dentry); |
} |
static int usbfs_empty (struct dentry *dentry) |
{ |
struct list_head *list; |
spin_lock(&dcache_lock); |
list_for_each(list, &dentry->d_subdirs) { |
struct dentry *de = list_entry(list, struct dentry, d_child); |
if (usbfs_positive(de)) { |
spin_unlock(&dcache_lock); |
return 0; |
} |
} |
spin_unlock(&dcache_lock); |
return 1; |
} |
static int usbfs_unlink (struct inode *dir, struct dentry *dentry) |
{ |
struct inode *inode = dentry->d_inode; |
down(&inode->i_sem); |
dentry->d_inode->i_nlink--; |
dput(dentry); |
up(&inode->i_sem); |
d_delete(dentry); |
return 0; |
} |
static void d_unhash(struct dentry *dentry) |
{ |
dget(dentry); |
spin_lock(&dcache_lock); |
switch (atomic_read(&dentry->d_count)) { |
default: |
spin_unlock(&dcache_lock); |
shrink_dcache_parent(dentry); |
spin_lock(&dcache_lock); |
if (atomic_read(&dentry->d_count) != 2) |
break; |
case 2: |
__d_drop(dentry); |
} |
spin_unlock(&dcache_lock); |
} |
static int usbfs_rmdir(struct inode *dir, struct dentry *dentry) |
{ |
int error = -ENOTEMPTY; |
struct inode * inode = dentry->d_inode; |
down(&inode->i_sem); |
d_unhash(dentry); |
if (usbfs_empty(dentry)) { |
dentry->d_inode->i_nlink -= 2; |
dput(dentry); |
inode->i_flags |= S_DEAD; |
dir->i_nlink--; |
error = 0; |
} |
up(&inode->i_sem); |
if (!error) |
d_delete(dentry); |
dput(dentry); |
return error; |
} |
/* default file operations */ |
static ssize_t default_read_file (struct file *file, char __user *buf, |
size_t count, loff_t *ppos) |
{ |
return 0; |
} |
static ssize_t default_write_file (struct file *file, const char __user *buf, |
size_t count, loff_t *ppos) |
{ |
return count; |
} |
static loff_t default_file_lseek (struct file *file, loff_t offset, int orig) |
{ |
loff_t retval = -EINVAL; |
down(&file->f_dentry->d_inode->i_sem); |
switch(orig) { |
case 0: |
if (offset > 0) { |
file->f_pos = offset; |
retval = file->f_pos; |
} |
break; |
case 1: |
if ((offset + file->f_pos) > 0) { |
file->f_pos += offset; |
retval = file->f_pos; |
} |
break; |
default: |
break; |
} |
up(&file->f_dentry->d_inode->i_sem); |
return retval; |
} |
static int default_open (struct inode *inode, struct file *file) |
{ |
if (inode->u.generic_ip) |
file->private_data = inode->u.generic_ip; |
return 0; |
} |
static struct file_operations default_file_operations = { |
.read = default_read_file, |
.write = default_write_file, |
.open = default_open, |
.llseek = default_file_lseek, |
}; |
static struct inode_operations usbfs_dir_inode_operations = { |
.lookup = simple_lookup, |
}; |
static struct super_operations usbfs_ops = { |
.statfs = simple_statfs, |
.drop_inode = generic_delete_inode, |
}; |
static int usbfs_fill_super(struct super_block *sb, void *data, int silent) |
{ |
struct inode *inode; |
struct dentry *root; |
if (parse_options(sb, data)) { |
warn("usbfs: mount parameter error:"); |
return -EINVAL; |
} |
sb->s_blocksize = PAGE_CACHE_SIZE; |
sb->s_blocksize_bits = PAGE_CACHE_SHIFT; |
sb->s_magic = USBDEVICE_SUPER_MAGIC; |
sb->s_op = &usbfs_ops; |
inode = usbfs_get_inode(sb, S_IFDIR | 0755, 0); |
if (!inode) { |
dbg("%s: could not get inode!",__FUNCTION__); |
return -ENOMEM; |
} |
root = d_alloc_root(inode); |
if (!root) { |
dbg("%s: could not get root dentry!",__FUNCTION__); |
iput(inode); |
return -ENOMEM; |
} |
sb->s_root = root; |
return 0; |
} |
static struct dentry * get_dentry(struct dentry *parent, const char *name) |
{ |
struct qstr qstr; |
qstr.name = name; |
qstr.len = strlen(name); |
qstr.hash = full_name_hash(name,qstr.len); |
return lookup_hash(&qstr,parent); |
} |
/* |
* fs_create_by_name - create a file, given a name |
* @name: name of file |
* @mode: type of file |
* @parent: dentry of directory to create it in |
* @dentry: resulting dentry of file |
* |
* This function handles both regular files and directories. |
*/ |
static int fs_create_by_name (const char *name, mode_t mode, |
struct dentry *parent, struct dentry **dentry) |
{ |
int error = 0; |
/* If the parent is not specified, we create it in the root. |
* We need the root dentry to do this, which is in the super |
* block. A pointer to that is in the struct vfsmount that we |
* have around. |
*/ |
if (!parent ) { |
if (usbfs_mount && usbfs_mount->mnt_sb) { |
parent = usbfs_mount->mnt_sb->s_root; |
} |
} |
if (!parent) { |
dbg("Ah! can not find a parent!"); |
return -EFAULT; |
} |
*dentry = NULL; |
down(&parent->d_inode->i_sem); |
*dentry = get_dentry (parent, name); |
if (!IS_ERR(dentry)) { |
if ((mode & S_IFMT) == S_IFDIR) |
error = usbfs_mkdir (parent->d_inode, *dentry, mode); |
else |
error = usbfs_create (parent->d_inode, *dentry, mode); |
} else |
error = PTR_ERR(dentry); |
up(&parent->d_inode->i_sem); |
return error; |
} |
static struct dentry *fs_create_file (const char *name, mode_t mode, |
struct dentry *parent, void *data, |
struct file_operations *fops, |
uid_t uid, gid_t gid) |
{ |
struct dentry *dentry; |
int error; |
dbg("creating file '%s'",name); |
error = fs_create_by_name (name, mode, parent, &dentry); |
if (error) { |
dentry = NULL; |
} else { |
if (dentry->d_inode) { |
if (data) |
dentry->d_inode->u.generic_ip = data; |
if (fops) |
dentry->d_inode->i_fop = fops; |
dentry->d_inode->i_uid = uid; |
dentry->d_inode->i_gid = gid; |
} |
} |
return dentry; |
} |
static void fs_remove_file (struct dentry *dentry) |
{ |
struct dentry *parent = dentry->d_parent; |
if (!parent || !parent->d_inode) |
return; |
down(&parent->d_inode->i_sem); |
if (usbfs_positive(dentry)) { |
if (dentry->d_inode) { |
if (S_ISDIR(dentry->d_inode->i_mode)) |
usbfs_rmdir(parent->d_inode, dentry); |
else |
usbfs_unlink(parent->d_inode, dentry); |
dput(dentry); |
} |
} |
up(&parent->d_inode->i_sem); |
} |
/* --------------------------------------------------------------------- */ |
/* |
* The usbdevfs name is now deprecated (as of 2.5.1). |
* It will be removed when the 2.7.x development cycle is started. |
* You have been warned :) |
*/ |
static struct file_system_type usbdevice_fs_type; |
static struct super_block *usb_get_sb(struct file_system_type *fs_type, |
int flags, const char *dev_name, void *data) |
{ |
return get_sb_single(fs_type, flags, data, usbfs_fill_super); |
} |
static struct file_system_type usbdevice_fs_type = { |
.owner = THIS_MODULE, |
.name = "usbdevfs", |
.get_sb = usb_get_sb, |
.kill_sb = kill_litter_super, |
}; |
static struct file_system_type usb_fs_type = { |
.owner = THIS_MODULE, |
.name = "usbfs", |
.get_sb = usb_get_sb, |
.kill_sb = kill_litter_super, |
}; |
/* --------------------------------------------------------------------- */ |
static int create_special_files (void) |
{ |
struct dentry *parent; |
int retval; |
/* create the devices special file */ |
retval = simple_pin_fs("usbdevfs", &usbdevfs_mount, &usbdevfs_mount_count); |
if (retval) { |
err ("Unable to get usbdevfs mount"); |
goto exit; |
} |
retval = simple_pin_fs("usbfs", &usbfs_mount, &usbfs_mount_count); |
if (retval) { |
err ("Unable to get usbfs mount"); |
goto error_clean_usbdevfs_mount; |
} |
parent = usbfs_mount->mnt_sb->s_root; |
devices_usbfs_dentry = fs_create_file ("devices", |
listmode | S_IFREG, parent, |
NULL, &usbdevfs_devices_fops, |
listuid, listgid); |
if (devices_usbfs_dentry == NULL) { |
err ("Unable to create devices usbfs file"); |
retval = -ENODEV; |
goto error_clean_mounts; |
} |
parent = usbdevfs_mount->mnt_sb->s_root; |
devices_usbdevfs_dentry = fs_create_file ("devices", |
listmode | S_IFREG, parent, |
NULL, &usbdevfs_devices_fops, |
listuid, listgid); |
if (devices_usbdevfs_dentry == NULL) { |
err ("Unable to create devices usbfs file"); |
retval = -ENODEV; |
goto error_remove_file; |
} |
goto exit; |
error_remove_file: |
fs_remove_file (devices_usbfs_dentry); |
devices_usbfs_dentry = NULL; |
error_clean_mounts: |
simple_release_fs(&usbfs_mount, &usbfs_mount_count); |
error_clean_usbdevfs_mount: |
simple_release_fs(&usbdevfs_mount, &usbdevfs_mount_count); |
exit: |
return retval; |
} |
static void remove_special_files (void) |
{ |
if (devices_usbdevfs_dentry) |
fs_remove_file (devices_usbdevfs_dentry); |
if (devices_usbfs_dentry) |
fs_remove_file (devices_usbfs_dentry); |
devices_usbdevfs_dentry = NULL; |
devices_usbfs_dentry = NULL; |
simple_release_fs(&usbdevfs_mount, &usbdevfs_mount_count); |
simple_release_fs(&usbfs_mount, &usbfs_mount_count); |
} |
void usbfs_update_special (void) |
{ |
struct inode *inode; |
if (devices_usbdevfs_dentry) { |
inode = devices_usbdevfs_dentry->d_inode; |
if (inode) |
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; |
} |
if (devices_usbfs_dentry) { |
inode = devices_usbfs_dentry->d_inode; |
if (inode) |
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; |
} |
} |
void usbfs_add_bus(struct usb_bus *bus) |
{ |
struct dentry *parent; |
char name[8]; |
int retval; |
/* create the special files if this is the first bus added */ |
if (num_buses == 0) { |
retval = create_special_files(); |
if (retval) |
return; |
} |
++num_buses; |
sprintf (name, "%03d", bus->busnum); |
parent = usbfs_mount->mnt_sb->s_root; |
bus->usbfs_dentry = fs_create_file (name, busmode | S_IFDIR, parent, |
bus, NULL, busuid, busgid); |
if (bus->usbfs_dentry == NULL) { |
err ("error creating usbfs bus entry"); |
return; |
} |
parent = usbdevfs_mount->mnt_sb->s_root; |
bus->usbdevfs_dentry = fs_create_file (name, busmode | S_IFDIR, parent, |
bus, NULL, busuid, busgid); |
if (bus->usbdevfs_dentry == NULL) { |
err ("error creating usbdevfs bus entry"); |
return; |
} |
usbfs_update_special(); |
usbdevfs_conn_disc_event(); |
} |
void usbfs_remove_bus(struct usb_bus *bus) |
{ |
if (bus->usbfs_dentry) { |
fs_remove_file (bus->usbfs_dentry); |
bus->usbfs_dentry = NULL; |
} |
if (bus->usbdevfs_dentry) { |
fs_remove_file (bus->usbdevfs_dentry); |
bus->usbdevfs_dentry = NULL; |
} |
--num_buses; |
if (num_buses <= 0) { |
remove_special_files(); |
num_buses = 0; |
} |
usbfs_update_special(); |
usbdevfs_conn_disc_event(); |
} |
void usbfs_add_device(struct usb_device *dev) |
{ |
char name[8]; |
int i; |
int i_size; |
sprintf (name, "%03d", dev->devnum); |
dev->usbfs_dentry = fs_create_file (name, devmode | S_IFREG, |
dev->bus->usbfs_dentry, dev, |
&usbdevfs_device_file_operations, |
devuid, devgid); |
if (dev->usbfs_dentry == NULL) { |
err ("error creating usbfs device entry"); |
return; |
} |
dev->usbdevfs_dentry = fs_create_file (name, devmode | S_IFREG, |
dev->bus->usbdevfs_dentry, dev, |
&usbdevfs_device_file_operations, |
devuid, devgid); |
if (dev->usbdevfs_dentry == NULL) { |
err ("error creating usbdevfs device entry"); |
return; |
} |
/* Set the size of the device's file to be |
* equal to the size of the device descriptors. */ |
i_size = sizeof (struct usb_device_descriptor); |
for (i = 0; i < dev->descriptor.bNumConfigurations; ++i) { |
struct usb_config_descriptor *config = |
(struct usb_config_descriptor *)dev->rawdescriptors[i]; |
i_size += le16_to_cpu (config->wTotalLength); |
} |
if (dev->usbfs_dentry->d_inode) |
dev->usbfs_dentry->d_inode->i_size = i_size; |
if (dev->usbdevfs_dentry->d_inode) |
dev->usbdevfs_dentry->d_inode->i_size = i_size; |
usbfs_update_special(); |
usbdevfs_conn_disc_event(); |
} |
void usbfs_remove_device(struct usb_device *dev) |
{ |
struct dev_state *ds; |
struct siginfo sinfo; |
if (dev->usbfs_dentry) { |
fs_remove_file (dev->usbfs_dentry); |
dev->usbfs_dentry = NULL; |
} |
if (dev->usbdevfs_dentry) { |
fs_remove_file (dev->usbdevfs_dentry); |
dev->usbdevfs_dentry = NULL; |
} |
while (!list_empty(&dev->filelist)) { |
ds = list_entry(dev->filelist.next, struct dev_state, list); |
list_del_init(&ds->list); |
down_write(&ds->devsem); |
ds->dev = NULL; |
up_write(&ds->devsem); |
if (ds->discsignr) { |
sinfo.si_signo = SIGPIPE; |
sinfo.si_errno = EPIPE; |
sinfo.si_code = SI_ASYNCIO; |
sinfo.si_addr = ds->disccontext; |
send_sig_info(ds->discsignr, &sinfo, ds->disctask); |
} |
} |
usbfs_update_special(); |
usbdevfs_conn_disc_event(); |
} |
/* --------------------------------------------------------------------- */ |
#ifdef CONFIG_PROC_FS |
static struct proc_dir_entry *usbdir = NULL; |
#endif |
int __init usbfs_init(void) |
{ |
int retval; |
retval = usb_register(&usbdevfs_driver); |
if (retval) |
return retval; |
retval = register_filesystem(&usb_fs_type); |
if (retval) { |
usb_deregister(&usbdevfs_driver); |
return retval; |
} |
retval = register_filesystem(&usbdevice_fs_type); |
if (retval) { |
unregister_filesystem(&usb_fs_type); |
usb_deregister(&usbdevfs_driver); |
return retval; |
} |
#ifdef CONFIG_PROC_FS |
/* create mount point for usbdevfs */ |
usbdir = proc_mkdir("usb", proc_bus); |
#endif |
return 0; |
} |
void __exit usbfs_cleanup(void) |
{ |
usb_deregister(&usbdevfs_driver); |
unregister_filesystem(&usb_fs_type); |
unregister_filesystem(&usbdevice_fs_type); |
#ifdef CONFIG_PROC_FS |
if (usbdir) |
remove_proc_entry("usb", proc_bus); |
#endif |
} |
/shark/trunk/drivers/usb/core/unused/devio.c |
---|
0,0 → 1,1279 |
/*****************************************************************************/ |
/* |
* devio.c -- User space communication with USB devices. |
* |
* Copyright (C) 1999-2000 Thomas Sailer (sailer@ife.ee.ethz.ch) |
* |
* 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. |
* |
* $Id: devio.c,v 1.1 2004-09-13 15:04:47 giacomo Exp $ |
* |
* This file implements the usbdevfs/x/y files, where |
* x is the bus number and y the device number. |
* |
* It allows user space programs/"drivers" to communicate directly |
* with USB devices without intervening kernel driver. |
* |
* Revision history |
* 22.12.1999 0.1 Initial release (split from proc_usb.c) |
* 04.01.2000 0.2 Turned into its own filesystem |
*/ |
/*****************************************************************************/ |
#include <linux/fs.h> |
#include <linux/mm.h> |
#include <linux/slab.h> |
#include <linux/smp_lock.h> |
#include <linux/signal.h> |
#include <linux/poll.h> |
#include <linux/module.h> |
#include <linux/usb.h> |
#include <linux/usbdevice_fs.h> |
#include <asm/uaccess.h> |
#include <asm/byteorder.h> |
#include "hcd.h" /* for usbcore internals */ |
#include "usb.h" |
struct async { |
struct list_head asynclist; |
struct dev_state *ps; |
struct task_struct *task; |
unsigned int signr; |
unsigned int intf; |
void __user *userbuffer; |
void __user *userurb; |
struct urb *urb; |
}; |
static loff_t usbdev_lseek(struct file *file, loff_t offset, int orig) |
{ |
loff_t ret; |
lock_kernel(); |
switch (orig) { |
case 0: |
file->f_pos = offset; |
ret = file->f_pos; |
break; |
case 1: |
file->f_pos += offset; |
ret = file->f_pos; |
break; |
case 2: |
default: |
ret = -EINVAL; |
} |
unlock_kernel(); |
return ret; |
} |
static ssize_t usbdev_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) |
{ |
struct dev_state *ps = (struct dev_state *)file->private_data; |
ssize_t ret = 0; |
unsigned len; |
loff_t pos; |
int i; |
pos = *ppos; |
down_read(&ps->devsem); |
if (!ps->dev) { |
ret = -ENODEV; |
goto err; |
} else if (pos < 0) { |
ret = -EINVAL; |
goto err; |
} |
if (pos < sizeof(struct usb_device_descriptor)) { |
len = sizeof(struct usb_device_descriptor) - pos; |
if (len > nbytes) |
len = nbytes; |
if (copy_to_user(buf, ((char *)&ps->dev->descriptor) + pos, len)) { |
ret = -EFAULT; |
goto err; |
} |
*ppos += len; |
buf += len; |
nbytes -= len; |
ret += len; |
} |
pos = sizeof(struct usb_device_descriptor); |
for (i = 0; nbytes && i < ps->dev->descriptor.bNumConfigurations; i++) { |
struct usb_config_descriptor *config = |
(struct usb_config_descriptor *)ps->dev->rawdescriptors[i]; |
unsigned int length = le16_to_cpu(config->wTotalLength); |
if (*ppos < pos + length) { |
len = length - (*ppos - pos); |
if (len > nbytes) |
len = nbytes; |
if (copy_to_user(buf, |
ps->dev->rawdescriptors[i] + (*ppos - pos), len)) { |
ret = -EFAULT; |
goto err; |
} |
*ppos += len; |
buf += len; |
nbytes -= len; |
ret += len; |
} |
pos += length; |
} |
err: |
up_read(&ps->devsem); |
return ret; |
} |
extern inline unsigned int ld2(unsigned int x) |
{ |
unsigned int r = 0; |
if (x >= 0x10000) { |
x >>= 16; |
r += 16; |
} |
if (x >= 0x100) { |
x >>= 8; |
r += 8; |
} |
if (x >= 0x10) { |
x >>= 4; |
r += 4; |
} |
if (x >= 4) { |
x >>= 2; |
r += 2; |
} |
if (x >= 2) |
r++; |
return r; |
} |
/* |
* async list handling |
*/ |
static struct async *alloc_async(unsigned int numisoframes) |
{ |
unsigned int assize = sizeof(struct async) + numisoframes * sizeof(struct usb_iso_packet_descriptor); |
struct async *as = kmalloc(assize, GFP_KERNEL); |
if (!as) |
return NULL; |
memset(as, 0, assize); |
as->urb = usb_alloc_urb(numisoframes, GFP_KERNEL); |
if (!as->urb) { |
kfree(as); |
return NULL; |
} |
return as; |
} |
static void free_async(struct async *as) |
{ |
if (as->urb->transfer_buffer) |
kfree(as->urb->transfer_buffer); |
if (as->urb->setup_packet) |
kfree(as->urb->setup_packet); |
usb_free_urb(as->urb); |
kfree(as); |
} |
extern __inline__ void async_newpending(struct async *as) |
{ |
struct dev_state *ps = as->ps; |
unsigned long flags; |
spin_lock_irqsave(&ps->lock, flags); |
list_add_tail(&as->asynclist, &ps->async_pending); |
spin_unlock_irqrestore(&ps->lock, flags); |
} |
extern __inline__ void async_removepending(struct async *as) |
{ |
struct dev_state *ps = as->ps; |
unsigned long flags; |
spin_lock_irqsave(&ps->lock, flags); |
list_del_init(&as->asynclist); |
spin_unlock_irqrestore(&ps->lock, flags); |
} |
extern __inline__ struct async *async_getcompleted(struct dev_state *ps) |
{ |
unsigned long flags; |
struct async *as = NULL; |
spin_lock_irqsave(&ps->lock, flags); |
if (!list_empty(&ps->async_completed)) { |
as = list_entry(ps->async_completed.next, struct async, asynclist); |
list_del_init(&as->asynclist); |
} |
spin_unlock_irqrestore(&ps->lock, flags); |
return as; |
} |
extern __inline__ struct async *async_getpending(struct dev_state *ps, void __user *userurb) |
{ |
unsigned long flags; |
struct async *as; |
spin_lock_irqsave(&ps->lock, flags); |
list_for_each_entry(as, &ps->async_pending, asynclist) |
if (as->userurb == userurb) { |
list_del_init(&as->asynclist); |
spin_unlock_irqrestore(&ps->lock, flags); |
return as; |
} |
spin_unlock_irqrestore(&ps->lock, flags); |
return NULL; |
} |
static void async_completed(struct urb *urb, struct pt_regs *regs) |
{ |
struct async *as = (struct async *)urb->context; |
struct dev_state *ps = as->ps; |
struct siginfo sinfo; |
spin_lock(&ps->lock); |
list_move_tail(&as->asynclist, &ps->async_completed); |
spin_unlock(&ps->lock); |
if (as->signr) { |
sinfo.si_signo = as->signr; |
sinfo.si_errno = as->urb->status; |
sinfo.si_code = SI_ASYNCIO; |
sinfo.si_addr = (void *)as->userurb; |
send_sig_info(as->signr, &sinfo, as->task); |
} |
wake_up(&ps->wait); |
} |
static void destroy_async (struct dev_state *ps, struct list_head *list) |
{ |
struct async *as; |
unsigned long flags; |
spin_lock_irqsave(&ps->lock, flags); |
while (!list_empty(list)) { |
as = list_entry(list->next, struct async, asynclist); |
list_del_init(&as->asynclist); |
spin_unlock_irqrestore(&ps->lock, flags); |
/* usb_unlink_urb calls the completion handler with status == -ENOENT */ |
usb_unlink_urb(as->urb); |
spin_lock_irqsave(&ps->lock, flags); |
} |
spin_unlock_irqrestore(&ps->lock, flags); |
while ((as = async_getcompleted(ps))) |
free_async(as); |
} |
static void destroy_async_on_interface (struct dev_state *ps, unsigned int intf) |
{ |
struct list_head *p, *q, hitlist; |
unsigned long flags; |
INIT_LIST_HEAD(&hitlist); |
spin_lock_irqsave(&ps->lock, flags); |
list_for_each_safe(p, q, &ps->async_pending) |
if (intf == list_entry(p, struct async, asynclist)->intf) |
list_move_tail(p, &hitlist); |
spin_unlock_irqrestore(&ps->lock, flags); |
destroy_async(ps, &hitlist); |
} |
extern __inline__ void destroy_all_async(struct dev_state *ps) |
{ |
destroy_async(ps, &ps->async_pending); |
} |
/* |
* interface claims are made only at the request of user level code, |
* which can also release them (explicitly or by closing files). |
* they're also undone when devices disconnect. |
*/ |
static int driver_probe (struct usb_interface *intf, |
const struct usb_device_id *id) |
{ |
return -ENODEV; |
} |
static void driver_disconnect(struct usb_interface *intf) |
{ |
struct dev_state *ps = usb_get_intfdata (intf); |
if (!ps) |
return; |
/* this waits till synchronous requests complete */ |
down_write (&ps->devsem); |
/* prevent new I/O requests */ |
ps->dev = 0; |
ps->ifclaimed = 0; |
usb_set_intfdata (intf, NULL); |
/* force async requests to complete */ |
destroy_all_async (ps); |
up_write (&ps->devsem); |
} |
struct usb_driver usbdevfs_driver = { |
.owner = THIS_MODULE, |
.name = "usbfs", |
.probe = driver_probe, |
.disconnect = driver_disconnect, |
}; |
static int claimintf(struct dev_state *ps, unsigned int intf) |
{ |
struct usb_device *dev = ps->dev; |
struct usb_interface *iface; |
int err; |
if (intf >= 8*sizeof(ps->ifclaimed) || !dev |
|| intf >= dev->actconfig->desc.bNumInterfaces) |
return -EINVAL; |
/* already claimed */ |
if (test_bit(intf, &ps->ifclaimed)) |
return 0; |
iface = dev->actconfig->interface[intf]; |
err = -EBUSY; |
lock_kernel(); |
if (!usb_interface_claimed(iface)) { |
usb_driver_claim_interface(&usbdevfs_driver, iface, ps); |
set_bit(intf, &ps->ifclaimed); |
err = 0; |
} |
unlock_kernel(); |
return err; |
} |
static int releaseintf(struct dev_state *ps, unsigned int intf) |
{ |
struct usb_device *dev; |
struct usb_interface *iface; |
int err; |
if (intf >= 8*sizeof(ps->ifclaimed)) |
return -EINVAL; |
err = -EINVAL; |
dev = ps->dev; |
down(&dev->serialize); |
if (test_and_clear_bit(intf, &ps->ifclaimed)) { |
iface = dev->actconfig->interface[intf]; |
usb_driver_release_interface(&usbdevfs_driver, iface); |
err = 0; |
} |
up(&dev->serialize); |
return err; |
} |
static int checkintf(struct dev_state *ps, unsigned int intf) |
{ |
if (intf >= 8*sizeof(ps->ifclaimed)) |
return -EINVAL; |
if (test_bit(intf, &ps->ifclaimed)) |
return 0; |
/* if not yet claimed, claim it for the driver */ |
printk(KERN_WARNING "usbfs: process %d (%s) did not claim interface %u before use\n", |
current->pid, current->comm, intf); |
return claimintf(ps, intf); |
} |
static int findintfep(struct usb_device *dev, unsigned int ep) |
{ |
unsigned int i, j, e; |
struct usb_interface *iface; |
struct usb_host_interface *alts; |
struct usb_endpoint_descriptor *endpt; |
if (ep & ~(USB_DIR_IN|0xf)) |
return -EINVAL; |
for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { |
iface = dev->actconfig->interface[i]; |
for (j = 0; j < iface->num_altsetting; j++) { |
alts = &iface->altsetting[j]; |
for (e = 0; e < alts->desc.bNumEndpoints; e++) { |
endpt = &alts->endpoint[e].desc; |
if (endpt->bEndpointAddress == ep) |
return i; |
} |
} |
} |
return -ENOENT; |
} |
static int findintfif(struct usb_device *dev, unsigned int ifn) |
{ |
unsigned int i, j; |
struct usb_interface *iface; |
struct usb_host_interface *alts; |
if (ifn & ~0xff) |
return -EINVAL; |
for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { |
iface = dev->actconfig->interface[i]; |
for (j = 0; j < iface->num_altsetting; j++) { |
alts = &iface->altsetting[j]; |
if (alts->desc.bInterfaceNumber == ifn) |
return i; |
} |
} |
return -ENOENT; |
} |
static int check_ctrlrecip(struct dev_state *ps, unsigned int requesttype, unsigned int index) |
{ |
int ret; |
if (USB_TYPE_VENDOR == (USB_TYPE_MASK & requesttype)) |
return 0; |
switch (requesttype & USB_RECIP_MASK) { |
case USB_RECIP_ENDPOINT: |
if ((ret = findintfep(ps->dev, index & 0xff)) < 0) |
return ret; |
if ((ret = checkintf(ps, ret))) |
return ret; |
break; |
case USB_RECIP_INTERFACE: |
if ((ret = findintfif(ps->dev, index & 0xff)) < 0) |
return ret; |
if ((ret = checkintf(ps, ret))) |
return ret; |
break; |
} |
return 0; |
} |
/* |
* file operations |
*/ |
static int usbdev_open(struct inode *inode, struct file *file) |
{ |
struct usb_device *dev; |
struct dev_state *ps; |
int ret; |
/* |
* no locking necessary here, as both sys_open (actually filp_open) |
* and the hub thread have the kernel lock |
* (still acquire the kernel lock for safety) |
*/ |
ret = -ENOMEM; |
if (!(ps = kmalloc(sizeof(struct dev_state), GFP_KERNEL))) |
goto out_nolock; |
lock_kernel(); |
ret = -ENOENT; |
dev = inode->u.generic_ip; |
if (!dev) { |
kfree(ps); |
goto out; |
} |
ret = 0; |
ps->dev = dev; |
ps->file = file; |
spin_lock_init(&ps->lock); |
INIT_LIST_HEAD(&ps->async_pending); |
INIT_LIST_HEAD(&ps->async_completed); |
init_waitqueue_head(&ps->wait); |
init_rwsem(&ps->devsem); |
ps->discsignr = 0; |
ps->disctask = current; |
ps->disccontext = NULL; |
ps->ifclaimed = 0; |
wmb(); |
list_add_tail(&ps->list, &dev->filelist); |
file->private_data = ps; |
out: |
unlock_kernel(); |
out_nolock: |
return ret; |
} |
static int usbdev_release(struct inode *inode, struct file *file) |
{ |
struct dev_state *ps = (struct dev_state *)file->private_data; |
unsigned int i; |
lock_kernel(); |
list_del_init(&ps->list); |
if (ps->dev) { |
for (i = 0; ps->ifclaimed && i < 8*sizeof(ps->ifclaimed); i++) |
if (test_bit(i, &ps->ifclaimed)) |
releaseintf(ps, i); |
} |
unlock_kernel(); |
destroy_all_async(ps); |
kfree(ps); |
return 0; |
} |
static int proc_control(struct dev_state *ps, void __user *arg) |
{ |
struct usb_device *dev = ps->dev; |
struct usbdevfs_ctrltransfer ctrl; |
unsigned int tmo; |
unsigned char *tbuf; |
int i, ret; |
if (copy_from_user(&ctrl, arg, sizeof(ctrl))) |
return -EFAULT; |
if ((ret = check_ctrlrecip(ps, ctrl.bRequestType, ctrl.wIndex))) |
return ret; |
if (ctrl.wLength > PAGE_SIZE) |
return -EINVAL; |
if (!(tbuf = (unsigned char *)__get_free_page(GFP_KERNEL))) |
return -ENOMEM; |
tmo = (ctrl.timeout * HZ + 999) / 1000; |
if (ctrl.bRequestType & 0x80) { |
if (ctrl.wLength && !access_ok(VERIFY_WRITE, ctrl.data, ctrl.wLength)) { |
free_page((unsigned long)tbuf); |
return -EINVAL; |
} |
i = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), ctrl.bRequest, ctrl.bRequestType, |
ctrl.wValue, ctrl.wIndex, tbuf, ctrl.wLength, tmo); |
if ((i > 0) && ctrl.wLength) { |
if (copy_to_user(ctrl.data, tbuf, ctrl.wLength)) { |
free_page((unsigned long)tbuf); |
return -EFAULT; |
} |
} |
} else { |
if (ctrl.wLength) { |
if (copy_from_user(tbuf, ctrl.data, ctrl.wLength)) { |
free_page((unsigned long)tbuf); |
return -EFAULT; |
} |
} |
i = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), ctrl.bRequest, ctrl.bRequestType, |
ctrl.wValue, ctrl.wIndex, tbuf, ctrl.wLength, tmo); |
} |
free_page((unsigned long)tbuf); |
if (i<0) { |
printk(KERN_DEBUG "usbfs: USBDEVFS_CONTROL failed " |
"cmd %s dev %d rqt %u rq %u len %u ret %d\n", |
current->comm, |
dev->devnum, ctrl.bRequestType, ctrl.bRequest, ctrl.wLength, i); |
} |
return i; |
} |
static int proc_bulk(struct dev_state *ps, void __user *arg) |
{ |
struct usb_device *dev = ps->dev; |
struct usbdevfs_bulktransfer bulk; |
unsigned int tmo, len1, pipe; |
int len2; |
unsigned char *tbuf; |
int i, ret; |
if (copy_from_user(&bulk, arg, sizeof(bulk))) |
return -EFAULT; |
if ((ret = findintfep(ps->dev, bulk.ep)) < 0) |
return ret; |
if ((ret = checkintf(ps, ret))) |
return ret; |
if (bulk.ep & USB_DIR_IN) |
pipe = usb_rcvbulkpipe(dev, bulk.ep & 0x7f); |
else |
pipe = usb_sndbulkpipe(dev, bulk.ep & 0x7f); |
if (!usb_maxpacket(dev, pipe, !(bulk.ep & USB_DIR_IN))) |
return -EINVAL; |
len1 = bulk.len; |
if (!(tbuf = kmalloc(len1, GFP_KERNEL))) |
return -ENOMEM; |
tmo = (bulk.timeout * HZ + 999) / 1000; |
if (bulk.ep & 0x80) { |
if (len1 && !access_ok(VERIFY_WRITE, bulk.data, len1)) { |
kfree(tbuf); |
return -EINVAL; |
} |
i = usb_bulk_msg(dev, pipe, tbuf, len1, &len2, tmo); |
if (!i && len2) { |
if (copy_to_user(bulk.data, tbuf, len2)) { |
kfree(tbuf); |
return -EFAULT; |
} |
} |
} else { |
if (len1) { |
if (copy_from_user(tbuf, bulk.data, len1)) { |
kfree(tbuf); |
return -EFAULT; |
} |
} |
i = usb_bulk_msg(dev, pipe, tbuf, len1, &len2, tmo); |
} |
kfree(tbuf); |
if (i < 0) { |
printk(KERN_WARNING "usbfs: USBDEVFS_BULK failed dev %d ep 0x%x len %u ret %d\n", |
dev->devnum, bulk.ep, bulk.len, i); |
return i; |
} |
return len2; |
} |
static int proc_resetep(struct dev_state *ps, void __user *arg) |
{ |
unsigned int ep; |
int ret; |
if (get_user(ep, (unsigned int __user *)arg)) |
return -EFAULT; |
if ((ret = findintfep(ps->dev, ep)) < 0) |
return ret; |
if ((ret = checkintf(ps, ret))) |
return ret; |
usb_settoggle(ps->dev, ep & 0xf, !(ep & USB_DIR_IN), 0); |
return 0; |
} |
static int proc_clearhalt(struct dev_state *ps, void __user *arg) |
{ |
unsigned int ep; |
int pipe; |
int ret; |
if (get_user(ep, (unsigned int __user *)arg)) |
return -EFAULT; |
if ((ret = findintfep(ps->dev, ep)) < 0) |
return ret; |
if ((ret = checkintf(ps, ret))) |
return ret; |
if (ep & USB_DIR_IN) |
pipe = usb_rcvbulkpipe(ps->dev, ep & 0x7f); |
else |
pipe = usb_sndbulkpipe(ps->dev, ep & 0x7f); |
return usb_clear_halt(ps->dev, pipe); |
} |
static int proc_getdriver(struct dev_state *ps, void __user *arg) |
{ |
struct usbdevfs_getdriver gd; |
struct usb_interface *interface; |
int ret; |
if (copy_from_user(&gd, arg, sizeof(gd))) |
return -EFAULT; |
if ((ret = findintfif(ps->dev, gd.interface)) < 0) |
return ret; |
interface = usb_ifnum_to_if(ps->dev, gd.interface); |
if (!interface) |
return -EINVAL; |
if (!interface->driver) |
return -ENODATA; |
strcpy(gd.driver, interface->driver->name); |
if (copy_to_user(arg, &gd, sizeof(gd))) |
return -EFAULT; |
return 0; |
} |
static int proc_connectinfo(struct dev_state *ps, void __user *arg) |
{ |
struct usbdevfs_connectinfo ci; |
ci.devnum = ps->dev->devnum; |
ci.slow = ps->dev->speed == USB_SPEED_LOW; |
if (copy_to_user(arg, &ci, sizeof(ci))) |
return -EFAULT; |
return 0; |
} |
static int proc_resetdevice(struct dev_state *ps) |
{ |
int i, ret; |
ret = usb_reset_device(ps->dev); |
if (ret < 0) |
return ret; |
for (i = 0; i < ps->dev->actconfig->desc.bNumInterfaces; i++) { |
struct usb_interface *intf = ps->dev->actconfig->interface[i]; |
/* Don't simulate interfaces we've claimed */ |
if (test_bit(i, &ps->ifclaimed)) |
continue; |
err ("%s - this function is broken", __FUNCTION__); |
if (intf->driver && ps->dev) { |
usb_probe_interface (&intf->dev); |
} |
} |
return 0; |
} |
static int proc_setintf(struct dev_state *ps, void __user *arg) |
{ |
struct usbdevfs_setinterface setintf; |
struct usb_interface *interface; |
int ret; |
if (copy_from_user(&setintf, arg, sizeof(setintf))) |
return -EFAULT; |
if ((ret = findintfif(ps->dev, setintf.interface)) < 0) |
return ret; |
interface = usb_ifnum_to_if(ps->dev, setintf.interface); |
if (!interface) |
return -EINVAL; |
if (interface->driver) { |
if ((ret = checkintf(ps, ret))) |
return ret; |
} |
if (usb_set_interface(ps->dev, setintf.interface, setintf.altsetting)) |
return -EINVAL; |
return 0; |
} |
static int proc_setconfig(struct dev_state *ps, void __user *arg) |
{ |
unsigned int u; |
if (get_user(u, (unsigned int __user *)arg)) |
return -EFAULT; |
return usb_set_configuration(ps->dev, u); |
} |
static int proc_submiturb(struct dev_state *ps, void __user *arg) |
{ |
struct usbdevfs_urb uurb; |
struct usbdevfs_iso_packet_desc *isopkt = NULL; |
struct usb_endpoint_descriptor *ep_desc; |
struct async *as; |
struct usb_ctrlrequest *dr = NULL; |
unsigned int u, totlen, isofrmlen; |
int ret, interval = 0, intf = -1; |
if (copy_from_user(&uurb, arg, sizeof(uurb))) |
return -EFAULT; |
if (uurb.flags & ~(USBDEVFS_URB_ISO_ASAP|USBDEVFS_URB_SHORT_NOT_OK| |
URB_NO_FSBR|URB_ZERO_PACKET)) |
return -EINVAL; |
if (!uurb.buffer) |
return -EINVAL; |
if (uurb.signr != 0 && (uurb.signr < SIGRTMIN || uurb.signr > SIGRTMAX)) |
return -EINVAL; |
if (!(uurb.type == USBDEVFS_URB_TYPE_CONTROL && (uurb.endpoint & ~USB_ENDPOINT_DIR_MASK) == 0)) { |
if ((intf = findintfep(ps->dev, uurb.endpoint)) < 0) |
return intf; |
if ((ret = checkintf(ps, intf))) |
return ret; |
} |
switch(uurb.type) { |
case USBDEVFS_URB_TYPE_CONTROL: |
if ((uurb.endpoint & ~USB_ENDPOINT_DIR_MASK) != 0) { |
if (!(ep_desc = usb_epnum_to_ep_desc(ps->dev, uurb.endpoint))) |
return -ENOENT; |
if ((ep_desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_CONTROL) |
return -EINVAL; |
} |
/* min 8 byte setup packet, max arbitrary */ |
if (uurb.buffer_length < 8 || uurb.buffer_length > PAGE_SIZE) |
return -EINVAL; |
if (!(dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL))) |
return -ENOMEM; |
if (copy_from_user(dr, uurb.buffer, 8)) { |
kfree(dr); |
return -EFAULT; |
} |
if (uurb.buffer_length < (le16_to_cpup(&dr->wLength) + 8)) { |
kfree(dr); |
return -EINVAL; |
} |
if ((ret = check_ctrlrecip(ps, dr->bRequestType, le16_to_cpup(&dr->wIndex)))) { |
kfree(dr); |
return ret; |
} |
uurb.endpoint = (uurb.endpoint & ~USB_ENDPOINT_DIR_MASK) | (dr->bRequestType & USB_ENDPOINT_DIR_MASK); |
uurb.number_of_packets = 0; |
uurb.buffer_length = le16_to_cpup(&dr->wLength); |
uurb.buffer += 8; |
if (!access_ok((uurb.endpoint & USB_DIR_IN) ? VERIFY_WRITE : VERIFY_READ, uurb.buffer, uurb.buffer_length)) { |
kfree(dr); |
return -EFAULT; |
} |
break; |
case USBDEVFS_URB_TYPE_BULK: |
uurb.number_of_packets = 0; |
if (uurb.buffer_length > 16384) |
return -EINVAL; |
if (!access_ok((uurb.endpoint & USB_DIR_IN) ? VERIFY_WRITE : VERIFY_READ, uurb.buffer, uurb.buffer_length)) |
return -EFAULT; |
break; |
case USBDEVFS_URB_TYPE_ISO: |
/* arbitrary limit */ |
if (uurb.number_of_packets < 1 || uurb.number_of_packets > 128) |
return -EINVAL; |
isofrmlen = sizeof(struct usbdevfs_iso_packet_desc) * uurb.number_of_packets; |
if (!(isopkt = kmalloc(isofrmlen, GFP_KERNEL))) |
return -ENOMEM; |
if (copy_from_user(isopkt, &((struct usbdevfs_urb *)arg)->iso_frame_desc, isofrmlen)) { |
kfree(isopkt); |
return -EFAULT; |
} |
for (totlen = u = 0; u < uurb.number_of_packets; u++) { |
if (isopkt[u].length > 1023) { |
kfree(isopkt); |
return -EINVAL; |
} |
totlen += isopkt[u].length; |
} |
if (totlen > 32768) { |
kfree(isopkt); |
return -EINVAL; |
} |
uurb.buffer_length = totlen; |
break; |
case USBDEVFS_URB_TYPE_INTERRUPT: |
uurb.number_of_packets = 0; |
if (!(ep_desc = usb_epnum_to_ep_desc(ps->dev, uurb.endpoint))) |
return -ENOENT; |
interval = ep_desc->bInterval; |
if (uurb.buffer_length > 16384) |
return -EINVAL; |
if (!access_ok((uurb.endpoint & USB_DIR_IN) ? VERIFY_WRITE : VERIFY_READ, uurb.buffer, uurb.buffer_length)) |
return -EFAULT; |
break; |
default: |
return -EINVAL; |
} |
if (!(as = alloc_async(uurb.number_of_packets))) { |
if (isopkt) |
kfree(isopkt); |
if (dr) |
kfree(dr); |
return -ENOMEM; |
} |
if (!(as->urb->transfer_buffer = kmalloc(uurb.buffer_length, GFP_KERNEL))) { |
if (isopkt) |
kfree(isopkt); |
if (dr) |
kfree(dr); |
free_async(as); |
return -ENOMEM; |
} |
as->urb->dev = ps->dev; |
as->urb->pipe = (uurb.type << 30) | __create_pipe(ps->dev, uurb.endpoint & 0xf) | (uurb.endpoint & USB_DIR_IN); |
as->urb->transfer_flags = uurb.flags; |
as->urb->transfer_buffer_length = uurb.buffer_length; |
as->urb->setup_packet = (unsigned char*)dr; |
as->urb->start_frame = uurb.start_frame; |
as->urb->number_of_packets = uurb.number_of_packets; |
as->urb->interval = interval; |
as->urb->context = as; |
as->urb->complete = async_completed; |
for (totlen = u = 0; u < uurb.number_of_packets; u++) { |
as->urb->iso_frame_desc[u].offset = totlen; |
as->urb->iso_frame_desc[u].length = isopkt[u].length; |
totlen += isopkt[u].length; |
} |
if (isopkt) |
kfree(isopkt); |
as->ps = ps; |
as->userurb = arg; |
if (uurb.endpoint & USB_DIR_IN) |
as->userbuffer = uurb.buffer; |
else |
as->userbuffer = NULL; |
as->signr = uurb.signr; |
as->intf = intf; |
as->task = current; |
if (!(uurb.endpoint & USB_DIR_IN)) { |
if (copy_from_user(as->urb->transfer_buffer, uurb.buffer, as->urb->transfer_buffer_length)) { |
free_async(as); |
return -EFAULT; |
} |
} |
async_newpending(as); |
if ((ret = usb_submit_urb(as->urb, GFP_KERNEL))) { |
printk(KERN_DEBUG "usbfs: usb_submit_urb returned %d\n", ret); |
async_removepending(as); |
free_async(as); |
return ret; |
} |
return 0; |
} |
static int proc_unlinkurb(struct dev_state *ps, void __user *arg) |
{ |
struct async *as; |
as = async_getpending(ps, arg); |
if (!as) |
return -EINVAL; |
usb_unlink_urb(as->urb); |
return 0; |
} |
static int processcompl(struct async *as) |
{ |
struct urb *urb = as->urb; |
unsigned int i; |
if (as->userbuffer) |
if (copy_to_user(as->userbuffer, urb->transfer_buffer, urb->transfer_buffer_length)) |
return -EFAULT; |
if (put_user(urb->status, |
&((struct usbdevfs_urb *)as->userurb)->status)) |
return -EFAULT; |
if (put_user(urb->actual_length, |
&((struct usbdevfs_urb *)as->userurb)->actual_length)) |
return -EFAULT; |
if (put_user(urb->error_count, |
&((struct usbdevfs_urb *)as->userurb)->error_count)) |
return -EFAULT; |
if (!(usb_pipeisoc(urb->pipe))) |
return 0; |
for (i = 0; i < urb->number_of_packets; i++) { |
if (put_user(urb->iso_frame_desc[i].actual_length, |
&((struct usbdevfs_urb *)as->userurb)->iso_frame_desc[i].actual_length)) |
return -EFAULT; |
if (put_user(urb->iso_frame_desc[i].status, |
&((struct usbdevfs_urb *)as->userurb)->iso_frame_desc[i].status)) |
return -EFAULT; |
} |
return 0; |
} |
static int proc_reapurb(struct dev_state *ps, void __user *arg) |
{ |
DECLARE_WAITQUEUE(wait, current); |
struct async *as = NULL; |
void __user *addr; |
int ret; |
add_wait_queue(&ps->wait, &wait); |
while (ps->dev) { |
__set_current_state(TASK_INTERRUPTIBLE); |
if ((as = async_getcompleted(ps))) |
break; |
if (signal_pending(current)) |
break; |
up_read(&ps->devsem); |
schedule(); |
down_read(&ps->devsem); |
} |
remove_wait_queue(&ps->wait, &wait); |
set_current_state(TASK_RUNNING); |
if (as) { |
ret = processcompl(as); |
addr = as->userurb; |
free_async(as); |
if (ret) |
return ret; |
if (put_user(addr, (void **)arg)) |
return -EFAULT; |
return 0; |
} |
if (signal_pending(current)) |
return -EINTR; |
return -EIO; |
} |
static int proc_reapurbnonblock(struct dev_state *ps, void __user *arg) |
{ |
struct async *as; |
void __user *addr; |
int ret; |
if (!(as = async_getcompleted(ps))) |
return -EAGAIN; |
ret = processcompl(as); |
addr = as->userurb; |
free_async(as); |
if (ret) |
return ret; |
if (put_user(addr, (void **)arg)) |
return -EFAULT; |
return 0; |
} |
static int proc_disconnectsignal(struct dev_state *ps, void __user *arg) |
{ |
struct usbdevfs_disconnectsignal ds; |
if (copy_from_user(&ds, arg, sizeof(ds))) |
return -EFAULT; |
if (ds.signr != 0 && (ds.signr < SIGRTMIN || ds.signr > SIGRTMAX)) |
return -EINVAL; |
ps->discsignr = ds.signr; |
ps->disccontext = ds.context; |
return 0; |
} |
static int proc_claiminterface(struct dev_state *ps, void __user *arg) |
{ |
unsigned int intf; |
int ret; |
if (get_user(intf, (unsigned int __user *)arg)) |
return -EFAULT; |
if ((ret = findintfif(ps->dev, intf)) < 0) |
return ret; |
return claimintf(ps, ret); |
} |
static int proc_releaseinterface(struct dev_state *ps, void __user *arg) |
{ |
unsigned int intf; |
int ret; |
if (get_user(intf, (unsigned int __user *)arg)) |
return -EFAULT; |
if ((ret = findintfif(ps->dev, intf)) < 0) |
return ret; |
if ((ret = releaseintf(ps, intf)) < 0) |
return ret; |
destroy_async_on_interface (ps, intf); |
return 0; |
} |
static int proc_ioctl (struct dev_state *ps, void __user *arg) |
{ |
struct usbdevfs_ioctl ctrl; |
int size; |
void *buf = 0; |
int retval = 0; |
struct usb_interface *ifp = 0; |
struct usb_driver *driver = 0; |
/* get input parameters and alloc buffer */ |
if (copy_from_user(&ctrl, arg, sizeof (ctrl))) |
return -EFAULT; |
if ((size = _IOC_SIZE (ctrl.ioctl_code)) > 0) { |
if ((buf = kmalloc (size, GFP_KERNEL)) == 0) |
return -ENOMEM; |
if ((_IOC_DIR(ctrl.ioctl_code) & _IOC_WRITE)) { |
if (copy_from_user (buf, ctrl.data, size)) { |
kfree (buf); |
return -EFAULT; |
} |
} else { |
memset (buf, 0, size); |
} |
} |
if (!ps->dev) |
retval = -ENODEV; |
else if (!(ifp = usb_ifnum_to_if (ps->dev, ctrl.ifno))) |
retval = -EINVAL; |
else switch (ctrl.ioctl_code) { |
/* disconnect kernel driver from interface, leaving it unbound. */ |
/* maybe unbound - you get no guarantee it stays unbound */ |
case USBDEVFS_DISCONNECT: |
/* this function is misdesigned - retained for compatibility */ |
lock_kernel(); |
driver = ifp->driver; |
if (driver) { |
dbg ("disconnect '%s' from dev %d interface %d", |
driver->name, ps->dev->devnum, ctrl.ifno); |
usb_unbind_interface(&ifp->dev); |
} else |
retval = -ENODATA; |
unlock_kernel(); |
break; |
/* let kernel drivers try to (re)bind to the interface */ |
case USBDEVFS_CONNECT: |
lock_kernel(); |
retval = usb_probe_interface (&ifp->dev); |
unlock_kernel(); |
break; |
/* talk directly to the interface's driver */ |
default: |
/* BKL used here to protect against changing the binding |
* of this driver to this device, as well as unloading its |
* driver module. |
*/ |
lock_kernel (); |
driver = ifp->driver; |
if (driver == 0 || driver->ioctl == 0) { |
unlock_kernel(); |
retval = -ENOSYS; |
} else { |
if (!try_module_get (driver->owner)) { |
unlock_kernel(); |
retval = -ENOSYS; |
break; |
} |
unlock_kernel (); |
retval = driver->ioctl (ifp, ctrl.ioctl_code, buf); |
if (retval == -ENOIOCTLCMD) |
retval = -ENOTTY; |
module_put (driver->owner); |
} |
} |
/* cleanup and return */ |
if (retval >= 0 |
&& (_IOC_DIR (ctrl.ioctl_code) & _IOC_READ) != 0 |
&& size > 0 |
&& copy_to_user (ctrl.data, buf, size) != 0) |
retval = -EFAULT; |
if (buf != 0) |
kfree (buf); |
return retval; |
} |
/* |
* NOTE: All requests here that have interface numbers as parameters |
* are assuming that somehow the configuration has been prevented from |
* changing. But there's no mechanism to ensure that... |
*/ |
static int usbdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) |
{ |
struct dev_state *ps = (struct dev_state *)file->private_data; |
int ret = -ENOTTY; |
if (!(file->f_mode & FMODE_WRITE)) |
return -EPERM; |
down_read(&ps->devsem); |
if (!ps->dev) { |
up_read(&ps->devsem); |
return -ENODEV; |
} |
switch (cmd) { |
case USBDEVFS_CONTROL: |
ret = proc_control(ps, (void __user *)arg); |
if (ret >= 0) |
inode->i_mtime = CURRENT_TIME; |
break; |
case USBDEVFS_BULK: |
ret = proc_bulk(ps, (void __user *)arg); |
if (ret >= 0) |
inode->i_mtime = CURRENT_TIME; |
break; |
case USBDEVFS_RESETEP: |
ret = proc_resetep(ps, (void __user *)arg); |
if (ret >= 0) |
inode->i_mtime = CURRENT_TIME; |
break; |
case USBDEVFS_RESET: |
ret = proc_resetdevice(ps); |
break; |
case USBDEVFS_CLEAR_HALT: |
ret = proc_clearhalt(ps, (void __user *)arg); |
if (ret >= 0) |
inode->i_mtime = CURRENT_TIME; |
break; |
case USBDEVFS_GETDRIVER: |
ret = proc_getdriver(ps, (void __user *)arg); |
break; |
case USBDEVFS_CONNECTINFO: |
ret = proc_connectinfo(ps, (void __user *)arg); |
break; |
case USBDEVFS_SETINTERFACE: |
ret = proc_setintf(ps, (void __user *)arg); |
break; |
case USBDEVFS_SETCONFIGURATION: |
ret = proc_setconfig(ps, (void __user *)arg); |
break; |
case USBDEVFS_SUBMITURB: |
ret = proc_submiturb(ps, (void __user *)arg); |
if (ret >= 0) |
inode->i_mtime = CURRENT_TIME; |
break; |
case USBDEVFS_DISCARDURB: |
ret = proc_unlinkurb(ps, (void __user *)arg); |
break; |
case USBDEVFS_REAPURB: |
ret = proc_reapurb(ps, (void __user *)arg); |
break; |
case USBDEVFS_REAPURBNDELAY: |
ret = proc_reapurbnonblock(ps, (void __user *)arg); |
break; |
case USBDEVFS_DISCSIGNAL: |
ret = proc_disconnectsignal(ps, (void __user *)arg); |
break; |
case USBDEVFS_CLAIMINTERFACE: |
ret = proc_claiminterface(ps, (void __user *)arg); |
break; |
case USBDEVFS_RELEASEINTERFACE: |
ret = proc_releaseinterface(ps, (void __user *)arg); |
break; |
case USBDEVFS_IOCTL: |
ret = proc_ioctl(ps, (void __user *) arg); |
break; |
} |
up_read(&ps->devsem); |
if (ret >= 0) |
inode->i_atime = CURRENT_TIME; |
return ret; |
} |
/* No kernel lock - fine */ |
static unsigned int usbdev_poll(struct file *file, struct poll_table_struct *wait) |
{ |
struct dev_state *ps = (struct dev_state *)file->private_data; |
unsigned int mask = 0; |
poll_wait(file, &ps->wait, wait); |
if (file->f_mode & FMODE_WRITE && !list_empty(&ps->async_completed)) |
mask |= POLLOUT | POLLWRNORM; |
if (!ps->dev) |
mask |= POLLERR | POLLHUP; |
return mask; |
} |
struct file_operations usbdevfs_device_file_operations = { |
.llseek = usbdev_lseek, |
.read = usbdev_read, |
.poll = usbdev_poll, |
.ioctl = usbdev_ioctl, |
.open = usbdev_open, |
.release = usbdev_release, |
}; |
/shark/trunk/drivers/usb/core/hub.h |
---|
0,0 → 1,195 |
#ifndef __LINUX_HUB_H |
#define __LINUX_HUB_H |
/* |
* Hub protocol and driver data structures. |
* |
* Some of these are known to the "virtual root hub" code |
* in host controller drivers. |
*/ |
#include <linux/list.h> |
#include <linux/workqueue.h> |
#include <linux/compiler.h> /* likely()/unlikely() */ |
/* |
* Hub request types |
*/ |
#define USB_RT_HUB (USB_TYPE_CLASS | USB_RECIP_DEVICE) |
#define USB_RT_PORT (USB_TYPE_CLASS | USB_RECIP_OTHER) |
/* |
* Hub class requests |
* See USB 2.0 spec Table 11-16 |
*/ |
#define HUB_CLEAR_TT_BUFFER 8 |
#define HUB_RESET_TT 9 |
#define HUB_GET_TT_STATE 10 |
#define HUB_STOP_TT 11 |
/* |
* Hub Class feature numbers |
* See USB 2.0 spec Table 11-17 |
*/ |
#define C_HUB_LOCAL_POWER 0 |
#define C_HUB_OVER_CURRENT 1 |
/* |
* Port feature numbers |
* See USB 2.0 spec Table 11-17 |
*/ |
#define USB_PORT_FEAT_CONNECTION 0 |
#define USB_PORT_FEAT_ENABLE 1 |
#define USB_PORT_FEAT_SUSPEND 2 |
#define USB_PORT_FEAT_OVER_CURRENT 3 |
#define USB_PORT_FEAT_RESET 4 |
#define USB_PORT_FEAT_POWER 8 |
#define USB_PORT_FEAT_LOWSPEED 9 |
#define USB_PORT_FEAT_HIGHSPEED 10 |
#define USB_PORT_FEAT_C_CONNECTION 16 |
#define USB_PORT_FEAT_C_ENABLE 17 |
#define USB_PORT_FEAT_C_SUSPEND 18 |
#define USB_PORT_FEAT_C_OVER_CURRENT 19 |
#define USB_PORT_FEAT_C_RESET 20 |
#define USB_PORT_FEAT_TEST 21 |
#define USB_PORT_FEAT_INDICATOR 22 |
/* |
* Hub Status and Hub Change results |
* See USB 2.0 spec Table 11-19 and Table 11-20 |
*/ |
struct usb_port_status { |
__u16 wPortStatus; |
__u16 wPortChange; |
} __attribute__ ((packed)); |
/* |
* wPortStatus bit field |
* See USB 2.0 spec Table 11-21 |
*/ |
#define USB_PORT_STAT_CONNECTION 0x0001 |
#define USB_PORT_STAT_ENABLE 0x0002 |
#define USB_PORT_STAT_SUSPEND 0x0004 |
#define USB_PORT_STAT_OVERCURRENT 0x0008 |
#define USB_PORT_STAT_RESET 0x0010 |
/* bits 5 to 7 are reserved */ |
#define USB_PORT_STAT_POWER 0x0100 |
#define USB_PORT_STAT_LOW_SPEED 0x0200 |
#define USB_PORT_STAT_HIGH_SPEED 0x0400 |
#define USB_PORT_STAT_TEST 0x0800 |
#define USB_PORT_STAT_INDICATOR 0x1000 |
/* bits 13 to 15 are reserved */ |
/* |
* wPortChange bit field |
* See USB 2.0 spec Table 11-22 |
* Bits 0 to 4 shown, bits 5 to 15 are reserved |
*/ |
#define USB_PORT_STAT_C_CONNECTION 0x0001 |
#define USB_PORT_STAT_C_ENABLE 0x0002 |
#define USB_PORT_STAT_C_SUSPEND 0x0004 |
#define USB_PORT_STAT_C_OVERCURRENT 0x0008 |
#define USB_PORT_STAT_C_RESET 0x0010 |
/* |
* wHubCharacteristics (masks) |
* See USB 2.0 spec Table 11-13, offset 3 |
*/ |
#define HUB_CHAR_LPSM 0x0003 /* D1 .. D0 */ |
#define HUB_CHAR_COMPOUND 0x0004 /* D2 */ |
#define HUB_CHAR_OCPM 0x0018 /* D4 .. D3 */ |
#define HUB_CHAR_TTTT 0x0060 /* D6 .. D5 */ |
#define HUB_CHAR_PORTIND 0x0080 /* D7 */ |
struct usb_hub_status { |
__u16 wHubStatus; |
__u16 wHubChange; |
} __attribute__ ((packed)); |
/* |
* Hub Status & Hub Change bit masks |
* See USB 2.0 spec Table 11-19 and Table 11-20 |
* Bits 0 and 1 for wHubStatus and wHubChange |
* Bits 2 to 15 are reserved for both |
*/ |
#define HUB_STATUS_LOCAL_POWER 0x0001 |
#define HUB_STATUS_OVERCURRENT 0x0002 |
#define HUB_CHANGE_LOCAL_POWER 0x0001 |
#define HUB_CHANGE_OVERCURRENT 0x0002 |
/* |
* Hub descriptor |
* See USB 2.0 spec Table 11-13 |
*/ |
#define USB_DT_HUB (USB_TYPE_CLASS | 0x09) |
#define USB_DT_HUB_NONVAR_SIZE 7 |
struct usb_hub_descriptor { |
__u8 bDescLength; |
__u8 bDescriptorType; |
__u8 bNbrPorts; |
__u16 wHubCharacteristics; |
__u8 bPwrOn2PwrGood; |
__u8 bHubContrCurrent; |
/* add 1 bit for hub status change; round to bytes */ |
__u8 DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8]; |
__u8 PortPwrCtrlMask[(USB_MAXCHILDREN + 1 + 7) / 8]; |
} __attribute__ ((packed)); |
struct usb_device; |
/* |
* As of USB 2.0, full/low speed devices are segregated into trees. |
* One type grows from USB 1.1 host controllers (OHCI, UHCI etc). |
* The other type grows from high speed hubs when they connect to |
* full/low speed devices using "Transaction Translators" (TTs). |
* |
* TTs should only be known to the hub driver, and high speed bus |
* drivers (only EHCI for now). They affect periodic scheduling and |
* sometimes control/bulk error recovery. |
*/ |
struct usb_tt { |
struct usb_device *hub; /* upstream highspeed hub */ |
int multi; /* true means one TT per port */ |
/* for control/bulk error recovery (CLEAR_TT_BUFFER) */ |
spinlock_t lock; |
struct list_head clear_list; /* of usb_tt_clear */ |
struct work_struct kevent; |
}; |
struct usb_tt_clear { |
struct list_head clear_list; |
unsigned tt; |
u16 devinfo; |
}; |
extern void usb_hub_tt_clear_buffer (struct usb_device *dev, int pipe); |
struct usb_hub { |
struct usb_interface *intf; /* the "real" device */ |
struct urb *urb; /* for interrupt polling pipe */ |
/* buffer for urb ... 1 bit each for hub and children, rounded up */ |
char (*buffer)[(USB_MAXCHILDREN + 1 + 7) / 8]; |
dma_addr_t buffer_dma; /* DMA address for buffer */ |
union { |
struct usb_hub_status hub; |
struct usb_port_status port; |
} *status; /* buffer for status reports */ |
int error; /* last reported error */ |
int nerrors; /* track consecutive errors */ |
struct list_head hub_list; /* all hubs */ |
struct list_head event_list; /* hubs w/data or errs ready */ |
struct usb_hub_descriptor *descriptor; /* class descriptor */ |
struct semaphore khubd_sem; |
struct usb_tt tt; /* Transaction Translator */ |
}; |
#endif /* __LINUX_HUB_H */ |
/shark/trunk/drivers/usb/core/message.c |
---|
0,0 → 1,1263 |
/* |
* message.c - synchronous message handling |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#ifdef CONFIG_USB_DEBUG |
#define DEBUG |
#else |
#undef DEBUG |
#endif |
#include <linux/pci.h> /* for scatterlist macros */ |
#include <linux/usb.h> |
#include <linux/module.h> |
#include <linux/slab.h> |
#include <linux/init.h> |
#include <linux/mm.h> |
#include <linux/timer.h> |
#include <asm/byteorder.h> |
#include "hcd.h" /* for usbcore internals */ |
#include "usb.h" |
static void usb_api_blocking_completion(struct urb *urb, struct pt_regs *regs) |
{ |
//printk(KERN_INFO "api\n"); |
complete((struct completion *)urb->context); |
} |
static void timeout_kill(unsigned long data) |
{ |
struct urb *urb = (struct urb *) data; |
dev_warn(&urb->dev->dev, "%s timeout on ep%d%s\n", |
usb_pipecontrol(urb->pipe) ? "control" : "bulk", |
usb_pipeendpoint(urb->pipe), |
usb_pipein(urb->pipe) ? "in" : "out"); |
usb_unlink_urb(urb); |
} |
// Starts urb and waits for completion or timeout |
// note that this call is NOT interruptible, while |
// many device driver i/o requests should be interruptible |
static int usb_start_wait_urb(struct urb *urb, int timeout, int* actual_length) |
{ |
struct completion done; |
struct timer_list timer; |
int status; |
struct pt_regs *regs; |
init_completion(&done); |
urb->context = &done; |
urb->transfer_flags |= URB_ASYNC_UNLINK; |
urb->actual_length = 0; |
status = usb_submit_urb(urb, GFP_NOIO); |
if (status == 0) { |
if (timeout > 0) { |
init_timer(&timer); |
timer.expires = jiffies26 + timeout; |
timer.data = (unsigned long)urb; |
timer.function = timeout_kill; |
/* grr. timeout _should_ include submit delays. */ |
add_timer(&timer); |
} |
wait_for_completion(&done); |
status = urb->status; |
/* note: HCDs return ETIMEDOUT for other reasons too */ |
if (status == -ECONNRESET) |
status = -ETIMEDOUT; |
if (timeout > 0) |
del_timer_sync(&timer); |
} |
if (actual_length) |
*actual_length = urb->actual_length; |
usb_free_urb(urb); |
return status; |
} |
/*-------------------------------------------------------------------*/ |
// returns status (negative) or length (positive) |
int usb_internal_control_msg(struct usb_device *usb_dev, unsigned int pipe, |
struct usb_ctrlrequest *cmd, void *data, int len, int timeout) |
{ |
struct urb *urb; |
int retv; |
int length; |
urb = usb_alloc_urb(0, GFP_NOIO); |
if (!urb) |
return -ENOMEM; |
usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char*)cmd, data, len, |
usb_api_blocking_completion, 0); |
retv = usb_start_wait_urb(urb, timeout, &length); |
if (retv < 0) |
return retv; |
else |
return length; |
} |
/** |
* usb_control_msg - Builds a control urb, sends it off and waits for completion |
* @dev: pointer to the usb device to send the message to |
* @pipe: endpoint "pipe" to send the message to |
* @request: USB message request value |
* @requesttype: USB message request type value |
* @value: USB message value |
* @index: USB message index value |
* @data: pointer to the data to send |
* @size: length in bytes of the data to send |
* @timeout: time in jiffies26 to wait for the message to complete before |
* timing out (if 0 the wait is forever) |
* Context: !in_interrupt () |
* |
* This function sends a simple control message to a specified endpoint |
* and waits for the message to complete, or timeout. |
* |
* If successful, it returns the number of bytes transferred, otherwise a negative error number. |
* |
* Don't use this function from within an interrupt context, like a |
* bottom half handler. If you need an asynchronous message, or need to send |
* a message from within interrupt context, use usb_submit_urb() |
* If a thread in your driver uses this call, make sure your disconnect() |
* method can wait for it to complete. Since you don't have a handle on |
* the URB used, you can't cancel the request. |
*/ |
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, |
__u16 value, __u16 index, void *data, __u16 size, int timeout) |
{ |
struct usb_ctrlrequest *dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_NOIO); |
int ret; |
if (!dr) |
return -ENOMEM; |
dr->bRequestType= requesttype; |
dr->bRequest = request; |
dr->wValue = cpu_to_le16p(&value); |
dr->wIndex = cpu_to_le16p(&index); |
dr->wLength = cpu_to_le16p(&size); |
//dbg("usb_control_msg"); |
ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout); |
kfree(dr); |
return ret; |
} |
/** |
* usb_bulk_msg - Builds a bulk urb, sends it off and waits for completion |
* @usb_dev: pointer to the usb device to send the message to |
* @pipe: endpoint "pipe" to send the message to |
* @data: pointer to the data to send |
* @len: length in bytes of the data to send |
* @actual_length: pointer to a location to put the actual length transferred in bytes |
* @timeout: time in jiffies26 to wait for the message to complete before |
* timing out (if 0 the wait is forever) |
* Context: !in_interrupt () |
* |
* This function sends a simple bulk message to a specified endpoint |
* and waits for the message to complete, or timeout. |
* |
* If successful, it returns 0, otherwise a negative error number. |
* The number of actual bytes transferred will be stored in the |
* actual_length paramater. |
* |
* Don't use this function from within an interrupt context, like a |
* bottom half handler. If you need an asynchronous message, or need to |
* send a message from within interrupt context, use usb_submit_urb() |
* If a thread in your driver uses this call, make sure your disconnect() |
* method can wait for it to complete. Since you don't have a handle on |
* the URB used, you can't cancel the request. |
*/ |
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, |
void *data, int len, int *actual_length, int timeout) |
{ |
struct urb *urb; |
if (len < 0) |
return -EINVAL; |
urb=usb_alloc_urb(0, GFP_KERNEL); |
if (!urb) |
return -ENOMEM; |
usb_fill_bulk_urb(urb, usb_dev, pipe, data, len, |
usb_api_blocking_completion, 0); |
return usb_start_wait_urb(urb,timeout,actual_length); |
} |
/*-------------------------------------------------------------------*/ |
static void sg_clean (struct usb_sg_request *io) |
{ |
if (io->urbs) { |
while (io->entries--) |
usb_free_urb (io->urbs [io->entries]); |
kfree (io->urbs); |
io->urbs = 0; |
} |
if (io->dev->dev.dma_mask != 0) |
usb_buffer_unmap_sg (io->dev, io->pipe, io->sg, io->nents); |
io->dev = 0; |
} |
static void sg_complete (struct urb *urb, struct pt_regs *regs) |
{ |
struct usb_sg_request *io = (struct usb_sg_request *) urb->context; |
unsigned long flags; |
spin_lock_irqsave (&io->lock, flags); |
/* In 2.5 we require hcds' endpoint queues not to progress after fault |
* reports, until the completion callback (this!) returns. That lets |
* device driver code (like this routine) unlink queued urbs first, |
* if it needs to, since the HC won't work on them at all. So it's |
* not possible for page N+1 to overwrite page N, and so on. |
* |
* That's only for "hard" faults; "soft" faults (unlinks) sometimes |
* complete before the HCD can get requests away from hardware, |
* though never during cleanup after a hard fault. |
*/ |
if (io->status |
&& (io->status != -ECONNRESET |
|| urb->status != -ECONNRESET) |
&& urb->actual_length) { |
dev_err (io->dev->bus->controller, |
"dev %s ep%d%s scatterlist error %d/%d\n", |
io->dev->devpath, |
usb_pipeendpoint (urb->pipe), |
usb_pipein (urb->pipe) ? "in" : "out", |
urb->status, io->status); |
// BUG (); |
} |
if (urb->status && urb->status != -ECONNRESET) { |
int i, found, status; |
io->status = urb->status; |
/* the previous urbs, and this one, completed already. |
* unlink pending urbs so they won't rx/tx bad data. |
*/ |
for (i = 0, found = 0; i < io->entries; i++) { |
if (!io->urbs [i]) |
continue; |
if (found) { |
status = usb_unlink_urb (io->urbs [i]); |
if (status != -EINPROGRESS && status != -EBUSY) |
dev_err (&io->dev->dev, |
"%s, unlink --> %d\n", |
__FUNCTION__, status); |
} else if (urb == io->urbs [i]) |
found = 1; |
} |
} |
urb->dev = 0; |
/* on the last completion, signal usb_sg_wait() */ |
io->bytes += urb->actual_length; |
io->count--; |
if (!io->count) |
complete (&io->complete); |
spin_unlock_irqrestore (&io->lock, flags); |
} |
/** |
* usb_sg_init - initializes scatterlist-based bulk/interrupt I/O request |
* @io: request block being initialized. until usb_sg_wait() returns, |
* treat this as a pointer to an opaque block of memory, |
* @dev: the usb device that will send or receive the data |
* @pipe: endpoint "pipe" used to transfer the data |
* @period: polling rate for interrupt endpoints, in frames or |
* (for high speed endpoints) microframes; ignored for bulk |
* @sg: scatterlist entries |
* @nents: how many entries in the scatterlist |
* @length: how many bytes to send from the scatterlist, or zero to |
* send every byte identified in the list. |
* @mem_flags: SLAB_* flags affecting memory allocations in this call |
* |
* Returns zero for success, else a negative errno value. This initializes a |
* scatter/gather request, allocating resources such as I/O mappings and urb |
* memory (except maybe memory used by USB controller drivers). |
* |
* The request must be issued using usb_sg_wait(), which waits for the I/O to |
* complete (or to be canceled) and then cleans up all resources allocated by |
* usb_sg_init(). |
* |
* The request may be canceled with usb_sg_cancel(), either before or after |
* usb_sg_wait() is called. |
*/ |
int usb_sg_init ( |
struct usb_sg_request *io, |
struct usb_device *dev, |
unsigned pipe, |
unsigned period, |
struct scatterlist *sg, |
int nents, |
size_t length, |
int mem_flags |
) |
{ |
int i; |
int urb_flags; |
int dma; |
{ |
int i; |
for (i=0; i<20; i++) |
{ |
wait_ms26(300); |
printk(KERN_INFO "usb_sg_init!!!!!!!\n"); |
} |
} |
if (!io || !dev || !sg |
|| usb_pipecontrol (pipe) |
|| usb_pipeisoc (pipe) |
|| nents <= 0) |
return -EINVAL; |
spin_lock_init (&io->lock); |
io->dev = dev; |
io->pipe = pipe; |
io->sg = sg; |
io->nents = nents; |
/* not all host controllers use DMA (like the mainstream pci ones); |
* they can use PIO (sl811) or be software over another transport. |
*/ |
dma = (dev->dev.dma_mask != 0); |
if (dma) |
io->entries = usb_buffer_map_sg (dev, pipe, sg, nents); |
else |
io->entries = nents; |
/* initialize all the urbs we'll use */ |
if (io->entries <= 0) |
return io->entries; |
io->count = 0; |
io->urbs = kmalloc (io->entries * sizeof *io->urbs, mem_flags); |
if (!io->urbs) |
goto nomem; |
urb_flags = URB_ASYNC_UNLINK | URB_NO_TRANSFER_DMA_MAP |
| URB_NO_INTERRUPT; |
if (usb_pipein (pipe)) |
urb_flags |= URB_SHORT_NOT_OK; |
for (i = 0; i < io->entries; i++, io->count = i) { |
unsigned len; |
io->urbs [i] = usb_alloc_urb (0, mem_flags); |
if (!io->urbs [i]) { |
io->entries = i; |
goto nomem; |
} |
io->urbs [i]->dev = 0; |
io->urbs [i]->pipe = pipe; |
io->urbs [i]->interval = period; |
io->urbs [i]->transfer_flags = urb_flags; |
io->urbs [i]->complete = sg_complete; |
io->urbs [i]->context = io; |
io->urbs [i]->status = -EINPROGRESS; |
io->urbs [i]->actual_length = 0; |
if (dma) { |
/* hc may use _only_ transfer_dma */ |
io->urbs [i]->transfer_dma = sg_dma_address (sg + i); |
len = sg_dma_len (sg + i); |
} else { |
/* hc may use _only_ transfer_buffer */ |
io->urbs [i]->transfer_buffer = |
page_address (sg [i].page) + sg [i].offset; |
len = sg [i].length; |
} |
if (length) { |
len = min_t (unsigned, len, length); |
length -= len; |
if (length == 0) |
io->entries = i + 1; |
} |
io->urbs [i]->transfer_buffer_length = len; |
} |
io->urbs [--i]->transfer_flags &= ~URB_NO_INTERRUPT; |
/* transaction state */ |
io->status = 0; |
io->bytes = 0; |
init_completion (&io->complete); |
return 0; |
nomem: |
sg_clean (io); |
return -ENOMEM; |
} |
/** |
* usb_sg_wait - synchronously execute scatter/gather request |
* @io: request block handle, as initialized with usb_sg_init(). |
* some fields become accessible when this call returns. |
* Context: !in_interrupt () |
* |
* This function blocks until the specified I/O operation completes. It |
* leverages the grouping of the related I/O requests to get good transfer |
* rates, by queueing the requests. At higher speeds, such queuing can |
* significantly improve USB throughput. |
* |
* There are three kinds of completion for this function. |
* (1) success, where io->status is zero. The number of io->bytes |
* transferred is as requested. |
* (2) error, where io->status is a negative errno value. The number |
* of io->bytes transferred before the error is usually less |
* than requested, and can be nonzero. |
* (3) cancelation, a type of error with status -ECONNRESET that |
* is initiated by usb_sg_cancel(). |
* |
* When this function returns, all memory allocated through usb_sg_init() or |
* this call will have been freed. The request block parameter may still be |
* passed to usb_sg_cancel(), or it may be freed. It could also be |
* reinitialized and then reused. |
* |
* Data Transfer Rates: |
* |
* Bulk transfers are valid for full or high speed endpoints. |
* The best full speed data rate is 19 packets of 64 bytes each |
* per frame, or 1216 bytes per millisecond. |
* The best high speed data rate is 13 packets of 512 bytes each |
* per microframe, or 52 KBytes per millisecond. |
* |
* The reason to use interrupt transfers through this API would most likely |
* be to reserve high speed bandwidth, where up to 24 KBytes per millisecond |
* could be transferred. That capability is less useful for low or full |
* speed interrupt endpoints, which allow at most one packet per millisecond, |
* of at most 8 or 64 bytes (respectively). |
*/ |
void usb_sg_wait (struct usb_sg_request *io) |
{ |
int i; |
unsigned long flags; |
/* queue the urbs. */ |
spin_lock_irqsave (&io->lock, flags); |
for (i = 0; i < io->entries && !io->status; i++) { |
int retval; |
io->urbs [i]->dev = io->dev; |
retval = usb_submit_urb (io->urbs [i], SLAB_ATOMIC); |
/* after we submit, let completions or cancelations fire; |
* we handshake using io->status. |
*/ |
spin_unlock_irqrestore (&io->lock, flags); |
switch (retval) { |
/* maybe we retrying will recover */ |
case -ENXIO: // hc didn't queue this one |
case -EAGAIN: |
case -ENOMEM: |
io->urbs [i]->dev = 0; |
retval = 0; |
i--; |
yield (); |
break; |
/* no error? continue immediately. |
* |
* NOTE: to work better with UHCI (4K I/O buffer may |
* need 3K of TDs) it may be good to limit how many |
* URBs are queued at once; N milliseconds? |
*/ |
case 0: |
cpu_relax (); |
break; |
/* fail any uncompleted urbs */ |
default: |
io->urbs [i]->dev = 0; |
io->urbs [i]->status = retval; |
dev_dbg (&io->dev->dev, "%s, submit --> %d\n", |
__FUNCTION__, retval); |
usb_sg_cancel (io); |
} |
spin_lock_irqsave (&io->lock, flags); |
if (retval && io->status == -ECONNRESET) |
io->status = retval; |
} |
spin_unlock_irqrestore (&io->lock, flags); |
/* OK, yes, this could be packaged as non-blocking. |
* So could the submit loop above ... but it's easier to |
* solve neither problem than to solve both! |
*/ |
wait_for_completion (&io->complete); |
sg_clean (io); |
} |
/** |
* usb_sg_cancel - stop scatter/gather i/o issued by usb_sg_wait() |
* @io: request block, initialized with usb_sg_init() |
* |
* This stops a request after it has been started by usb_sg_wait(). |
* It can also prevents one initialized by usb_sg_init() from starting, |
* so that call just frees resources allocated to the request. |
*/ |
void usb_sg_cancel (struct usb_sg_request *io) |
{ |
unsigned long flags; |
spin_lock_irqsave (&io->lock, flags); |
/* shut everything down, if it didn't already */ |
if (!io->status) { |
int i; |
io->status = -ECONNRESET; |
for (i = 0; i < io->entries; i++) { |
int retval; |
if (!io->urbs [i]->dev) |
continue; |
retval = usb_unlink_urb (io->urbs [i]); |
if (retval != -EINPROGRESS && retval != -EBUSY) |
dev_warn (&io->dev->dev, "%s, unlink --> %d\n", |
__FUNCTION__, retval); |
} |
} |
spin_unlock_irqrestore (&io->lock, flags); |
} |
/*-------------------------------------------------------------------*/ |
/** |
* usb_get_descriptor - issues a generic GET_DESCRIPTOR request |
* @dev: the device whose descriptor is being retrieved |
* @type: the descriptor type (USB_DT_*) |
* @index: the number of the descriptor |
* @buf: where to put the descriptor |
* @size: how big is "buf"? |
* Context: !in_interrupt () |
* |
* Gets a USB descriptor. Convenience functions exist to simplify |
* getting some types of descriptors. Use |
* usb_get_device_descriptor() for USB_DT_DEVICE, |
* and usb_get_string() or usb_string() for USB_DT_STRING. |
* Configuration descriptors (USB_DT_CONFIG) are part of the device |
* structure, at least for the current configuration. |
* In addition to a number of USB-standard descriptors, some |
* devices also use class-specific or vendor-specific descriptors. |
* |
* This call is synchronous, and may not be used in an interrupt context. |
* |
* Returns the number of bytes received on success, or else the status code |
* returned by the underlying usb_control_msg() call. |
*/ |
int usb_get_descriptor(struct usb_device *dev, unsigned char type, unsigned char index, void *buf, int size) |
{ |
int i = 5; |
int result = 0; |
memset(buf,0,size); // Make sure we parse really received data |
while (i--) { |
/* retries if the returned length was 0; flakey device */ |
if ((result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), |
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, |
(type << 8) + index, 0, buf, size, |
HZ * USB_CTRL_GET_TIMEOUT)) > 0 |
|| result == -EPIPE) |
break; |
} |
return result; |
} |
/** |
* usb_get_string - gets a string descriptor |
* @dev: the device whose string descriptor is being retrieved |
* @langid: code for language chosen (from string descriptor zero) |
* @index: the number of the descriptor |
* @buf: where to put the string |
* @size: how big is "buf"? |
* Context: !in_interrupt () |
* |
* Retrieves a string, encoded using UTF-16LE (Unicode, 16 bits per character, |
* in little-endian byte order). |
* The usb_string() function will often be a convenient way to turn |
* these strings into kernel-printable form. |
* |
* Strings may be referenced in device, configuration, interface, or other |
* descriptors, and could also be used in vendor-specific ways. |
* |
* This call is synchronous, and may not be used in an interrupt context. |
* |
* Returns the number of bytes received on success, or else the status code |
* returned by the underlying usb_control_msg() call. |
*/ |
int usb_get_string(struct usb_device *dev, unsigned short langid, unsigned char index, void *buf, int size) |
{ |
return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), |
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, |
(USB_DT_STRING << 8) + index, langid, buf, size, |
HZ * USB_CTRL_GET_TIMEOUT); |
} |
/** |
* usb_get_device_descriptor - (re)reads the device descriptor |
* @dev: the device whose device descriptor is being updated |
* Context: !in_interrupt () |
* |
* Updates the copy of the device descriptor stored in the device structure, |
* which dedicates space for this purpose. Note that several fields are |
* converted to the host CPU's byte order: the USB version (bcdUSB), and |
* vendors product and version fields (idVendor, idProduct, and bcdDevice). |
* That lets device drivers compare against non-byteswapped constants. |
* |
* There's normally no need to use this call, although some devices |
* will change their descriptors after events like updating firmware. |
* |
* This call is synchronous, and may not be used in an interrupt context. |
* |
* Returns the number of bytes received on success, or else the status code |
* returned by the underlying usb_control_msg() call. |
*/ |
int usb_get_device_descriptor(struct usb_device *dev) |
{ |
int ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, &dev->descriptor, |
sizeof(dev->descriptor)); |
if (ret >= 0) { |
le16_to_cpus(&dev->descriptor.bcdUSB); |
le16_to_cpus(&dev->descriptor.idVendor); |
le16_to_cpus(&dev->descriptor.idProduct); |
le16_to_cpus(&dev->descriptor.bcdDevice); |
} |
return ret; |
} |
/** |
* usb_get_status - issues a GET_STATUS call |
* @dev: the device whose status is being checked |
* @type: USB_RECIP_*; for device, interface, or endpoint |
* @target: zero (for device), else interface or endpoint number |
* @data: pointer to two bytes of bitmap data |
* Context: !in_interrupt () |
* |
* Returns device, interface, or endpoint status. Normally only of |
* interest to see if the device is self powered, or has enabled the |
* remote wakeup facility; or whether a bulk or interrupt endpoint |
* is halted ("stalled"). |
* |
* Bits in these status bitmaps are set using the SET_FEATURE request, |
* and cleared using the CLEAR_FEATURE request. The usb_clear_halt() |
* function should be used to clear halt ("stall") status. |
* |
* This call is synchronous, and may not be used in an interrupt context. |
* |
* Returns the number of bytes received on success, or else the status code |
* returned by the underlying usb_control_msg() call. |
*/ |
int usb_get_status(struct usb_device *dev, int type, int target, void *data) |
{ |
return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), |
USB_REQ_GET_STATUS, USB_DIR_IN | type, 0, target, data, 2, |
HZ * USB_CTRL_GET_TIMEOUT); |
} |
/** |
* usb_clear_halt - tells device to clear endpoint halt/stall condition |
* @dev: device whose endpoint is halted |
* @pipe: endpoint "pipe" being cleared |
* Context: !in_interrupt () |
* |
* This is used to clear halt conditions for bulk and interrupt endpoints, |
* as reported by URB completion status. Endpoints that are halted are |
* sometimes referred to as being "stalled". Such endpoints are unable |
* to transmit or receive data until the halt status is cleared. Any URBs |
* queued for such an endpoint should normally be unlinked by the driver |
* before clearing the halt condition, as described in sections 5.7.5 |
* and 5.8.5 of the USB 2.0 spec. |
* |
* Note that control and isochronous endpoints don't halt, although control |
* endpoints report "protocol stall" (for unsupported requests) using the |
* same status code used to report a true stall. |
* |
* This call is synchronous, and may not be used in an interrupt context. |
* |
* Returns zero on success, or else the status code returned by the |
* underlying usb_control_msg() call. |
*/ |
int usb_clear_halt(struct usb_device *dev, int pipe) |
{ |
int result; |
int endp = usb_pipeendpoint(pipe); |
if (usb_pipein (pipe)) |
endp |= USB_DIR_IN; |
/* we don't care if it wasn't halted first. in fact some devices |
* (like some ibmcam model 1 units) seem to expect hosts to make |
* this request for iso endpoints, which can't halt! |
*/ |
result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), |
USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT, 0, endp, NULL, 0, |
HZ * USB_CTRL_SET_TIMEOUT); |
/* don't un-halt or force to DATA0 except on success */ |
if (result < 0) |
return result; |
/* NOTE: seems like Microsoft and Apple don't bother verifying |
* the clear "took", so some devices could lock up if you check... |
* such as the Hagiwara FlashGate DUAL. So we won't bother. |
* |
* NOTE: make sure the logic here doesn't diverge much from |
* the copy in usb-storage, for as long as we need two copies. |
*/ |
/* toggle was reset by the clear, then ep was reactivated */ |
usb_settoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe), 0); |
usb_endpoint_running(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)); |
return 0; |
} |
/** |
* usb_disable_endpoint -- Disable an endpoint by address |
* @dev: the device whose endpoint is being disabled |
* @epaddr: the endpoint's address. Endpoint number for output, |
* endpoint number + USB_DIR_IN for input |
* |
* Deallocates hcd/hardware state for this endpoint ... and nukes all |
* pending urbs. |
* |
* If the HCD hasn't registered a disable() function, this marks the |
* endpoint as halted and sets its maxpacket size to 0 to prevent |
* further submissions. |
*/ |
void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr) |
{ |
if (dev && dev->bus && dev->bus->op && dev->bus->op->disable) |
dev->bus->op->disable(dev, epaddr); |
else { |
unsigned int epnum = epaddr & USB_ENDPOINT_NUMBER_MASK; |
if (usb_endpoint_out(epaddr)) { |
usb_endpoint_halt(dev, epnum, 1); |
dev->epmaxpacketout[epnum] = 0; |
} else { |
usb_endpoint_halt(dev, epnum, 0); |
dev->epmaxpacketin[epnum] = 0; |
} |
} |
} |
/** |
* usb_disable_interface -- Disable all endpoints for an interface |
* @dev: the device whose interface is being disabled |
* @intf: pointer to the interface descriptor |
* |
* Disables all the endpoints for the interface's current altsetting. |
*/ |
void usb_disable_interface(struct usb_device *dev, struct usb_interface *intf) |
{ |
struct usb_host_interface *hintf = |
&intf->altsetting[intf->act_altsetting]; |
int i; |
for (i = 0; i < hintf->desc.bNumEndpoints; ++i) { |
usb_disable_endpoint(dev, |
hintf->endpoint[i].desc.bEndpointAddress); |
} |
} |
/* |
* usb_disable_device - Disable all the endpoints for a USB device |
* @dev: the device whose endpoints are being disabled |
* @skip_ep0: 0 to disable endpoint 0, 1 to skip it. |
* |
* Disables all the device's endpoints, potentially including endpoint 0. |
* Deallocates hcd/hardware state for the endpoints (nuking all or most |
* pending urbs) and usbcore state for the interfaces, so that usbcore |
* must usb_set_configuration() before any interfaces could be used. |
*/ |
void usb_disable_device(struct usb_device *dev, int skip_ep0) |
{ |
int i; |
dev_dbg(&dev->dev, "%s nuking %s URBs\n", __FUNCTION__, |
skip_ep0 ? "non-ep0" : "all"); |
for (i = skip_ep0; i < 16; ++i) { |
usb_disable_endpoint(dev, i); |
usb_disable_endpoint(dev, i + USB_DIR_IN); |
} |
dev->toggle[0] = dev->toggle[1] = 0; |
dev->halted[0] = dev->halted[1] = 0; |
/* getting rid of interfaces will disconnect |
* any drivers bound to them (a key side effect) |
*/ |
if (dev->actconfig) { |
for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { |
struct usb_interface *interface; |
/* remove this interface */ |
interface = dev->actconfig->interface[i]; |
dev_dbg (&dev->dev, "unregistering interface %s\n", |
interface->dev.bus_id); |
device_del(&interface->dev); |
} |
dev->actconfig = 0; |
if (dev->state == USB_STATE_CONFIGURED) |
dev->state = USB_STATE_ADDRESS; |
} |
} |
/* |
* usb_enable_endpoint - Enable an endpoint for USB communications |
* @dev: the device whose interface is being enabled |
* @epd: pointer to the endpoint descriptor |
* |
* Marks the endpoint as running, resets its toggle, and stores |
* its maxpacket value. For control endpoints, both the input |
* and output sides are handled. |
*/ |
void usb_enable_endpoint(struct usb_device *dev, |
struct usb_endpoint_descriptor *epd) |
{ |
int maxsize = epd->wMaxPacketSize; |
unsigned int epaddr = epd->bEndpointAddress; |
unsigned int epnum = epaddr & USB_ENDPOINT_NUMBER_MASK; |
int is_control = ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == |
USB_ENDPOINT_XFER_CONTROL); |
if (usb_endpoint_out(epaddr) || is_control) { |
usb_endpoint_running(dev, epnum, 1); |
usb_settoggle(dev, epnum, 1, 0); |
dev->epmaxpacketout[epnum] = maxsize; |
} |
if (!usb_endpoint_out(epaddr) || is_control) { |
usb_endpoint_running(dev, epnum, 0); |
usb_settoggle(dev, epnum, 0, 0); |
dev->epmaxpacketin[epnum] = maxsize; |
} |
} |
/* |
* usb_enable_interface - Enable all the endpoints for an interface |
* @dev: the device whose interface is being enabled |
* @intf: pointer to the interface descriptor |
* |
* Enables all the endpoints for the interface's current altsetting. |
*/ |
void usb_enable_interface(struct usb_device *dev, |
struct usb_interface *intf) |
{ |
struct usb_host_interface *hintf = |
&intf->altsetting[intf->act_altsetting]; |
int i; |
for (i = 0; i < hintf->desc.bNumEndpoints; ++i) |
usb_enable_endpoint(dev, &hintf->endpoint[i].desc); |
} |
/** |
* usb_set_interface - Makes a particular alternate setting be current |
* @dev: the device whose interface is being updated |
* @interface: the interface being updated |
* @alternate: the setting being chosen. |
* Context: !in_interrupt () |
* |
* This is used to enable data transfers on interfaces that may not |
* be enabled by default. Not all devices support such configurability. |
* Only the driver bound to an interface may change its setting. |
* |
* Within any given configuration, each interface may have several |
* alternative settings. These are often used to control levels of |
* bandwidth consumption. For example, the default setting for a high |
* speed interrupt endpoint may not send more than 64 bytes per microframe, |
* while interrupt transfers of up to 3KBytes per microframe are legal. |
* Also, isochronous endpoints may never be part of an |
* interface's default setting. To access such bandwidth, alternate |
* interface settings must be made current. |
* |
* Note that in the Linux USB subsystem, bandwidth associated with |
* an endpoint in a given alternate setting is not reserved until an URB |
* is submitted that needs that bandwidth. Some other operating systems |
* allocate bandwidth early, when a configuration is chosen. |
* |
* This call is synchronous, and may not be used in an interrupt context. |
* Also, drivers must not change altsettings while urbs are scheduled for |
* endpoints in that interface; all such urbs must first be completed |
* (perhaps forced by unlinking). |
* |
* Returns zero on success, or else the status code returned by the |
* underlying usb_control_msg() call. |
*/ |
int usb_set_interface(struct usb_device *dev, int interface, int alternate) |
{ |
struct usb_interface *iface; |
int ret; |
int manual = 0; |
iface = usb_ifnum_to_if(dev, interface); |
if (!iface) { |
warn("selecting invalid interface %d", interface); |
return -EINVAL; |
} |
if (alternate < 0 || alternate >= iface->num_altsetting) |
return -EINVAL; |
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), |
USB_REQ_SET_INTERFACE, USB_RECIP_INTERFACE, |
iface->altsetting[alternate] |
.desc.bAlternateSetting, |
interface, NULL, 0, HZ * 5); |
/* 9.4.10 says devices don't need this and are free to STALL the |
* request if the interface only has one alternate setting. |
*/ |
if (ret == -EPIPE && iface->num_altsetting == 1) { |
dbg("manual set_interface for dev %d, iface %d, alt %d", |
dev->devnum, interface, alternate); |
manual = 1; |
} else if (ret < 0) |
return ret; |
/* FIXME drivers shouldn't need to replicate/bugfix the logic here |
* when they implement async or easily-killable versions of this or |
* other "should-be-internal" functions (like clear_halt). |
* should hcd+usbcore postprocess control requests? |
*/ |
/* prevent submissions using previous endpoint settings */ |
usb_disable_interface(dev, iface); |
iface->act_altsetting = alternate; |
/* If the interface only has one altsetting and the device didn't |
* accept the request, we attempt to carry out the equivalent action |
* by manually clearing the HALT feature for each endpoint in the |
* new altsetting. |
*/ |
if (manual) { |
struct usb_host_interface *iface_as = |
&iface->altsetting[alternate]; |
int i; |
for (i = 0; i < iface_as->desc.bNumEndpoints; i++) { |
unsigned int epaddr = |
iface_as->endpoint[i].desc.bEndpointAddress; |
unsigned int pipe = |
__create_pipe(dev, USB_ENDPOINT_NUMBER_MASK & epaddr) |
| (usb_endpoint_out(epaddr) ? USB_DIR_OUT : USB_DIR_IN); |
usb_clear_halt(dev, pipe); |
} |
} |
/* 9.1.1.5: reset toggles for all endpoints in the new altsetting |
* |
* Note: |
* Despite EP0 is always present in all interfaces/AS, the list of |
* endpoints from the descriptor does not contain EP0. Due to its |
* omnipresence one might expect EP0 being considered "affected" by |
* any SetInterface request and hence assume toggles need to be reset. |
* However, EP0 toggles are re-synced for every individual transfer |
* during the SETUP stage - hence EP0 toggles are "don't care" here. |
* (Likewise, EP0 never "halts" on well designed devices.) |
*/ |
usb_enable_interface(dev, iface); |
return 0; |
} |
/** |
* usb_reset_configuration - lightweight device reset |
* @dev: the device whose configuration is being reset |
* |
* This issues a standard SET_CONFIGURATION request to the device using |
* the current configuration. The effect is to reset most USB-related |
* state in the device, including interface altsettings (reset to zero), |
* endpoint halts (cleared), and data toggle (only for bulk and interrupt |
* endpoints). Other usbcore state is unchanged, including bindings of |
* usb device drivers to interfaces. |
* |
* Because this affects multiple interfaces, avoid using this with composite |
* (multi-interface) devices. Instead, the driver for each interface may |
* use usb_set_interface() on the interfaces it claims. Resetting the whole |
* configuration would affect other drivers' interfaces. |
* |
* Returns zero on success, else a negative error code. |
*/ |
int usb_reset_configuration(struct usb_device *dev) |
{ |
int i, retval; |
struct usb_host_config *config; |
/* caller must own dev->serialize (config won't change) |
* and the usb bus readlock (so driver bindings are stable); |
* so calls during probe() are fine |
*/ |
for (i = 1; i < 16; ++i) { |
usb_disable_endpoint(dev, i); |
usb_disable_endpoint(dev, i + USB_DIR_IN); |
} |
config = dev->actconfig; |
retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), |
USB_REQ_SET_CONFIGURATION, 0, |
config->desc.bConfigurationValue, 0, |
NULL, 0, HZ * USB_CTRL_SET_TIMEOUT); |
if (retval < 0) { |
dev->state = USB_STATE_ADDRESS; |
return retval; |
} |
dev->toggle[0] = dev->toggle[1] = 0; |
dev->halted[0] = dev->halted[1] = 0; |
/* re-init hc/hcd interface/endpoint state */ |
for (i = 0; i < config->desc.bNumInterfaces; i++) { |
struct usb_interface *intf = config->interface[i]; |
intf->act_altsetting = 0; |
usb_enable_interface(dev, intf); |
} |
return 0; |
} |
/** |
* usb_set_configuration - Makes a particular device setting be current |
* @dev: the device whose configuration is being updated |
* @configuration: the configuration being chosen. |
* Context: !in_interrupt () |
* |
* This is used to enable non-default device modes. Not all devices |
* use this kind of configurability; many devices only have one |
* configuration. |
* |
* USB device configurations may affect Linux interoperability, |
* power consumption and the functionality available. For example, |
* the default configuration is limited to using 100mA of bus power, |
* so that when certain device functionality requires more power, |
* and the device is bus powered, that functionality should be in some |
* non-default device configuration. Other device modes may also be |
* reflected as configuration options, such as whether two ISDN |
* channels are available independently; and choosing between open |
* standard device protocols (like CDC) or proprietary ones. |
* |
* Note that USB has an additional level of device configurability, |
* associated with interfaces. That configurability is accessed using |
* usb_set_interface(). |
* |
* This call is synchronous. The calling context must be able to sleep, |
* and must not hold the driver model lock for USB; usb device driver |
* probe() methods may not use this routine. |
* |
* Returns zero on success, or else the status code returned by the |
* underlying call that failed. On succesful completion, each interface |
* in the original device configuration has been destroyed, and each one |
* in the new configuration has been probed by all relevant usb device |
* drivers currently known to the kernel. |
*/ |
int usb_set_configuration(struct usb_device *dev, int configuration) |
{ |
int i, ret; |
struct usb_host_config *cp = NULL; |
/* dev->serialize guards all config changes */ |
down(&dev->serialize); |
for (i=0; i<dev->descriptor.bNumConfigurations; i++) { |
if (dev->config[i].desc.bConfigurationValue == configuration) { |
cp = &dev->config[i]; |
break; |
} |
} |
if ((!cp && configuration != 0)) { |
ret = -EINVAL; |
goto out; |
} |
if (cp && configuration == 0) |
dev_warn(&dev->dev, "config 0 descriptor??\n"); |
/* if it's already configured, clear out old state first. |
* getting rid of old interfaces means unbinding their drivers. |
*/ |
if (dev->state != USB_STATE_ADDRESS) |
usb_disable_device (dev, 1); // Skip ep0 |
if ((ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), |
USB_REQ_SET_CONFIGURATION, 0, configuration, 0, |
NULL, 0, HZ * USB_CTRL_SET_TIMEOUT)) < 0) |
goto out; |
dev->actconfig = cp; |
if (!configuration) |
dev->state = USB_STATE_ADDRESS; |
else { |
dev->state = USB_STATE_CONFIGURED; |
/* re-initialize hc/hcd/usbcore interface/endpoint state. |
* this triggers binding of drivers to interfaces; and |
* maybe probe() calls will choose different altsettings. |
*/ |
for (i = 0; i < cp->desc.bNumInterfaces; ++i) { |
struct usb_interface *intf = cp->interface[i]; |
struct usb_interface_descriptor *desc; |
intf->act_altsetting = 0; |
desc = &intf->altsetting [0].desc; |
usb_enable_interface(dev, intf); |
intf->dev.parent = &dev->dev; |
intf->dev.driver = NULL; |
intf->dev.bus = &usb_bus_type; |
intf->dev.dma_mask = dev->dev.dma_mask; |
sprintf26 (&intf->dev.bus_id[0], "%d-%s:%d.%d", |
dev->bus->busnum, dev->devpath, |
configuration, |
desc->bInterfaceNumber); |
dev_dbg (&dev->dev, |
"registering %s (config #%d, interface %d)\n", |
intf->dev.bus_id, configuration, |
desc->bInterfaceNumber); |
device_add (&intf->dev); |
usb_create_driverfs_intf_files (intf); |
} |
} |
out: |
up(&dev->serialize); |
return ret; |
} |
/** |
* usb_string - returns ISO 8859-1 version of a string descriptor |
* @dev: the device whose string descriptor is being retrieved |
* @index: the number of the descriptor |
* @buf: where to put the string |
* @size: how big is "buf"? |
* Context: !in_interrupt () |
* |
* This converts the UTF-16LE encoded strings returned by devices, from |
* usb_get_string_descriptor(), to null-terminated ISO-8859-1 encoded ones |
* that are more usable in most kernel contexts. Note that all characters |
* in the chosen descriptor that can't be encoded using ISO-8859-1 |
* are converted to the question mark ("?") character, and this function |
* chooses strings in the first language supported by the device. |
* |
* The ASCII (or, redundantly, "US-ASCII") character set is the seven-bit |
* subset of ISO 8859-1. ISO-8859-1 is the eight-bit subset of Unicode, |
* and is appropriate for use many uses of English and several other |
* Western European languages. (But it doesn't include the "Euro" symbol.) |
* |
* This call is synchronous, and may not be used in an interrupt context. |
* |
* Returns length of the string (>= 0) or usb_control_msg status (< 0). |
*/ |
int usb_string(struct usb_device *dev, int index, char *buf, size_t size) |
{ |
unsigned char *tbuf; |
int err, len; |
unsigned int u, idx; |
if (size <= 0 || !buf || !index) |
return -EINVAL; |
buf[0] = 0; |
tbuf = kmalloc(256, GFP_KERNEL); |
if (!tbuf) |
return -ENOMEM; |
/* get langid for strings if it's not yet known */ |
if (!dev->have_langid) { |
err = usb_get_string(dev, 0, 0, tbuf, 4); |
if (err < 0) { |
err("error getting string descriptor 0 (error=%d)", err); |
goto errout; |
} else if (err < 4 || tbuf[0] < 4) { |
err("string descriptor 0 too short"); |
err = -EINVAL; |
goto errout; |
} else { |
dev->have_langid = -1; |
dev->string_langid = tbuf[2] | (tbuf[3]<< 8); |
/* always use the first langid listed */ |
dbg("USB device number %d default language ID 0x%x", |
dev->devnum, dev->string_langid); |
} |
} |
/* |
* ask for the length of the string |
*/ |
err = usb_get_string(dev, dev->string_langid, index, tbuf, 2); |
if(err<2) |
goto errout; |
len=tbuf[0]; |
err = usb_get_string(dev, dev->string_langid, index, tbuf, len); |
if (err < 0) |
goto errout; |
size--; /* leave room for trailing NULL char in output buffer */ |
for (idx = 0, u = 2; u < err; u += 2) { |
if (idx >= size) |
break; |
if (tbuf[u+1]) /* high byte */ |
buf[idx++] = '?'; /* non ISO-8859-1 character */ |
else |
buf[idx++] = tbuf[u]; |
} |
buf[idx] = 0; |
err = idx; |
errout: |
kfree(tbuf); |
return err; |
} |
// synchronous request completion model |
EXPORT_SYMBOL(usb_control_msg); |
EXPORT_SYMBOL(usb_bulk_msg); |
EXPORT_SYMBOL(usb_sg_init); |
EXPORT_SYMBOL(usb_sg_cancel); |
EXPORT_SYMBOL(usb_sg_wait); |
// synchronous control message convenience routines |
EXPORT_SYMBOL(usb_get_descriptor); |
EXPORT_SYMBOL(usb_get_device_descriptor); |
EXPORT_SYMBOL(usb_get_status); |
EXPORT_SYMBOL(usb_get_string); |
EXPORT_SYMBOL(usb_string); |
// synchronous calls that also maintain usbcore state |
EXPORT_SYMBOL(usb_clear_halt); |
EXPORT_SYMBOL(usb_reset_configuration); |
EXPORT_SYMBOL(usb_set_configuration); |
EXPORT_SYMBOL(usb_set_interface); |
/shark/trunk/drivers/usb/core/driverfs.c |
---|
0,0 → 1,227 |
/* |
* drivers/usb/core/driverfs.c |
* |
* (C) Copyright 2002 David Brownell |
* (C) Copyright 2002 Greg Kroah-Hartman |
* (C) Copyright 2002 IBM Corp. |
* |
* All of the driverfs file attributes for usb devices and interfaces. |
* |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/kernel.h> |
#ifdef CONFIG_USB_DEBUG |
#define DEBUG |
#else |
#undef DEBUG |
#endif |
#include <linux/usb.h> |
#include "usb.h" |
/* Active configuration fields */ |
#define usb_actconfig_show(field, format_string) \ |
static ssize_t \ |
show_##field (struct device *dev, char *buf) \ |
{ \ |
struct usb_device *udev; \ |
\ |
udev = to_usb_device (dev); \ |
if (udev->actconfig) \ |
return sprintf26 (buf, format_string, udev->actconfig->desc.field); \ |
else return 0; \ |
} \ |
#define usb_actconfig_attr(field, format_string) \ |
usb_actconfig_show(field,format_string) \ |
static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); |
usb_actconfig_attr (bNumInterfaces, "%2d\n") |
usb_actconfig_attr (bmAttributes, "%2x\n") |
usb_actconfig_attr (bMaxPower, "%3dmA\n") |
/* configuration value is always present, and r/w */ |
usb_actconfig_show(bConfigurationValue,"%u\n"); |
static ssize_t |
set_bConfigurationValue (struct device *dev, const char *buf, size_t count) |
{ |
struct usb_device *udev = udev = to_usb_device (dev); |
int config, value; |
if (sscanf26(buf, "%u", &config) != 1 || config > 255) |
return -EINVAL; |
value = usb_set_configuration (udev, config); |
return (value < 0) ? value : count; |
} |
static DEVICE_ATTR(bConfigurationValue, S_IRUGO | S_IWUSR, |
show_bConfigurationValue, set_bConfigurationValue); |
/* String fields */ |
static ssize_t show_product (struct device *dev, char *buf) |
{ |
struct usb_device *udev; |
int len; |
udev = to_usb_device (dev); |
len = usb_string(udev, udev->descriptor.iProduct, buf, PAGE_SIZE); |
if (len < 0) |
return 0; |
buf[len] = '\n'; |
buf[len+1] = 0; |
return len+1; |
} |
static DEVICE_ATTR(product,S_IRUGO,show_product,NULL); |
static ssize_t |
show_manufacturer (struct device *dev, char *buf) |
{ |
struct usb_device *udev; |
int len; |
udev = to_usb_device (dev); |
len = usb_string(udev, udev->descriptor.iManufacturer, buf, PAGE_SIZE); |
if (len < 0) |
return 0; |
buf[len] = '\n'; |
buf[len+1] = 0; |
return len+1; |
} |
static DEVICE_ATTR(manufacturer,S_IRUGO,show_manufacturer,NULL); |
static ssize_t |
show_serial (struct device *dev, char *buf) |
{ |
struct usb_device *udev; |
int len; |
udev = to_usb_device (dev); |
len = usb_string(udev, udev->descriptor.iSerialNumber, buf, PAGE_SIZE); |
if (len < 0) |
return 0; |
buf[len] = '\n'; |
buf[len+1] = 0; |
return len+1; |
} |
static DEVICE_ATTR(serial,S_IRUGO,show_serial,NULL); |
static ssize_t |
show_speed (struct device *dev, char *buf) |
{ |
struct usb_device *udev; |
char *speed; |
udev = to_usb_device (dev); |
switch (udev->speed) { |
case USB_SPEED_LOW: |
speed = "1.5"; |
break; |
case USB_SPEED_UNKNOWN: |
case USB_SPEED_FULL: |
speed = "12"; |
break; |
case USB_SPEED_HIGH: |
speed = "480"; |
break; |
default: |
speed = "unknown"; |
} |
return sprintf26 (buf, "%s\n", speed); |
} |
static DEVICE_ATTR(speed, S_IRUGO, show_speed, NULL); |
/* Descriptor fields */ |
#define usb_descriptor_attr(field, format_string) \ |
static ssize_t \ |
show_##field (struct device *dev, char *buf) \ |
{ \ |
struct usb_device *udev; \ |
\ |
udev = to_usb_device (dev); \ |
return sprintf26 (buf, format_string, udev->descriptor.field); \ |
} \ |
static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); |
usb_descriptor_attr (idVendor, "%04x\n") |
usb_descriptor_attr (idProduct, "%04x\n") |
usb_descriptor_attr (bcdDevice, "%04x\n") |
usb_descriptor_attr (bDeviceClass, "%02x\n") |
usb_descriptor_attr (bDeviceSubClass, "%02x\n") |
usb_descriptor_attr (bDeviceProtocol, "%02x\n") |
usb_descriptor_attr (bNumConfigurations, "%d\n") |
void usb_create_driverfs_dev_files (struct usb_device *udev) |
{ |
struct device *dev = &udev->dev; |
/* current configuration's attributes */ |
device_create_file (dev, &dev_attr_bNumInterfaces); |
device_create_file (dev, &dev_attr_bConfigurationValue); |
device_create_file (dev, &dev_attr_bmAttributes); |
device_create_file (dev, &dev_attr_bMaxPower); |
/* device attributes */ |
device_create_file (dev, &dev_attr_idVendor); |
device_create_file (dev, &dev_attr_idProduct); |
device_create_file (dev, &dev_attr_bcdDevice); |
device_create_file (dev, &dev_attr_bDeviceClass); |
device_create_file (dev, &dev_attr_bDeviceSubClass); |
device_create_file (dev, &dev_attr_bDeviceProtocol); |
device_create_file (dev, &dev_attr_bNumConfigurations); |
/* speed varies depending on how you connect the device */ |
device_create_file (dev, &dev_attr_speed); |
// FIXME iff there are other speed configs, show how many |
if (udev->descriptor.iManufacturer) |
device_create_file (dev, &dev_attr_manufacturer); |
if (udev->descriptor.iProduct) |
device_create_file (dev, &dev_attr_product); |
if (udev->descriptor.iSerialNumber) |
device_create_file (dev, &dev_attr_serial); |
} |
/* Interface fields */ |
#define usb_intf_attr(field, format_string) \ |
static ssize_t \ |
show_##field (struct device *dev, char *buf) \ |
{ \ |
struct usb_interface *intf; \ |
int alt; \ |
\ |
intf = to_usb_interface (dev); \ |
alt = intf->act_altsetting; \ |
\ |
return sprintf26 (buf, format_string, intf->altsetting[alt].desc.field); \ |
} \ |
static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); |
usb_intf_attr (bInterfaceNumber, "%02x\n") |
usb_intf_attr (bAlternateSetting, "%2d\n") |
usb_intf_attr (bNumEndpoints, "%02x\n") |
usb_intf_attr (bInterfaceClass, "%02x\n") |
usb_intf_attr (bInterfaceSubClass, "%02x\n") |
usb_intf_attr (bInterfaceProtocol, "%02x\n") |
usb_intf_attr (iInterface, "%02x\n") |
void usb_create_driverfs_intf_files (struct usb_interface *intf) |
{ |
device_create_file (&intf->dev, &dev_attr_bInterfaceNumber); |
device_create_file (&intf->dev, &dev_attr_bAlternateSetting); |
device_create_file (&intf->dev, &dev_attr_bNumEndpoints); |
device_create_file (&intf->dev, &dev_attr_bInterfaceClass); |
device_create_file (&intf->dev, &dev_attr_bInterfaceSubClass); |
device_create_file (&intf->dev, &dev_attr_bInterfaceProtocol); |
device_create_file (&intf->dev, &dev_attr_iInterface); |
} |
/shark/trunk/drivers/usb/core/urb.c |
---|
0,0 → 1,422 |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/module.h> |
#include <linux/string.h> |
#include <linux/bitops.h> |
#include <linux/slab.h> |
#include <linux/init.h> |
#ifdef CONFIG_USB_DEBUG |
#define DEBUG |
#else |
#undef DEBUG |
#endif |
#include <linux/usb.h> |
#include "hcd.h" |
/** |
* usb_init_urb - initializes a urb so that it can be used by a USB driver |
* @urb: pointer to the urb to initialize |
* |
* Initializes a urb so that the USB subsystem can use it properly. |
* |
* If a urb is created with a call to usb_alloc_urb() it is not |
* necessary to call this function. Only use this if you allocate the |
* space for a struct urb on your own. If you call this function, be |
* careful when freeing the memory for your urb that it is no longer in |
* use by the USB core. |
* |
* Only use this function if you _really_ understand what you are doing. |
*/ |
void usb_init_urb(struct urb *urb) |
{ |
if (urb) { |
memset(urb, 0, sizeof(*urb)); |
urb->count = (atomic_t)ATOMIC_INIT(1); |
spin_lock_init(&urb->lock); |
} |
} |
/** |
* usb_alloc_urb - creates a new urb for a USB driver to use |
* @iso_packets: number of iso packets for this urb |
* @mem_flags: the type of memory to allocate, see kmalloc() for a list of |
* valid options for this. |
* |
* Creates an urb for the USB driver to use, initializes a few internal |
* structures, incrementes the usage counter, and returns a pointer to it. |
* |
* If no memory is available, NULL is returned. |
* |
* If the driver want to use this urb for interrupt, control, or bulk |
* endpoints, pass '0' as the number of iso packets. |
* |
* The driver must call usb_free_urb() when it is finished with the urb. |
*/ |
struct urb *usb_alloc_urb(int iso_packets, int mem_flags) |
{ |
struct urb *urb; |
urb = (struct urb *)kmalloc(sizeof(struct urb) + |
iso_packets * sizeof(struct usb_iso_packet_descriptor), |
mem_flags); |
if (!urb) { |
err("alloc_urb: kmalloc failed"); |
return NULL; |
} |
usb_init_urb(urb); |
return urb; |
} |
/** |
* usb_free_urb - frees the memory used by a urb when all users of it are finished |
* @urb: pointer to the urb to free |
* |
* Must be called when a user of a urb is finished with it. When the last user |
* of the urb calls this function, the memory of the urb is freed. |
* |
* Note: The transfer buffer associated with the urb is not freed, that must be |
* done elsewhere. |
*/ |
void usb_free_urb(struct urb *urb) |
{ |
if (urb) |
if (atomic_dec_and_test(&urb->count)) |
kfree(urb); |
} |
/** |
* usb_get_urb - increments the reference count of the urb |
* @urb: pointer to the urb to modify |
* |
* This must be called whenever a urb is transferred from a device driver to a |
* host controller driver. This allows proper reference counting to happen |
* for urbs. |
* |
* A pointer to the urb with the incremented reference counter is returned. |
*/ |
struct urb * usb_get_urb(struct urb *urb) |
{ |
if (urb) { |
atomic_inc(&urb->count); |
return urb; |
} else |
return NULL; |
} |
/*-------------------------------------------------------------------*/ |
/** |
* usb_submit_urb - issue an asynchronous transfer request for an endpoint |
* @urb: pointer to the urb describing the request |
* @mem_flags: the type of memory to allocate, see kmalloc() for a list |
* of valid options for this. |
* |
* This submits a transfer request, and transfers control of the URB |
* describing that request to the USB subsystem. Request completion will |
* be indicated later, asynchronously, by calling the completion handler. |
* The three types of completion are success, error, and unlink |
* (also called "request cancellation"). |
* URBs may be submitted in interrupt context. |
* |
* The caller must have correctly initialized the URB before submitting |
* it. Functions such as usb_fill_bulk_urb() and usb_fill_control_urb() are |
* available to ensure that most fields are correctly initialized, for |
* the particular kind of transfer, although they will not initialize |
* any transfer flags. |
* |
* Successful submissions return 0; otherwise this routine returns a |
* negative error number. If the submission is successful, the complete() |
* callback from the urb will be called exactly once, when the USB core and |
* host controller driver are finished with the urb. When the completion |
* function is called, control of the URB is returned to the device |
* driver which issued the request. The completion handler may then |
* immediately free or reuse that URB. |
* |
* For control endpoints, the synchronous usb_control_msg() call is |
* often used (in non-interrupt context) instead of this call. |
* That is often used through convenience wrappers, for the requests |
* that are standardized in the USB 2.0 specification. For bulk |
* endpoints, a synchronous usb_bulk_msg() call is available. |
* |
* Request Queuing: |
* |
* URBs may be submitted to endpoints before previous ones complete, to |
* minimize the impact of interrupt latencies and system overhead on data |
* throughput. This is required for continuous isochronous data streams, |
* and may also be required for some kinds of interrupt transfers. Such |
* queueing also maximizes bandwidth utilization by letting USB controllers |
* start work on later requests before driver software has finished the |
* completion processing for earlier requests. |
* |
* Bulk and Isochronous URBs may always be queued. At this writing, all |
* mainstream host controller drivers support queueing for control and |
* interrupt transfer requests. |
* |
* Reserved Bandwidth Transfers: |
* |
* Periodic transfers (interrupt or isochronous) are performed repeatedly, |
* using the interval specified in the urb. Submitting the first urb to |
* the endpoint reserves the bandwidth necessary to make those transfers. |
* If the USB subsystem can't allocate sufficient bandwidth to perform |
* the periodic request, submitting such a periodic request should fail. |
* |
* Device drivers must explicitly request that repetition, by ensuring that |
* some URB is always on the endpoint's queue (except possibly for short |
* periods during completion callacks). When there is no longer an urb |
* queued, the endpoint's bandwidth reservation is canceled. This means |
* drivers can use their completion handlers to ensure they keep bandwidth |
* they need, by reinitializing and resubmitting the just-completed urb |
* until the driver longer needs that periodic bandwidth. |
* |
* Memory Flags: |
* |
* The general rules for how to decide which mem_flags to use |
* are the same as for kmalloc. There are four |
* different possible values; GFP_KERNEL, GFP_NOFS, GFP_NOIO and |
* GFP_ATOMIC. |
* |
* GFP_NOFS is not ever used, as it has not been implemented yet. |
* |
* GFP_ATOMIC is used when |
* (a) you are inside a completion handler, an interrupt, bottom half, |
* tasklet or timer, or |
* (b) you are holding a spinlock or rwlock (does not apply to |
* semaphores), or |
* (c) current->state != TASK_RUNNING, this is the case only after |
* you've changed it. |
* |
* GFP_NOIO is used in the block io path and error handling of storage |
* devices. |
* |
* All other situations use GFP_KERNEL. |
* |
* Some more specific rules for mem_flags can be inferred, such as |
* (1) start_xmit, timeout, and receive methods of network drivers must |
* use GFP_ATOMIC (they are called with a spinlock held); |
* (2) queuecommand methods of scsi drivers must use GFP_ATOMIC (also |
* called with a spinlock held); |
* (3) If you use a kernel thread with a network driver you must use |
* GFP_NOIO, unless (b) or (c) apply; |
* (4) after you have done a down() you can use GFP_KERNEL, unless (b) or (c) |
* apply or your are in a storage driver's block io path; |
* (5) USB probe and disconnect can use GFP_KERNEL unless (b) or (c) apply; and |
* (6) changing firmware on a running storage or net device uses |
* GFP_NOIO, unless b) or c) apply |
* |
*/ |
int usb_submit_urb(struct urb *urb, int mem_flags) |
{ |
int pipe, temp, max; |
struct usb_device *dev; |
struct usb_operations *op; |
int is_out; |
if (!urb || urb->hcpriv || !urb->complete) |
return -EINVAL; |
if (!(dev = urb->dev) || |
(dev->state < USB_STATE_DEFAULT) || |
(!dev->bus) || (dev->devnum <= 0)) |
return -ENODEV; |
if (!(op = dev->bus->op) || !op->submit_urb) |
return -ENODEV; |
urb->status = -EINPROGRESS; |
urb->actual_length = 0; |
urb->bandwidth = 0; |
/* Lots of sanity checks, so HCDs can rely on clean data |
* and don't need to duplicate tests |
*/ |
pipe = urb->pipe; |
temp = usb_pipetype (pipe); |
is_out = usb_pipeout (pipe); |
if (!usb_pipecontrol (pipe) && dev->state < USB_STATE_CONFIGURED) |
return -ENODEV; |
/* (actually HCDs may need to duplicate this, endpoint might yet |
* stall due to queued bulk/intr transactions that complete after |
* we check) |
*/ |
if (usb_endpoint_halted (dev, usb_pipeendpoint (pipe), is_out)) |
return -EPIPE; |
/* FIXME there should be a sharable lock protecting us against |
* config/altsetting changes and disconnects, kicking in here. |
* (here == before maxpacket, and eventually endpoint type, |
* checks get made.) |
*/ |
max = usb_maxpacket (dev, pipe, is_out); |
if (max <= 0) { |
dbg ("%s: bogus endpoint %d-%s on usb-%s-%s (bad maxpacket %d)", |
__FUNCTION__, |
usb_pipeendpoint (pipe), is_out ? "OUT" : "IN", |
dev->bus->bus_name, dev->devpath, |
max); |
return -EMSGSIZE; |
} |
/* periodic transfers limit size per frame/uframe, |
* but drivers only control those sizes for ISO. |
* while we're checking, initialize return status. |
*/ |
if (temp == PIPE_ISOCHRONOUS) { |
int n, len; |
/* "high bandwidth" mode, 1-3 packets/uframe? */ |
if (dev->speed == USB_SPEED_HIGH) { |
int mult = 1 + ((max >> 11) & 0x03); |
max &= 0x03ff; |
max *= mult; |
} |
if (urb->number_of_packets <= 0) |
return -EINVAL; |
for (n = 0; n < urb->number_of_packets; n++) { |
len = urb->iso_frame_desc [n].length; |
if (len < 0 || len > max) |
return -EMSGSIZE; |
urb->iso_frame_desc [n].status = -EXDEV; |
urb->iso_frame_desc [n].actual_length = 0; |
} |
} |
/* the I/O buffer must be mapped/unmapped, except when length=0 */ |
if (urb->transfer_buffer_length < 0) |
return -EMSGSIZE; |
#ifdef DEBUG |
/* stuff that drivers shouldn't do, but which shouldn't |
* cause problems in HCDs if they get it wrong. |
*/ |
{ |
unsigned int orig_flags = urb->transfer_flags; |
unsigned int allowed; |
/* enforce simple/standard policy */ |
allowed = URB_ASYNC_UNLINK; // affects later unlinks |
allowed |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP); |
allowed |= URB_NO_INTERRUPT; |
switch (temp) { |
case PIPE_BULK: |
if (is_out) |
allowed |= URB_ZERO_PACKET; |
/* FALLTHROUGH */ |
case PIPE_CONTROL: |
allowed |= URB_NO_FSBR; /* only affects UHCI */ |
/* FALLTHROUGH */ |
default: /* all non-iso endpoints */ |
if (!is_out) |
allowed |= URB_SHORT_NOT_OK; |
break; |
case PIPE_ISOCHRONOUS: |
allowed |= URB_ISO_ASAP; |
break; |
} |
urb->transfer_flags &= allowed; |
/* fail if submitter gave bogus flags */ |
if (urb->transfer_flags != orig_flags) { |
err ("BOGUS urb flags, %x --> %x", |
orig_flags, urb->transfer_flags); |
return -EINVAL; |
} |
} |
#endif |
/* |
* Force periodic transfer intervals to be legal values that are |
* a power of two (so HCDs don't need to). |
* |
* FIXME want bus->{intr,iso}_sched_horizon values here. Each HC |
* supports different values... this uses EHCI/UHCI defaults (and |
* EHCI can use smaller non-default values). |
*/ |
switch (temp) { |
case PIPE_ISOCHRONOUS: |
case PIPE_INTERRUPT: |
/* too small? */ |
if (urb->interval <= 0) |
return -EINVAL; |
/* too big? */ |
switch (dev->speed) { |
case USB_SPEED_HIGH: /* units are microframes */ |
// NOTE usb handles 2^15 |
if (urb->interval > (1024 * 8)) |
urb->interval = 1024 * 8; |
temp = 1024 * 8; |
break; |
case USB_SPEED_FULL: /* units are frames/msec */ |
case USB_SPEED_LOW: |
if (temp == PIPE_INTERRUPT) { |
if (urb->interval > 255) |
return -EINVAL; |
// NOTE ohci only handles up to 32 |
temp = 128; |
} else { |
if (urb->interval > 1024) |
urb->interval = 1024; |
// NOTE usb and ohci handle up to 2^15 |
temp = 1024; |
} |
break; |
default: |
return -EINVAL; |
} |
/* power of two? */ |
while (temp > urb->interval) |
temp >>= 1; |
urb->interval = temp; |
} |
return op->submit_urb (urb, mem_flags); |
} |
/*-------------------------------------------------------------------*/ |
/** |
* usb_unlink_urb - abort/cancel a transfer request for an endpoint |
* @urb: pointer to urb describing a previously submitted request |
* |
* This routine cancels an in-progress request. URBs complete only |
* once per submission, and may be canceled only once per submission. |
* Successful cancelation means the requests's completion handler will |
* be called with a status code indicating that the request has been |
* canceled (rather than any other code) and will quickly be removed |
* from host controller data structures. |
* |
* When the URB_ASYNC_UNLINK transfer flag for the URB is clear, this |
* request is synchronous. Success is indicated by returning zero, |
* at which time the urb will have been unlinked and its completion |
* handler will have been called with urb->status -ENOENT. Failure is |
* indicated by any other return value. |
* |
* The synchronous cancelation mode may not be used |
* when unlinking an urb from an interrupt context, such as a bottom |
* half or a completion handler; or when holding a spinlock; or in |
* other cases when the caller can't schedule(). |
* |
* When the URB_ASYNC_UNLINK transfer flag for the URB is set, this |
* request is asynchronous. Success is indicated by returning -EINPROGRESS, |
* at which time the urb will normally not have been unlinked. |
* The completion function will see urb->status -ECONNRESET. Failure |
* is indicated by any other return value. |
*/ |
int usb_unlink_urb(struct urb *urb) |
{ |
if (urb && urb->dev && urb->dev->bus && urb->dev->bus->op) |
return urb->dev->bus->op->unlink_urb(urb); |
else |
return -ENODEV; |
} |
EXPORT_SYMBOL(usb_init_urb); |
EXPORT_SYMBOL(usb_alloc_urb); |
EXPORT_SYMBOL(usb_free_urb); |
EXPORT_SYMBOL(usb_get_urb); |
EXPORT_SYMBOL(usb_submit_urb); |
EXPORT_SYMBOL(usb_unlink_urb); |
/shark/trunk/drivers/usb/core/usb.c |
---|
0,0 → 1,1547 |
/* |
* drivers/usb/usb.c |
* |
* (C) Copyright Linus Torvalds 1999 |
* (C) Copyright Johannes Erdfelt 1999-2001 |
* (C) Copyright Andreas Gal 1999 |
* (C) Copyright Gregory P. Smith 1999 |
* (C) Copyright Deti Fliegl 1999 (new USB architecture) |
* (C) Copyright Randy Dunlap 2000 |
* (C) Copyright David Brownell 2000-2001 (kernel hotplug, usb_device_id, |
more docs, etc) |
* (C) Copyright Yggdrasil Computing, Inc. 2000 |
* (usb_device_id matching changes by Adam J. Richter) |
* (C) Copyright Greg Kroah-Hartman 2002-2003 |
* |
* NOTE! This is not actually a driver at all, rather this is |
* just a collection of helper routines that implement the |
* generic USB things that the real drivers can use.. |
* |
* Think of this as a "USB library" rather than anything else. |
* It should be considered a slave, with no callbacks. Callbacks |
* are evil. |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#ifdef CONFIG_USB_DEBUG |
#define DEBUG |
#else |
#undef DEBUG |
#endif |
#include <linux/module.h> |
#include <linux/string.h> |
#include <linux/bitops.h> |
#include <linux/slab.h> |
#include <linux/interrupt.h> /* for in_interrupt() */ |
#include <linux/kmod.h> |
#include <linux/init.h> |
#include <linux/spinlock.h> |
#include <linux/errno.h> |
#include <linux/smp_lock.h> |
#include <linux/usb.h> |
#include <asm/io.h> |
#include <asm/scatterlist.h> |
#include <linux/mm.h> |
#include <linux/dma-mapping.h> |
#include "hcd.h" |
#include "usb.h" |
extern int usb_hub_init(void); |
extern void usb_hub_cleanup(void); |
extern int usb_major_init(void); |
extern void usb_major_cleanup(void); |
extern int usb_host_init(void); |
extern void usb_host_cleanup(void); |
int nousb; /* Disable USB when built into kernel image */ |
/* Not honored on modular build */ |
static int generic_probe (struct device *dev) |
{ |
return 0; |
} |
static int generic_remove (struct device *dev) |
{ |
return 0; |
} |
static struct device_driver usb_generic_driver = { |
.name = "usb", |
.bus = &usb_bus_type, |
.probe = generic_probe, |
.remove = generic_remove, |
}; |
static int usb_generic_driver_data; |
/* needs to be called with BKL held */ |
int usb_probe_interface(struct device *dev) |
{ |
struct usb_interface * intf = to_usb_interface(dev); |
struct usb_driver * driver = to_usb_driver(dev->driver); |
const struct usb_device_id *id; |
int error = -ENODEV; |
dev_dbg(dev, "%s\n", __FUNCTION__); |
if (!driver->probe) |
return error; |
id = usb_match_id (intf, driver->id_table); |
if (id) { |
dev_dbg (dev, "%s - got id\n", __FUNCTION__); |
down (&driver->serialize); |
error = driver->probe (intf, id); |
up (&driver->serialize); |
} |
if (!error) |
intf->driver = driver; |
return error; |
} |
int usb_unbind_interface(struct device *dev) |
{ |
struct usb_interface *intf = to_usb_interface(dev); |
struct usb_driver *driver = to_usb_driver(dev->driver); |
down(&driver->serialize); |
/* release all urbs for this interface */ |
usb_disable_interface(interface_to_usbdev(intf), intf); |
if (intf->driver && intf->driver->disconnect) |
intf->driver->disconnect(intf); |
/* force a release and re-initialize the interface */ |
usb_driver_release_interface(driver, intf); |
up(&driver->serialize); |
return 0; |
} |
/** |
* usb_register - register a USB driver |
* @new_driver: USB operations for the driver |
* |
* Registers a USB driver with the USB core. The list of unattached |
* interfaces will be rescanned whenever a new driver is added, allowing |
* the new driver to attach to any recognized devices. |
* Returns a negative error code on failure and 0 on success. |
* |
* NOTE: if you want your driver to use the USB major number, you must call |
* usb_register_dev() to enable that functionality. This function no longer |
* takes care of that. |
*/ |
int usb_register(struct usb_driver *new_driver) |
{ |
int retval = 0; |
if (nousb) |
return -ENODEV; |
new_driver->driver.name = (char *)new_driver->name; |
new_driver->driver.bus = &usb_bus_type; |
new_driver->driver.probe = usb_probe_interface; |
new_driver->driver.remove = usb_unbind_interface; |
init_MUTEX(&new_driver->serialize); |
retval = driver_register(&new_driver->driver); |
if (!retval) { |
info("registered new driver %s", new_driver->name); |
usbfs_update_special(); |
} else { |
err("problem %d when registering driver %s", |
retval, new_driver->name); |
} |
return retval; |
} |
/** |
* usb_deregister - unregister a USB driver |
* @driver: USB operations of the driver to unregister |
* Context: !in_interrupt (), must be called with BKL held |
* |
* Unlinks the specified driver from the internal USB driver list. |
* |
* NOTE: If you called usb_register_dev(), you still need to call |
* usb_deregister_dev() to clean up your driver's allocated minor numbers, |
* this * call will no longer do it for you. |
*/ |
void usb_deregister(struct usb_driver *driver) |
{ |
info("deregistering driver %s", driver->name); |
driver_unregister (&driver->driver); |
usbfs_update_special(); |
} |
/** |
* usb_ifnum_to_if - get the interface object with a given interface number (usbcore-internal) |
* @dev: the device whose current configuration is considered |
* @ifnum: the desired interface |
* |
* This walks the device descriptor for the currently active configuration |
* and returns a pointer to the interface with that particular interface |
* number, or null. |
* |
* Note that configuration descriptors are not required to assign interface |
* numbers sequentially, so that it would be incorrect to assume that |
* the first interface in that descriptor corresponds to interface zero. |
* This routine helps device drivers avoid such mistakes. |
* However, you should make sure that you do the right thing with any |
* alternate settings available for this interfaces. |
*/ |
struct usb_interface *usb_ifnum_to_if(struct usb_device *dev, unsigned ifnum) |
{ |
int i; |
for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) |
if (dev->actconfig->interface[i]->altsetting[0] |
.desc.bInterfaceNumber == ifnum) |
return dev->actconfig->interface[i]; |
return NULL; |
} |
/** |
* usb_epnum_to_ep_desc - get the endpoint object with a given endpoint number |
* @dev: the device whose current configuration+altsettings is considered |
* @epnum: the desired endpoint, masked with USB_DIR_IN as appropriate. |
* |
* This walks the device descriptor for the currently active configuration, |
* and returns a pointer to the endpoint with that particular endpoint |
* number, or null. |
* |
* Note that interface descriptors are not required to list endpoint |
* numbers in any standardized order, so that it would be wrong to |
* assume that ep2in precedes either ep5in, ep2out, or even ep1out. |
* This routine helps device drivers avoid such mistakes. |
*/ |
struct usb_endpoint_descriptor * |
usb_epnum_to_ep_desc(struct usb_device *dev, unsigned epnum) |
{ |
int i, k; |
for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { |
struct usb_interface *intf; |
struct usb_host_interface *alt; |
/* only endpoints in current altseting are active */ |
intf = dev->actconfig->interface[i]; |
alt = intf->altsetting + intf->act_altsetting; |
for (k = 0; k < alt->desc.bNumEndpoints; k++) |
if (epnum == alt->endpoint[k].desc.bEndpointAddress) |
return &alt->endpoint[k].desc; |
} |
return NULL; |
} |
/** |
* usb_driver_claim_interface - bind a driver to an interface |
* @driver: the driver to be bound |
* @iface: the interface to which it will be bound |
* @priv: driver data associated with that interface |
* |
* This is used by usb device drivers that need to claim more than one |
* interface on a device when probing (audio and acm are current examples). |
* No device driver should directly modify internal usb_interface or |
* usb_device structure members. |
* |
* Few drivers should need to use this routine, since the most natural |
* way to bind to an interface is to return the private data from |
* the driver's probe() method. |
*/ |
int usb_driver_claim_interface(struct usb_driver *driver, struct usb_interface *iface, void* priv) |
{ |
if (!iface || !driver) |
return -EINVAL; |
/* this is mainly to lock against usbfs */ |
lock_kernel(); |
if (iface->driver) { |
unlock_kernel(); |
err ("%s driver booted %s off interface %p", |
driver->name, iface->driver->name, iface); |
return -EBUSY; |
} else { |
dbg("%s driver claimed interface %p", driver->name, iface); |
} |
iface->driver = driver; |
usb_set_intfdata(iface, priv); |
unlock_kernel(); |
return 0; |
} |
/** |
* usb_interface_claimed - returns true iff an interface is claimed |
* @iface: the interface being checked |
* |
* This should be used by drivers to check other interfaces to see if |
* they are available or not. If another driver has claimed the interface, |
* they may not claim it. Otherwise it's OK to claim it using |
* usb_driver_claim_interface(). |
* |
* Returns true (nonzero) iff the interface is claimed, else false (zero). |
*/ |
int usb_interface_claimed(struct usb_interface *iface) |
{ |
if (!iface) |
return 0; |
return (iface->driver != NULL); |
} /* usb_interface_claimed() */ |
/** |
* usb_driver_release_interface - unbind a driver from an interface |
* @driver: the driver to be unbound |
* @iface: the interface from which it will be unbound |
* |
* In addition to unbinding the driver, this re-initializes the interface |
* by selecting altsetting 0, the default alternate setting. |
* |
* This can be used by drivers to release an interface without waiting |
* for their disconnect() methods to be called. |
* |
* When the USB subsystem disconnect()s a driver from some interface, |
* it automatically invokes this method for that interface. That |
* means that even drivers that used usb_driver_claim_interface() |
* usually won't need to call this. |
* |
* This call is synchronous, and may not be used in an interrupt context. |
*/ |
void usb_driver_release_interface(struct usb_driver *driver, struct usb_interface *iface) |
{ |
/* this should never happen, don't release something that's not ours */ |
if (iface->driver && iface->driver != driver) |
return; |
usb_set_interface(interface_to_usbdev(iface), |
iface->altsetting[0].desc.bInterfaceNumber, |
0); |
usb_set_intfdata(iface, NULL); |
iface->driver = NULL; |
} |
/** |
* usb_match_id - find first usb_device_id matching device or interface |
* @interface: the interface of interest |
* @id: array of usb_device_id structures, terminated by zero entry |
* |
* usb_match_id searches an array of usb_device_id's and returns |
* the first one matching the device or interface, or null. |
* This is used when binding (or rebinding) a driver to an interface. |
* Most USB device drivers will use this indirectly, through the usb core, |
* but some layered driver frameworks use it directly. |
* These device tables are exported with MODULE_DEVICE_TABLE, through |
* modutils and "modules.usbmap", to support the driver loading |
* functionality of USB hotplugging. |
* |
* What Matches: |
* |
* The "match_flags" element in a usb_device_id controls which |
* members are used. If the corresponding bit is set, the |
* value in the device_id must match its corresponding member |
* in the device or interface descriptor, or else the device_id |
* does not match. |
* |
* "driver_info" is normally used only by device drivers, |
* but you can create a wildcard "matches anything" usb_device_id |
* as a driver's "modules.usbmap" entry if you provide an id with |
* only a nonzero "driver_info" field. If you do this, the USB device |
* driver's probe() routine should use additional intelligence to |
* decide whether to bind to the specified interface. |
* |
* What Makes Good usb_device_id Tables: |
* |
* The match algorithm is very simple, so that intelligence in |
* driver selection must come from smart driver id records. |
* Unless you have good reasons to use another selection policy, |
* provide match elements only in related groups, and order match |
* specifiers from specific to general. Use the macros provided |
* for that purpose if you can. |
* |
* The most specific match specifiers use device descriptor |
* data. These are commonly used with product-specific matches; |
* the USB_DEVICE macro lets you provide vendor and product IDs, |
* and you can also match against ranges of product revisions. |
* These are widely used for devices with application or vendor |
* specific bDeviceClass values. |
* |
* Matches based on device class/subclass/protocol specifications |
* are slightly more general; use the USB_DEVICE_INFO macro, or |
* its siblings. These are used with single-function devices |
* where bDeviceClass doesn't specify that each interface has |
* its own class. |
* |
* Matches based on interface class/subclass/protocol are the |
* most general; they let drivers bind to any interface on a |
* multiple-function device. Use the USB_INTERFACE_INFO |
* macro, or its siblings, to match class-per-interface style |
* devices (as recorded in bDeviceClass). |
* |
* Within those groups, remember that not all combinations are |
* meaningful. For example, don't give a product version range |
* without vendor and product IDs; or specify a protocol without |
* its associated class and subclass. |
*/ |
const struct usb_device_id * |
usb_match_id(struct usb_interface *interface, const struct usb_device_id *id) |
{ |
struct usb_host_interface *intf; |
struct usb_device *dev; |
/* proc_connectinfo in devio.c may call us with id == NULL. */ |
if (id == NULL) |
return NULL; |
intf = &interface->altsetting [interface->act_altsetting]; |
dev = interface_to_usbdev(interface); |
/* It is important to check that id->driver_info is nonzero, |
since an entry that is all zeroes except for a nonzero |
id->driver_info is the way to create an entry that |
indicates that the driver want to examine every |
device and interface. */ |
for (; id->idVendor || id->bDeviceClass || id->bInterfaceClass || |
id->driver_info; id++) { |
if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && |
id->idVendor != dev->descriptor.idVendor) |
continue; |
if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && |
id->idProduct != dev->descriptor.idProduct) |
continue; |
/* No need to test id->bcdDevice_lo != 0, since 0 is never |
greater than any unsigned number. */ |
if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) && |
(id->bcdDevice_lo > dev->descriptor.bcdDevice)) |
continue; |
if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) && |
(id->bcdDevice_hi < dev->descriptor.bcdDevice)) |
continue; |
if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) && |
(id->bDeviceClass != dev->descriptor.bDeviceClass)) |
continue; |
if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) && |
(id->bDeviceSubClass!= dev->descriptor.bDeviceSubClass)) |
continue; |
if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) && |
(id->bDeviceProtocol != dev->descriptor.bDeviceProtocol)) |
continue; |
if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) && |
(id->bInterfaceClass != intf->desc.bInterfaceClass)) |
continue; |
if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_SUBCLASS) && |
(id->bInterfaceSubClass != intf->desc.bInterfaceSubClass)) |
continue; |
if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_PROTOCOL) && |
(id->bInterfaceProtocol != intf->desc.bInterfaceProtocol)) |
continue; |
return id; |
} |
return NULL; |
} |
/** |
* usb_find_interface - find usb_interface pointer for driver and device |
* @drv: the driver whose current configuration is considered |
* @minor: the minor number of the desired device |
* |
* This walks the driver device list and returns a pointer to the interface |
* with the matching minor. Note, this only works for devices that share the |
* USB major number. |
*/ |
struct usb_interface *usb_find_interface(struct usb_driver *drv, int minor) |
{ |
struct list_head *entry; |
struct device *dev; |
struct usb_interface *intf; |
list_for_each(entry, &drv->driver.devices) { |
dev = container_of(entry, struct device, driver_list); |
/* can't look at usb devices, only interfaces */ |
if (dev->driver == &usb_generic_driver) |
continue; |
intf = to_usb_interface(dev); |
if (intf->minor == -1) |
continue; |
if (intf->minor == minor) |
return intf; |
} |
/* no device found that matches */ |
return NULL; |
} |
static int usb_device_match (struct device *dev, struct device_driver *drv) |
{ |
struct usb_interface *intf; |
struct usb_driver *usb_drv; |
const struct usb_device_id *id; |
/* check for generic driver, which we don't match any device with */ |
if (drv == &usb_generic_driver) |
return 0; |
intf = to_usb_interface(dev); |
usb_drv = to_usb_driver(drv); |
id = usb_drv->id_table; |
id = usb_match_id (intf, usb_drv->id_table); |
if (id) |
return 1; |
return 0; |
} |
#ifdef CONFIG_HOTPLUG |
/* |
* USB hotplugging invokes what /proc/sys/kernel/hotplug says |
* (normally /sbin/hotplug) when USB devices get added or removed. |
* |
* This invokes a user mode policy agent, typically helping to load driver |
* or other modules, configure the device, and more. Drivers can provide |
* a MODULE_DEVICE_TABLE to help with module loading subtasks. |
* |
* We're called either from khubd (the typical case) or from root hub |
* (init, kapmd, modprobe, rmmod, etc), but the agents need to handle |
* delays in event delivery. Use sysfs (and DEVPATH) to make sure the |
* device (and this configuration!) are still present. |
*/ |
static int usb_hotplug (struct device *dev, char **envp, int num_envp, |
char *buffer, int buffer_size) |
{ |
struct usb_interface *intf; |
struct usb_device *usb_dev; |
char *scratch; |
int i = 0; |
int length = 0; |
dbg ("%s", __FUNCTION__); |
if (!dev) |
return -ENODEV; |
/* Must check driver_data here, as on remove driver is always NULL */ |
if ((dev->driver == &usb_generic_driver) || |
(dev->driver_data == &usb_generic_driver_data)) |
return 0; |
intf = to_usb_interface(dev); |
usb_dev = interface_to_usbdev (intf); |
if (usb_dev->devnum < 0) { |
dbg ("device already deleted ??"); |
return -ENODEV; |
} |
if (!usb_dev->bus) { |
dbg ("bus already removed?"); |
return -ENODEV; |
} |
scratch = buffer; |
#ifdef CONFIG_USB_DEVICEFS |
/* If this is available, userspace programs can directly read |
* all the device descriptors we don't tell them about. Or |
* even act as usermode drivers. |
* |
* FIXME reduce hardwired intelligence here |
*/ |
envp [i++] = scratch; |
length += snprintf (scratch, buffer_size - length, |
"DEVICE=/proc/bus/usb/%03d/%03d", |
usb_dev->bus->busnum, usb_dev->devnum); |
if ((buffer_size - length <= 0) || (i >= num_envp)) |
return -ENOMEM; |
++length; |
scratch += length; |
#endif |
/* per-device configurations are common */ |
envp [i++] = scratch; |
length += snprintf (scratch, buffer_size - length, "PRODUCT=%x/%x/%x", |
usb_dev->descriptor.idVendor, |
usb_dev->descriptor.idProduct, |
usb_dev->descriptor.bcdDevice); |
if ((buffer_size - length <= 0) || (i >= num_envp)) |
return -ENOMEM; |
++length; |
scratch += length; |
/* class-based driver binding models */ |
envp [i++] = scratch; |
length += snprintf (scratch, buffer_size - length, "TYPE=%d/%d/%d", |
usb_dev->descriptor.bDeviceClass, |
usb_dev->descriptor.bDeviceSubClass, |
usb_dev->descriptor.bDeviceProtocol); |
if ((buffer_size - length <= 0) || (i >= num_envp)) |
return -ENOMEM; |
++length; |
scratch += length; |
if (usb_dev->descriptor.bDeviceClass == 0) { |
int alt = intf->act_altsetting; |
/* 2.4 only exposed interface zero. in 2.5, hotplug |
* agents are called for all interfaces, and can use |
* $DEVPATH/bInterfaceNumber if necessary. |
*/ |
envp [i++] = scratch; |
length += snprintf (scratch, buffer_size - length, |
"INTERFACE=%d/%d/%d", |
intf->altsetting[alt].desc.bInterfaceClass, |
intf->altsetting[alt].desc.bInterfaceSubClass, |
intf->altsetting[alt].desc.bInterfaceProtocol); |
if ((buffer_size - length <= 0) || (i >= num_envp)) |
return -ENOMEM; |
++length; |
scratch += length; |
} |
envp [i++] = 0; |
return 0; |
} |
#else |
static int usb_hotplug (struct device *dev, char **envp, |
int num_envp, char *buffer, int buffer_size) |
{ |
return -ENODEV; |
} |
#endif /* CONFIG_HOTPLUG */ |
/** |
* usb_release_dev - free a usb device structure when all users of it are finished. |
* @dev: device that's been disconnected |
* |
* Will be called only by the device core when all users of this usb device are |
* done. |
*/ |
static void usb_release_dev(struct device *dev) |
{ |
struct usb_device *udev; |
udev = to_usb_device(dev); |
if (udev->bus && udev->bus->op && udev->bus->op->deallocate) |
udev->bus->op->deallocate(udev); |
usb_destroy_configuration(udev); |
usb_bus_put(udev->bus); |
kfree (udev); |
} |
/** |
* usb_alloc_dev - allocate a usb device structure (usbcore-internal) |
* @parent: hub to which device is connected |
* @bus: bus used to access the device |
* Context: !in_interrupt () |
* |
* Only hub drivers (including virtual root hub drivers for host |
* controllers) should ever call this. |
* |
* This call is synchronous, and may not be used in an interrupt context. |
*/ |
struct usb_device *usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus) |
{ |
struct usb_device *dev; |
dev = kmalloc(sizeof(*dev), GFP_KERNEL); |
if (!dev) |
return NULL; |
memset(dev, 0, sizeof(*dev)); |
bus = usb_bus_get(bus); |
if (!bus) { |
kfree(dev); |
return NULL; |
} |
device_initialize(&dev->dev); |
dev->dev.release = usb_release_dev; |
dev->state = USB_STATE_ATTACHED; |
if (!parent) |
dev->devpath [0] = '0'; |
dev->bus = bus; |
dev->parent = parent; |
INIT_LIST_HEAD(&dev->filelist); |
init_MUTEX(&dev->serialize); |
if (dev->bus->op->allocate) |
dev->bus->op->allocate(dev); |
return dev; |
} |
/** |
* usb_get_dev - increments the reference count of the usb device structure |
* @dev: the device being referenced |
* |
* Each live reference to a device should be refcounted. |
* |
* Drivers for USB interfaces should normally record such references in |
* their probe() methods, when they bind to an interface, and release |
* them by calling usb_put_dev(), in their disconnect() methods. |
* |
* A pointer to the device with the incremented reference counter is returned. |
*/ |
struct usb_device *usb_get_dev (struct usb_device *dev) |
{ |
struct device *tmp; |
if (!dev) |
return NULL; |
tmp = get_device(&dev->dev); |
if (tmp) |
return to_usb_device(tmp); |
else |
return NULL; |
} |
/** |
* usb_put_dev - release a use of the usb device structure |
* @dev: device that's been disconnected |
* |
* Must be called when a user of a device is finished with it. When the last |
* user of the device calls this function, the memory of the device is freed. |
*/ |
void usb_put_dev(struct usb_device *dev) |
{ |
if (dev) |
put_device(&dev->dev); |
} |
static struct usb_device *match_device(struct usb_device *dev, |
u16 vendor_id, u16 product_id) |
{ |
struct usb_device *ret_dev = NULL; |
int child; |
dbg("looking at vendor %d, product %d", |
dev->descriptor.idVendor, |
dev->descriptor.idProduct); |
/* see if this device matches */ |
if ((dev->descriptor.idVendor == vendor_id) && |
(dev->descriptor.idProduct == product_id)) { |
dbg ("found the device!"); |
ret_dev = usb_get_dev(dev); |
goto exit; |
} |
/* look through all of the children of this device */ |
for (child = 0; child < dev->maxchild; ++child) { |
if (dev->children[child]) { |
ret_dev = match_device(dev->children[child], |
vendor_id, product_id); |
if (ret_dev) |
goto exit; |
} |
} |
exit: |
return ret_dev; |
} |
/** |
* usb_find_device - find a specific usb device in the system |
* @vendor_id: the vendor id of the device to find |
* @product_id: the product id of the device to find |
* |
* Returns a pointer to a struct usb_device if such a specified usb |
* device is present in the system currently. The usage count of the |
* device will be incremented if a device is found. Make sure to call |
* usb_put_dev() when the caller is finished with the device. |
* |
* If a device with the specified vendor and product id is not found, |
* NULL is returned. |
*/ |
struct usb_device *usb_find_device(u16 vendor_id, u16 product_id) |
{ |
struct list_head *buslist; |
struct usb_bus *bus; |
struct usb_device *dev = NULL; |
down(&usb_bus_list_lock); |
for (buslist = usb_bus_list.next; |
buslist != &usb_bus_list; |
buslist = buslist->next) { |
bus = container_of(buslist, struct usb_bus, bus_list); |
dev = match_device(bus->root_hub, vendor_id, product_id); |
if (dev) |
goto exit; |
} |
exit: |
up(&usb_bus_list_lock); |
return dev; |
} |
/** |
* usb_get_current_frame_number - return current bus frame number |
* @dev: the device whose bus is being queried |
* |
* Returns the current frame number for the USB host controller |
* used with the given USB device. This can be used when scheduling |
* isochronous requests. |
* |
* Note that different kinds of host controller have different |
* "scheduling horizons". While one type might support scheduling only |
* 32 frames into the future, others could support scheduling up to |
* 1024 frames into the future. |
*/ |
int usb_get_current_frame_number(struct usb_device *dev) |
{ |
return dev->bus->op->get_frame_number (dev); |
} |
/*-------------------------------------------------------------------*/ |
/* |
* __usb_get_extra_descriptor() finds a descriptor of specific type in the |
* extra field of the interface and endpoint descriptor structs. |
*/ |
int __usb_get_extra_descriptor(char *buffer, unsigned size, unsigned char type, void **ptr) |
{ |
struct usb_descriptor_header *header; |
while (size >= sizeof(struct usb_descriptor_header)) { |
header = (struct usb_descriptor_header *)buffer; |
if (header->bLength < 2) { |
err("invalid descriptor length of %d", header->bLength); |
return -1; |
} |
if (header->bDescriptorType == type) { |
*ptr = header; |
return 0; |
} |
buffer += header->bLength; |
size -= header->bLength; |
} |
return -1; |
} |
/** |
* usb_disconnect - disconnect a device (usbcore-internal) |
* @pdev: pointer to device being disconnected |
* Context: !in_interrupt () |
* |
* Something got disconnected. Get rid of it, and all of its children. |
* |
* Only hub drivers (including virtual root hub drivers for host |
* controllers) should ever call this. |
* |
* This call is synchronous, and may not be used in an interrupt context. |
*/ |
void usb_disconnect(struct usb_device **pdev) |
{ |
struct usb_device *dev = *pdev; |
struct usb_bus *bus; |
struct usb_operations *ops; |
int i; |
might_sleep (); |
if (!dev) { |
pr_debug ("%s nodev\n", __FUNCTION__); |
return; |
} |
bus = dev->bus; |
if (!bus) { |
pr_debug ("%s nobus\n", __FUNCTION__); |
return; |
} |
ops = bus->op; |
*pdev = NULL; |
/* mark the device as inactive, so any further urb submissions for |
* this device will fail. |
*/ |
dev->state = USB_STATE_NOTATTACHED; |
down(&dev->serialize); |
dev_info (&dev->dev, "USB disconnect, address %d\n", dev->devnum); |
/* Free up all the children before we remove this device */ |
for (i = 0; i < USB_MAXCHILDREN; i++) { |
struct usb_device **child = dev->children + i; |
if (*child) |
usb_disconnect(child); |
} |
/* deallocate hcd/hardware state ... nuking all pending urbs and |
* cleaning up all state associated with the current configuration |
*/ |
usb_disable_device(dev, 0); |
dev_dbg (&dev->dev, "unregistering device\n"); |
/* Free the device number and remove the /proc/bus/usb entry */ |
if (dev->devnum > 0) { |
clear_bit(dev->devnum, dev->bus->devmap.devicemap); |
usbfs_remove_device(dev); |
} |
up(&dev->serialize); |
device_unregister(&dev->dev); |
} |
/** |
* usb_choose_address - pick device address (usbcore-internal) |
* @dev: newly detected device (in DEFAULT state) |
* |
* Picks a device address. It's up to the hub (or root hub) driver |
* to handle and manage enumeration, starting from the DEFAULT state. |
* Only hub drivers (but not virtual root hub drivers for host |
* controllers) should ever call this. |
*/ |
void usb_choose_address(struct usb_device *dev) |
{ |
int devnum; |
// FIXME needs locking for SMP!! |
/* why? this is called only from the hub thread, |
* which hopefully doesn't run on multiple CPU's simultaneously 8-) |
*/ |
/* Try to allocate the next devnum beginning at bus->devnum_next. */ |
devnum = find_next_zero_bit(dev->bus->devmap.devicemap, 128, dev->bus->devnum_next); |
if (devnum >= 128) |
devnum = find_next_zero_bit(dev->bus->devmap.devicemap, 128, 1); |
dev->bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1); |
if (devnum < 128) { |
set_bit(devnum, dev->bus->devmap.devicemap); |
dev->devnum = devnum; |
} |
} |
// hub-only!! ... and only exported for reset/reinit path. |
// otherwise used internally, for usb_new_device() |
int usb_set_address(struct usb_device *dev) |
{ |
int retval; |
if (dev->devnum == 0) |
return -EINVAL; |
if (dev->state != USB_STATE_DEFAULT && dev->state != USB_STATE_ADDRESS) |
return -EINVAL; |
retval = usb_control_msg(dev, usb_snddefctrl(dev), USB_REQ_SET_ADDRESS, |
0, dev->devnum, 0, NULL, 0, HZ * USB_CTRL_SET_TIMEOUT); |
if (retval == 0) |
dev->state = USB_STATE_ADDRESS; |
return retval; |
} |
/* |
* By the time we get here, we chose a new device address |
* and is in the default state. We need to identify the thing and |
* get the ball rolling.. |
* |
* Returns 0 for success, != 0 for error. |
* |
* This call is synchronous, and may not be used in an interrupt context. |
* |
* Only the hub driver should ever call this; root hub registration |
* uses it only indirectly. |
*/ |
#define NEW_DEVICE_RETRYS 2 |
#define SET_ADDRESS_RETRYS 2 |
int usb_new_device(struct usb_device *dev, struct device *parent) |
{ |
int err = -EINVAL; |
int i; |
int j; |
/* |
* Set the driver for the usb device to point to the "generic" driver. |
* This prevents the main usb device from being sent to the usb bus |
* probe function. Yes, it's a hack, but a nice one :) |
* |
* Do it asap, so more driver model stuff (like the device.h message |
* utilities) can be used in hcd submit/unlink code paths. |
*/ |
usb_generic_driver.bus = &usb_bus_type; |
dev->dev.parent = parent; |
dev->dev.driver = &usb_generic_driver; |
dev->dev.bus = &usb_bus_type; |
dev->dev.driver_data = &usb_generic_driver_data; |
if (dev->dev.bus_id[0] == 0) |
sprintf26 (&dev->dev.bus_id[0], "%d-%s", |
dev->bus->busnum, dev->devpath); |
/* dma masks come from the controller; readonly, except to hcd */ |
dev->dev.dma_mask = parent->dma_mask; |
/* USB 2.0 section 5.5.3 talks about ep0 maxpacket ... |
* it's fixed size except for full speed devices. |
*/ |
switch (dev->speed) { |
case USB_SPEED_HIGH: /* fixed at 64 */ |
i = 64; |
break; |
case USB_SPEED_FULL: /* 8, 16, 32, or 64 */ |
/* to determine the ep0 maxpacket size, read the first 8 |
* bytes from the device descriptor to get bMaxPacketSize0; |
* then correct our initial (small) guess. |
*/ |
// FALLTHROUGH |
case USB_SPEED_LOW: /* fixed at 8 */ |
i = 8; |
break; |
default: |
goto fail; |
} |
dev->epmaxpacketin [0] = i; |
dev->epmaxpacketout[0] = i; |
for (i = 0; i < NEW_DEVICE_RETRYS; ++i) { |
for (j = 0; j < SET_ADDRESS_RETRYS; ++j) { |
err = usb_set_address(dev); |
if (err >= 0) |
break; |
wait_ms(200); |
} |
if (err < 0) { |
dev_err(&dev->dev, |
"device not accepting address %d, error %d\n", |
dev->devnum, err); |
goto fail; |
} |
wait_ms(10); /* Let the SET_ADDRESS settle */ |
/* high and low speed devices don't need this... */ |
err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, &dev->descriptor, 8); |
if (err >= 8) |
break; |
wait_ms(100); |
} |
if (err < 8) { |
dev_err(&dev->dev, "device descriptor read/8, error %d\n", err); |
goto fail; |
} |
if (dev->speed == USB_SPEED_FULL) { |
usb_disable_endpoint(dev, 0); |
usb_endpoint_running(dev, 0, 1); |
usb_endpoint_running(dev, 0, 0); |
dev->epmaxpacketin [0] = dev->descriptor.bMaxPacketSize0; |
dev->epmaxpacketout[0] = dev->descriptor.bMaxPacketSize0; |
} |
/* USB device state == addressed ... still not usable */ |
err = usb_get_device_descriptor(dev); |
if (err < (signed)sizeof(dev->descriptor)) { |
dev_err(&dev->dev, "device descriptor read/all, error %d\n", err); |
goto fail; |
} |
err = usb_get_configuration(dev); |
if (err < 0) { |
dev_err(&dev->dev, "can't read configurations, error %d\n", |
err); |
goto fail; |
} |
/* Tell the world! */ |
dev_dbg(&dev->dev, "new device strings: Mfr=%d, Product=%d, SerialNumber=%d\n", |
dev->descriptor.iManufacturer, dev->descriptor.iProduct, dev->descriptor.iSerialNumber); |
#ifdef DEBUG |
if (dev->descriptor.iProduct) |
usb_show_string(dev, "Product", dev->descriptor.iProduct); |
if (dev->descriptor.iManufacturer) |
usb_show_string(dev, "Manufacturer", dev->descriptor.iManufacturer); |
if (dev->descriptor.iSerialNumber) |
usb_show_string(dev, "SerialNumber", dev->descriptor.iSerialNumber); |
#endif |
/* put device-specific files into sysfs */ |
err = device_add (&dev->dev); |
if (err) { |
dev_err(&dev->dev, "can't device_add, error %d\n", err); |
goto fail; |
} |
usb_create_driverfs_dev_files (dev); |
/* choose and set the configuration. that registers the interfaces |
* with the driver core, and lets usb device drivers bind to them. |
*/ |
if (dev->descriptor.bNumConfigurations != 1) { |
dev_info(&dev->dev, |
"configuration #%d chosen from %d choices\n", |
dev->config[0].desc.bConfigurationValue, |
dev->descriptor.bNumConfigurations); |
} |
err = usb_set_configuration(dev, |
dev->config[0].desc.bConfigurationValue); |
if (err) { |
dev_err(&dev->dev, "can't set config #%d, error %d\n", |
dev->config[0].desc.bConfigurationValue, err); |
device_del(&dev->dev); |
goto fail; |
} |
/* USB device state == configured ... usable */ |
/* add a /proc/bus/usb entry */ |
usbfs_add_device(dev); |
return 0; |
fail: |
dev->state = USB_STATE_DEFAULT; |
clear_bit(dev->devnum, dev->bus->devmap.devicemap); |
dev->devnum = -1; |
return err; |
} |
/** |
* usb_buffer_alloc - allocate dma-consistent buffer for URB_NO_xxx_DMA_MAP |
* @dev: device the buffer will be used with |
* @size: requested buffer size |
* @mem_flags: affect whether allocation may block |
* @dma: used to return DMA address of buffer |
* |
* Return value is either null (indicating no buffer could be allocated), or |
* the cpu-space pointer to a buffer that may be used to perform DMA to the |
* specified device. Such cpu-space buffers are returned along with the DMA |
* address (through the pointer provided). |
* |
* These buffers are used with URB_NO_xxx_DMA_MAP set in urb->transfer_flags |
* to avoid behaviors like using "DMA bounce buffers", or tying down I/O |
* mapping hardware for long idle periods. The implementation varies between |
* platforms, depending on details of how DMA will work to this device. |
* Using these buffers also helps prevent cacheline sharing problems on |
* architectures where CPU caches are not DMA-coherent. |
* |
* When the buffer is no longer used, free it with usb_buffer_free(). |
*/ |
void *usb_buffer_alloc ( |
struct usb_device *dev, |
size_t size, |
int mem_flags, |
dma_addr_t *dma |
) |
{ |
if (!dev || !dev->bus || !dev->bus->op || !dev->bus->op->buffer_alloc) |
return 0; |
return dev->bus->op->buffer_alloc (dev->bus, size, mem_flags, dma); |
} |
/** |
* usb_buffer_free - free memory allocated with usb_buffer_alloc() |
* @dev: device the buffer was used with |
* @size: requested buffer size |
* @addr: CPU address of buffer |
* @dma: DMA address of buffer |
* |
* This reclaims an I/O buffer, letting it be reused. The memory must have |
* been allocated using usb_buffer_alloc(), and the parameters must match |
* those provided in that allocation request. |
*/ |
void usb_buffer_free ( |
struct usb_device *dev, |
size_t size, |
void *addr, |
dma_addr_t dma |
) |
{ |
if (!dev || !dev->bus || !dev->bus->op || !dev->bus->op->buffer_free) |
return; |
dev->bus->op->buffer_free (dev->bus, size, addr, dma); |
} |
/** |
* usb_buffer_map - create DMA mapping(s) for an urb |
* @urb: urb whose transfer_buffer/setup_packet will be mapped |
* |
* Return value is either null (indicating no buffer could be mapped), or |
* the parameter. URB_NO_TRANSFER_DMA_MAP and URB_NO_SETUP_DMA_MAP are |
* added to urb->transfer_flags if the operation succeeds. If the device |
* is connected to this system through a non-DMA controller, this operation |
* always succeeds. |
* |
* This call would normally be used for an urb which is reused, perhaps |
* as the target of a large periodic transfer, with usb_buffer_dmasync() |
* calls to synchronize memory and dma state. |
* |
* Reverse the effect of this call with usb_buffer_unmap(). |
*/ |
struct urb *usb_buffer_map (struct urb *urb) |
{ |
struct usb_bus *bus; |
struct device *controller; |
if (!urb |
|| !urb->dev |
|| !(bus = urb->dev->bus) |
|| !(controller = bus->controller)) |
return 0; |
if (controller->dma_mask) { |
urb->transfer_dma = dma_map_single (controller, |
urb->transfer_buffer, urb->transfer_buffer_length, |
usb_pipein (urb->pipe) |
? DMA_FROM_DEVICE : DMA_TO_DEVICE); |
if (usb_pipecontrol (urb->pipe)) |
urb->setup_dma = dma_map_single (controller, |
urb->setup_packet, |
sizeof (struct usb_ctrlrequest), |
DMA_TO_DEVICE); |
// FIXME generic api broken like pci, can't report errors |
// if (urb->transfer_dma == DMA_ADDR_INVALID) return 0; |
} else |
urb->transfer_dma = ~0; |
urb->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP |
| URB_NO_SETUP_DMA_MAP); |
return urb; |
} |
/** |
* usb_buffer_dmasync - synchronize DMA and CPU view of buffer(s) |
* @urb: urb whose transfer_buffer/setup_packet will be synchronized |
*/ |
void usb_buffer_dmasync (struct urb *urb) |
{ |
struct usb_bus *bus; |
struct device *controller; |
if (!urb |
|| !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) |
|| !urb->dev |
|| !(bus = urb->dev->bus) |
|| !(controller = bus->controller)) |
return; |
if (controller->dma_mask) { |
dma_sync_single (controller, |
urb->transfer_dma, urb->transfer_buffer_length, |
usb_pipein (urb->pipe) |
? DMA_FROM_DEVICE : DMA_TO_DEVICE); |
if (usb_pipecontrol (urb->pipe)) |
dma_sync_single (controller, |
urb->setup_dma, |
sizeof (struct usb_ctrlrequest), |
DMA_TO_DEVICE); |
} |
} |
/** |
* usb_buffer_unmap - free DMA mapping(s) for an urb |
* @urb: urb whose transfer_buffer will be unmapped |
* |
* Reverses the effect of usb_buffer_map(). |
*/ |
void usb_buffer_unmap (struct urb *urb) |
{ |
struct usb_bus *bus; |
struct device *controller; |
if (!urb |
|| !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) |
|| !urb->dev |
|| !(bus = urb->dev->bus) |
|| !(controller = bus->controller)) |
return; |
if (controller->dma_mask) { |
dma_unmap_single (controller, |
urb->transfer_dma, urb->transfer_buffer_length, |
usb_pipein (urb->pipe) |
? DMA_FROM_DEVICE : DMA_TO_DEVICE); |
if (usb_pipecontrol (urb->pipe)) |
dma_unmap_single (controller, |
urb->setup_dma, |
sizeof (struct usb_ctrlrequest), |
DMA_TO_DEVICE); |
} |
urb->transfer_flags &= ~(URB_NO_TRANSFER_DMA_MAP |
| URB_NO_SETUP_DMA_MAP); |
} |
/** |
* usb_buffer_map_sg - create scatterlist DMA mapping(s) for an endpoint |
* @dev: device to which the scatterlist will be mapped |
* @pipe: endpoint defining the mapping direction |
* @sg: the scatterlist to map |
* @nents: the number of entries in the scatterlist |
* |
* Return value is either < 0 (indicating no buffers could be mapped), or |
* the number of DMA mapping array entries in the scatterlist. |
* |
* The caller is responsible for placing the resulting DMA addresses from |
* the scatterlist into URB transfer buffer pointers, and for setting the |
* URB_NO_TRANSFER_DMA_MAP transfer flag in each of those URBs. |
* |
* Top I/O rates come from queuing URBs, instead of waiting for each one |
* to complete before starting the next I/O. This is particularly easy |
* to do with scatterlists. Just allocate and submit one URB for each DMA |
* mapping entry returned, stopping on the first error or when all succeed. |
* Better yet, use the usb_sg_*() calls, which do that (and more) for you. |
* |
* This call would normally be used when translating scatterlist requests, |
* rather than usb_buffer_map(), since on some hardware (with IOMMUs) it |
* may be able to coalesce mappings for improved I/O efficiency. |
* |
* Reverse the effect of this call with usb_buffer_unmap_sg(). |
*/ |
int usb_buffer_map_sg (struct usb_device *dev, unsigned pipe, |
struct scatterlist *sg, int nents) |
{ |
struct usb_bus *bus; |
struct device *controller; |
if (!dev |
|| usb_pipecontrol (pipe) |
|| !(bus = dev->bus) |
|| !(controller = bus->controller) |
|| !controller->dma_mask) |
return -1; |
// FIXME generic api broken like pci, can't report errors |
return dma_map_sg (controller, sg, nents, |
usb_pipein (pipe) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); |
} |
/** |
* usb_buffer_dmasync_sg - synchronize DMA and CPU view of scatterlist buffer(s) |
* @dev: device to which the scatterlist will be mapped |
* @pipe: endpoint defining the mapping direction |
* @sg: the scatterlist to synchronize |
* @n_hw_ents: the positive return value from usb_buffer_map_sg |
* |
* Use this when you are re-using a scatterlist's data buffers for |
* another USB request. |
*/ |
void usb_buffer_dmasync_sg (struct usb_device *dev, unsigned pipe, |
struct scatterlist *sg, int n_hw_ents) |
{ |
struct usb_bus *bus; |
struct device *controller; |
if (!dev |
|| !(bus = dev->bus) |
|| !(controller = bus->controller) |
|| !controller->dma_mask) |
return; |
dma_sync_sg (controller, sg, n_hw_ents, |
usb_pipein (pipe) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); |
} |
/** |
* usb_buffer_unmap_sg - free DMA mapping(s) for a scatterlist |
* @dev: device to which the scatterlist will be mapped |
* @pipe: endpoint defining the mapping direction |
* @sg: the scatterlist to unmap |
* @n_hw_ents: the positive return value from usb_buffer_map_sg |
* |
* Reverses the effect of usb_buffer_map_sg(). |
*/ |
void usb_buffer_unmap_sg (struct usb_device *dev, unsigned pipe, |
struct scatterlist *sg, int n_hw_ents) |
{ |
struct usb_bus *bus; |
struct device *controller; |
if (!dev |
|| !(bus = dev->bus) |
|| !(controller = bus->controller) |
|| !controller->dma_mask) |
return; |
dma_unmap_sg (controller, sg, n_hw_ents, |
usb_pipein (pipe) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); |
} |
static int usb_device_suspend(struct device *dev, u32 state) |
{ |
struct usb_interface *intf; |
struct usb_driver *driver; |
if ((dev->driver == NULL) || |
(dev->driver == &usb_generic_driver) || |
(dev->driver_data == &usb_generic_driver_data)) |
return 0; |
intf = to_usb_interface(dev); |
driver = to_usb_driver(dev->driver); |
if (driver->suspend) |
return driver->suspend(intf, state); |
return 0; |
} |
static int usb_device_resume(struct device *dev) |
{ |
struct usb_interface *intf; |
struct usb_driver *driver; |
if ((dev->driver == NULL) || |
(dev->driver == &usb_generic_driver) || |
(dev->driver_data == &usb_generic_driver_data)) |
return 0; |
intf = to_usb_interface(dev); |
driver = to_usb_driver(dev->driver); |
if (driver->resume) |
return driver->resume(intf); |
return 0; |
} |
struct bus_type usb_bus_type = { |
.name = "usb", |
.match = usb_device_match, |
.hotplug = usb_hotplug, |
.suspend = usb_device_suspend, |
.resume = usb_device_resume, |
}; |
#ifndef MODULE |
static int __init usb_setup_disable(char *str) |
{ |
nousb = 1; |
return 1; |
} |
/* format to disable USB on kernel command line is: nousb */ |
__setup("nousb", usb_setup_disable); |
#endif |
/* |
* for external read access to <nousb> |
*/ |
int usb_disabled(void) |
{ |
return nousb; |
} |
/* |
* Init |
*/ |
/*static*/ int __init usb_init(void) |
{ |
if (nousb) { |
info("USB support disabled\n"); |
return 0; |
} |
bus_register(&usb_bus_type); |
usb_host_init(); |
usb_major_init(); |
usbfs_init(); |
usb_hub_init(); |
driver_register(&usb_generic_driver); |
return 0; |
} |
/* |
* Cleanup |
*/ |
/*static*/ void __exit usb_exit(void) |
{ |
/* This will matter if shutdown/reboot does exitcalls. */ |
if (nousb) |
return; |
driver_unregister(&usb_generic_driver); |
// usb_major_cleanup(); |
// usbfs_cleanup(); |
// usb_hub_cleanup(); |
// usb_host_cleanup(); |
// bus_unregister(&usb_bus_type); |
} |
subsys_initcall(usb_init); |
module_exit(usb_exit); |
/* |
* USB may be built into the kernel or be built as modules. |
* These symbols are exported for device (or host controller) |
* driver modules to use. |
*/ |
EXPORT_SYMBOL(usb_epnum_to_ep_desc); |
EXPORT_SYMBOL(usb_register); |
EXPORT_SYMBOL(usb_deregister); |
EXPORT_SYMBOL(usb_disabled); |
EXPORT_SYMBOL(usb_alloc_dev); |
EXPORT_SYMBOL(usb_put_dev); |
EXPORT_SYMBOL(usb_get_dev); |
EXPORT_SYMBOL(usb_hub_tt_clear_buffer); |
EXPORT_SYMBOL(usb_driver_claim_interface); |
EXPORT_SYMBOL(usb_interface_claimed); |
EXPORT_SYMBOL(usb_driver_release_interface); |
EXPORT_SYMBOL(usb_match_id); |
EXPORT_SYMBOL(usb_find_interface); |
EXPORT_SYMBOL(usb_ifnum_to_if); |
EXPORT_SYMBOL(usb_reset_device); |
EXPORT_SYMBOL(usb_disconnect); |
EXPORT_SYMBOL(__usb_get_extra_descriptor); |
EXPORT_SYMBOL(usb_find_device); |
EXPORT_SYMBOL(usb_get_current_frame_number); |
EXPORT_SYMBOL (usb_buffer_alloc); |
EXPORT_SYMBOL (usb_buffer_free); |
EXPORT_SYMBOL (usb_buffer_map); |
EXPORT_SYMBOL (usb_buffer_dmasync); |
EXPORT_SYMBOL (usb_buffer_unmap); |
EXPORT_SYMBOL (usb_buffer_map_sg); |
EXPORT_SYMBOL (usb_buffer_dmasync_sg); |
EXPORT_SYMBOL (usb_buffer_unmap_sg); |
MODULE_LICENSE("GPL"); |
/shark/trunk/drivers/usb/host/ohci-hub.c |
---|
0,0 → 1,271 |
/* |
* OHCI HCD (Host Controller Driver) for USB. |
* |
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> |
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> |
* |
* This file is licenced under GPL |
*/ |
/*-------------------------------------------------------------------------*/ |
/* |
* OHCI Root Hub ... the nonsharable stuff |
* |
* Registers don't need cpu_to_le32, that happens transparently |
*/ |
/* AMD-756 (D2 rev) reports corrupt register contents in some cases. |
* The erratum (#4) description is incorrect. AMD's workaround waits |
* till some bits (mostly reserved) are clear; ok for all revs. |
*/ |
#define read_roothub(hc, register, mask) ({ \ |
u32 temp = readl (&hc->regs->roothub.register); \ |
if (temp == -1) \ |
disable (hc); \ |
else if (hc->flags & OHCI_QUIRK_AMD756) \ |
while (temp & mask) \ |
temp = readl (&hc->regs->roothub.register); \ |
temp; }) |
static u32 roothub_a (struct ohci_hcd *hc) |
{ return read_roothub (hc, a, 0xfc0fe000); } |
static inline u32 roothub_b (struct ohci_hcd *hc) |
{ return readl (&hc->regs->roothub.b); } |
static inline u32 roothub_status (struct ohci_hcd *hc) |
{ return readl (&hc->regs->roothub.status); } |
static u32 roothub_portstatus (struct ohci_hcd *hc, int i) |
{ return read_roothub (hc, portstatus [i], 0xffe0fce0); } |
/*-------------------------------------------------------------------------*/ |
#define dbg_port(hc,label,num,value) \ |
ohci_dbg (hc, \ |
"%s roothub.portstatus [%d] " \ |
"= 0x%08x%s%s%s%s%s%s%s%s%s%s%s%s\n", \ |
label, num, temp, \ |
(temp & RH_PS_PRSC) ? " PRSC" : "", \ |
(temp & RH_PS_OCIC) ? " OCIC" : "", \ |
(temp & RH_PS_PSSC) ? " PSSC" : "", \ |
(temp & RH_PS_PESC) ? " PESC" : "", \ |
(temp & RH_PS_CSC) ? " CSC" : "", \ |
\ |
(temp & RH_PS_LSDA) ? " LSDA" : "", \ |
(temp & RH_PS_PPS) ? " PPS" : "", \ |
(temp & RH_PS_PRS) ? " PRS" : "", \ |
(temp & RH_PS_POCI) ? " POCI" : "", \ |
(temp & RH_PS_PSS) ? " PSS" : "", \ |
\ |
(temp & RH_PS_PES) ? " PES" : "", \ |
(temp & RH_PS_CCS) ? " CCS" : "" \ |
); |
/*-------------------------------------------------------------------------*/ |
/* build "status change" packet (one or two bytes) from HC registers */ |
static int |
ohci_hub_status_data (struct usb_hcd *hcd, char *buf) |
{ |
struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
int ports, i, changed = 0, length = 1; |
ports = roothub_a (ohci) & RH_A_NDP; |
if (ports > MAX_ROOT_PORTS) { |
if (!HCD_IS_RUNNING(ohci->hcd.state)) |
return -ESHUTDOWN; |
ohci_err (ohci, "bogus NDP=%d, rereads as NDP=%d\n", |
ports, readl (&ohci->regs->roothub.a) & RH_A_NDP); |
/* retry later; "should not happen" */ |
return 0; |
} |
/* init status */ |
if (roothub_status (ohci) & (RH_HS_LPSC | RH_HS_OCIC)) |
buf [0] = changed = 1; |
else |
buf [0] = 0; |
if (ports > 7) { |
buf [1] = 0; |
length++; |
} |
/* look at each port */ |
for (i = 0; i < ports; i++) { |
u32 status = roothub_portstatus (ohci, i); |
status &= RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC |
| RH_PS_OCIC | RH_PS_PRSC; |
if (status) { |
changed = 1; |
if (i < 7) |
buf [0] |= 1 << (i + 1); |
else |
buf [1] |= 1 << (i - 7); |
} |
} |
return changed ? length : 0; |
} |
/*-------------------------------------------------------------------------*/ |
static void |
ohci_hub_descriptor ( |
struct ohci_hcd *ohci, |
struct usb_hub_descriptor *desc |
) { |
u32 rh = roothub_a (ohci); |
int ports = rh & RH_A_NDP; |
u16 temp; |
desc->bDescriptorType = 0x29; |
desc->bPwrOn2PwrGood = (rh & RH_A_POTPGT) >> 24; |
desc->bHubContrCurrent = 0; |
desc->bNbrPorts = ports; |
temp = 1 + (ports / 8); |
desc->bDescLength = 7 + 2 * temp; |
temp = 0; |
if (rh & RH_A_PSM) /* per-port power switching? */ |
temp |= 0x0001; |
if (rh & RH_A_NOCP) /* no overcurrent reporting? */ |
temp |= 0x0010; |
else if (rh & RH_A_OCPM) /* per-port overcurrent reporting? */ |
temp |= 0x0008; |
desc->wHubCharacteristics = cpu_to_le16 (temp); |
/* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ |
rh = roothub_b (ohci); |
desc->bitmap [0] = rh & RH_B_DR; |
if (ports > 7) { |
desc->bitmap [1] = (rh & RH_B_DR) >> 8; |
desc->bitmap [2] = desc->bitmap [3] = 0xff; |
} else |
desc->bitmap [1] = 0xff; |
} |
/*-------------------------------------------------------------------------*/ |
static int ohci_hub_control ( |
struct usb_hcd *hcd, |
u16 typeReq, |
u16 wValue, |
u16 wIndex, |
char *buf, |
u16 wLength |
) { |
struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
int ports = hcd_to_bus (hcd)->root_hub->maxchild; |
u32 temp; |
int retval = 0; |
switch (typeReq) { |
case ClearHubFeature: |
switch (wValue) { |
case C_HUB_OVER_CURRENT: |
writel (RH_HS_OCIC, &ohci->regs->roothub.status); |
case C_HUB_LOCAL_POWER: |
break; |
default: |
goto error; |
} |
break; |
case ClearPortFeature: |
if (!wIndex || wIndex > ports) |
goto error; |
wIndex--; |
switch (wValue) { |
case USB_PORT_FEAT_ENABLE: |
temp = RH_PS_CCS; |
break; |
case USB_PORT_FEAT_C_ENABLE: |
temp = RH_PS_PESC; |
break; |
case USB_PORT_FEAT_SUSPEND: |
temp = RH_PS_POCI; |
break; |
case USB_PORT_FEAT_C_SUSPEND: |
temp = RH_PS_PSSC; |
break; |
case USB_PORT_FEAT_POWER: |
temp = RH_PS_LSDA; |
break; |
case USB_PORT_FEAT_C_CONNECTION: |
temp = RH_PS_CSC; |
break; |
case USB_PORT_FEAT_C_OVER_CURRENT: |
temp = RH_PS_OCIC; |
break; |
case USB_PORT_FEAT_C_RESET: |
temp = RH_PS_PRSC; |
break; |
default: |
goto error; |
} |
writel (temp, &ohci->regs->roothub.portstatus [wIndex]); |
// readl (&ohci->regs->roothub.portstatus [wIndex]); |
break; |
case GetHubDescriptor: |
ohci_hub_descriptor (ohci, (struct usb_hub_descriptor *) buf); |
break; |
case GetHubStatus: |
temp = roothub_status (ohci) & ~(RH_HS_CRWE | RH_HS_DRWE); |
*(u32 *) buf = cpu_to_le32 (temp); |
break; |
case GetPortStatus: |
if (!wIndex || wIndex > ports) |
goto error; |
wIndex--; |
temp = roothub_portstatus (ohci, wIndex); |
*(u32 *) buf = cpu_to_le32 (temp); |
#ifndef OHCI_VERBOSE_DEBUG |
if (*(u16*)(buf+2)) /* only if wPortChange is interesting */ |
#endif |
dbg_port (ohci, "GetStatus", wIndex + 1, temp); |
break; |
case SetHubFeature: |
switch (wValue) { |
case C_HUB_OVER_CURRENT: |
// FIXME: this can be cleared, yes? |
case C_HUB_LOCAL_POWER: |
break; |
default: |
goto error; |
} |
break; |
case SetPortFeature: |
if (!wIndex || wIndex > ports) |
goto error; |
wIndex--; |
switch (wValue) { |
case USB_PORT_FEAT_SUSPEND: |
writel (RH_PS_PSS, |
&ohci->regs->roothub.portstatus [wIndex]); |
break; |
case USB_PORT_FEAT_POWER: |
writel (RH_PS_PPS, |
&ohci->regs->roothub.portstatus [wIndex]); |
break; |
case USB_PORT_FEAT_RESET: |
temp = readl (&ohci->regs->roothub.portstatus [wIndex]); |
if (temp & RH_PS_CCS) |
writel (RH_PS_PRS, |
&ohci->regs->roothub.portstatus [wIndex]); |
break; |
default: |
goto error; |
} |
break; |
default: |
error: |
/* "protocol stall" on error */ |
retval = -EPIPE; |
} |
return retval; |
} |
/shark/trunk/drivers/usb/host/ohci-mem.c |
---|
0,0 → 1,147 |
/* |
* OHCI HCD (Host Controller Driver) for USB. |
* |
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> |
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> |
* |
* This file is licenced under the GPL. |
*/ |
/*-------------------------------------------------------------------------*/ |
/* |
* There's basically three types of memory: |
* - data used only by the HCD ... kmalloc is fine |
* - async and periodic schedules, shared by HC and HCD ... these |
* need to use pci_pool or pci_alloc_consistent |
* - driver buffers, read/written by HC ... the hcd glue or the |
* device driver provides us with dma addresses |
* |
* There's also PCI "register" data, which is memory mapped. |
* No memory seen by this driver is pagable. |
*/ |
/*-------------------------------------------------------------------------*/ |
static struct usb_hcd *ohci_hcd_alloc (void) |
{ |
struct ohci_hcd *ohci; |
ohci = (struct ohci_hcd *) kmalloc (sizeof *ohci, GFP_KERNEL); |
if (ohci != 0) { |
memset (ohci, 0, sizeof (struct ohci_hcd)); |
ohci->hcd.product_desc = "OHCI Host Controller"; |
return &ohci->hcd; |
} |
return 0; |
} |
static void ohci_hcd_free (struct usb_hcd *hcd) |
{ |
kfree (hcd_to_ohci (hcd)); |
} |
/*-------------------------------------------------------------------------*/ |
static int ohci_mem_init (struct ohci_hcd *ohci) |
{ |
ohci->td_cache = pci_pool_create ("ohci_td", ohci->hcd.pdev, |
sizeof (struct td), |
32 /* byte alignment */, |
0 /* no page-crossing issues */); |
if (!ohci->td_cache) |
return -ENOMEM; |
ohci->ed_cache = pci_pool_create ("ohci_ed", ohci->hcd.pdev, |
sizeof (struct ed), |
16 /* byte alignment */, |
0 /* no page-crossing issues */); |
if (!ohci->ed_cache) { |
pci_pool_destroy (ohci->td_cache); |
return -ENOMEM; |
} |
return 0; |
} |
static void ohci_mem_cleanup (struct ohci_hcd *ohci) |
{ |
if (ohci->td_cache) { |
pci_pool_destroy (ohci->td_cache); |
ohci->td_cache = 0; |
} |
if (ohci->ed_cache) { |
pci_pool_destroy (ohci->ed_cache); |
ohci->ed_cache = 0; |
} |
} |
/*-------------------------------------------------------------------------*/ |
/* ohci "done list" processing needs this mapping */ |
static inline struct td * |
dma_to_td (struct ohci_hcd *hc, dma_addr_t td_dma) |
{ |
struct td *td; |
td_dma &= TD_MASK; |
td = hc->td_hash [TD_HASH_FUNC(td_dma)]; |
while (td && td->td_dma != td_dma) |
td = td->td_hash; |
return td; |
} |
/* TDs ... */ |
static struct td * |
td_alloc (struct ohci_hcd *hc, int mem_flags) |
{ |
dma_addr_t dma; |
struct td *td; |
td = pci_pool_alloc (hc->td_cache, mem_flags, &dma); |
if (td) { |
/* in case hc fetches it, make it look dead */ |
memset (td, 0, sizeof *td); |
td->hwNextTD = cpu_to_le32 (dma); |
td->td_dma = dma; |
/* hashed in td_fill */ |
} |
return td; |
} |
static void |
td_free (struct ohci_hcd *hc, struct td *td) |
{ |
struct td **prev = &hc->td_hash [TD_HASH_FUNC (td->td_dma)]; |
while (*prev && *prev != td) |
prev = &(*prev)->td_hash; |
if (*prev) |
*prev = td->td_hash; |
else if ((td->hwINFO & TD_DONE) != 0) |
ohci_dbg (hc, "no hash for td %p\n", td); |
pci_pool_free (hc->td_cache, td, td->td_dma); |
} |
/*-------------------------------------------------------------------------*/ |
/* EDs ... */ |
static struct ed * |
ed_alloc (struct ohci_hcd *hc, int mem_flags) |
{ |
dma_addr_t dma; |
struct ed *ed; |
ed = pci_pool_alloc (hc->ed_cache, mem_flags, &dma); |
if (ed) { |
memset (ed, 0, sizeof (*ed)); |
INIT_LIST_HEAD (&ed->td_list); |
ed->dma = dma; |
} |
return ed; |
} |
static void |
ed_free (struct ohci_hcd *hc, struct ed *ed) |
{ |
pci_pool_free (hc->ed_cache, ed, ed->dma); |
} |
/shark/trunk/drivers/usb/host/ehci-dbg.c |
---|
0,0 → 1,688 |
/* |
* Copyright (c) 2001-2002 by David Brownell |
* |
* 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. |
*/ |
/* this file is part of ehci-hcd.c */ |
#define ehci_dbg(ehci, fmt, args...) \ |
dev_dbg ((ehci)->hcd.controller , fmt , ## args ) |
#define ehci_err(ehci, fmt, args...) \ |
dev_err ((ehci)->hcd.controller , fmt , ## args ) |
#define ehci_info(ehci, fmt, args...) \ |
dev_info ((ehci)->hcd.controller , fmt , ## args ) |
#define ehci_warn(ehci, fmt, args...) \ |
dev_warn ((ehci)->hcd.controller , fmt , ## args ) |
#ifdef EHCI_VERBOSE_DEBUG |
# define vdbg dbg |
# define ehci_vdbg ehci_dbg |
#else |
# define vdbg(fmt,args...) do { } while (0) |
# define ehci_vdbg(ehci, fmt, args...) do { } while (0) |
#endif |
#ifdef DEBUG |
/* check the values in the HCSPARAMS register |
* (host controller _Structural_ parameters) |
* see EHCI spec, Table 2-4 for each value |
*/ |
static void dbg_hcs_params (struct ehci_hcd *ehci, char *label) |
{ |
u32 params = readl (&ehci->caps->hcs_params); |
ehci_dbg (ehci, |
"%s hcs_params 0x%x dbg=%d%s cc=%d pcc=%d%s%s ports=%d\n", |
label, params, |
HCS_DEBUG_PORT (params), |
HCS_INDICATOR (params) ? " ind" : "", |
HCS_N_CC (params), |
HCS_N_PCC (params), |
HCS_PORTROUTED (params) ? "" : " ordered", |
HCS_PPC (params) ? "" : " !ppc", |
HCS_N_PORTS (params) |
); |
/* Port routing, per EHCI 0.95 Spec, Section 2.2.5 */ |
if (HCS_PORTROUTED (params)) { |
int i; |
char buf [46], tmp [7], byte; |
buf[0] = 0; |
for (i = 0; i < HCS_N_PORTS (params); i++) { |
byte = readb (&ehci->caps->portroute[(i>>1)]); |
sprintf26(tmp, "%d ", |
((i & 0x1) ? ((byte)&0xf) : ((byte>>4)&0xf))); |
strcat(buf, tmp); |
} |
ehci_dbg (ehci, "%s portroute %s\n", |
label, buf); |
} |
} |
#else |
static inline void dbg_hcs_params (struct ehci_hcd *ehci, char *label) {} |
#endif |
#ifdef DEBUG |
/* check the values in the HCCPARAMS register |
* (host controller _Capability_ parameters) |
* see EHCI Spec, Table 2-5 for each value |
* */ |
static void dbg_hcc_params (struct ehci_hcd *ehci, char *label) |
{ |
u32 params = readl (&ehci->caps->hcc_params); |
if (HCC_ISOC_CACHE (params)) { |
ehci_dbg (ehci, |
"%s hcc_params %04x caching frame %s%s%s\n", |
label, params, |
HCC_PGM_FRAMELISTLEN (params) ? "256/512/1024" : "1024", |
HCC_CANPARK (params) ? " park" : "", |
HCC_64BIT_ADDR (params) ? " 64 bit addr" : ""); |
} else { |
ehci_dbg (ehci, |
"%s hcc_params %04x thresh %d uframes %s%s%s\n", |
label, |
params, |
HCC_ISOC_THRES (params), |
HCC_PGM_FRAMELISTLEN (params) ? "256/512/1024" : "1024", |
HCC_CANPARK (params) ? " park" : "", |
HCC_64BIT_ADDR (params) ? " 64 bit addr" : ""); |
} |
} |
#else |
static inline void dbg_hcc_params (struct ehci_hcd *ehci, char *label) {} |
#endif |
#ifdef DEBUG |
static void __attribute__((__unused__)) |
dbg_qtd (char *label, struct ehci_hcd *ehci, struct ehci_qtd *qtd) |
{ |
ehci_dbg (ehci, "%s td %p n%08x %08x t%08x p0=%08x\n", label, qtd, |
cpu_to_le32p (&qtd->hw_next), |
cpu_to_le32p (&qtd->hw_alt_next), |
cpu_to_le32p (&qtd->hw_token), |
cpu_to_le32p (&qtd->hw_buf [0])); |
if (qtd->hw_buf [1]) |
ehci_dbg (ehci, " p1=%08x p2=%08x p3=%08x p4=%08x\n", |
cpu_to_le32p (&qtd->hw_buf [1]), |
cpu_to_le32p (&qtd->hw_buf [2]), |
cpu_to_le32p (&qtd->hw_buf [3]), |
cpu_to_le32p (&qtd->hw_buf [4])); |
} |
static void __attribute__((__unused__)) |
dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) |
{ |
ehci_dbg (ehci, "%s qh %p n%08x info %x %x qtd %x\n", label, |
qh, qh->hw_next, qh->hw_info1, qh->hw_info2, |
qh->hw_current); |
dbg_qtd ("overlay", ehci, (struct ehci_qtd *) &qh->hw_qtd_next); |
} |
static int __attribute__((__unused__)) |
dbg_status_buf (char *buf, unsigned len, char *label, u32 status) |
{ |
return snprintf26(buf, len, |
"%s%sstatus %04x%s%s%s%s%s%s%s%s%s%s", |
label, label [0] ? " " : "", status, |
(status & STS_ASS) ? " Async" : "", |
(status & STS_PSS) ? " Periodic" : "", |
(status & STS_RECL) ? " Recl" : "", |
(status & STS_HALT) ? " Halt" : "", |
(status & STS_IAA) ? " IAA" : "", |
(status & STS_FATAL) ? " FATAL" : "", |
(status & STS_FLR) ? " FLR" : "", |
(status & STS_PCD) ? " PCD" : "", |
(status & STS_ERR) ? " ERR" : "", |
(status & STS_INT) ? " INT" : "" |
); |
} |
static int __attribute__((__unused__)) |
dbg_intr_buf (char *buf, unsigned len, char *label, u32 enable) |
{ |
return snprintf26(buf, len, |
"%s%sintrenable %02x%s%s%s%s%s%s", |
label, label [0] ? " " : "", enable, |
(enable & STS_IAA) ? " IAA" : "", |
(enable & STS_FATAL) ? " FATAL" : "", |
(enable & STS_FLR) ? " FLR" : "", |
(enable & STS_PCD) ? " PCD" : "", |
(enable & STS_ERR) ? " ERR" : "", |
(enable & STS_INT) ? " INT" : "" |
); |
} |
static const char *const fls_strings [] = |
{ "1024", "512", "256", "??" }; |
static int dbg_command_buf (char *buf, unsigned len, char *label, u32 command) |
{ |
return snprintf26(buf, len, |
"%s%scommand %06x %s=%d ithresh=%d%s%s%s%s period=%s%s %s", |
label, label [0] ? " " : "", command, |
(command & CMD_PARK) ? "park" : "(park)", |
CMD_PARK_CNT (command), |
(command >> 16) & 0x3f, |
(command & CMD_LRESET) ? " LReset" : "", |
(command & CMD_IAAD) ? " IAAD" : "", |
(command & CMD_ASE) ? " Async" : "", |
(command & CMD_PSE) ? " Periodic" : "", |
fls_strings [(command >> 2) & 0x3], |
(command & CMD_RESET) ? " Reset" : "", |
(command & CMD_RUN) ? "RUN" : "HALT" |
); |
} |
static int |
dbg_port_buf (char *buf, unsigned len, char *label, int port, u32 status) |
{ |
char *sig; |
/* signaling state */ |
switch (status & (3 << 10)) { |
case 0 << 10: sig = "se0"; break; |
case 1 << 10: sig = "k"; break; /* low speed */ |
case 2 << 10: sig = "j"; break; |
default: sig = "?"; break; |
} |
return snprintf26(buf, len, |
"%s%sport %d status %06x%s%s sig=%s %s%s%s%s%s%s%s%s%s", |
label, label [0] ? " " : "", port, status, |
(status & PORT_POWER) ? " POWER" : "", |
(status & PORT_OWNER) ? " OWNER" : "", |
sig, |
(status & PORT_RESET) ? " RESET" : "", |
(status & PORT_SUSPEND) ? " SUSPEND" : "", |
(status & PORT_RESUME) ? " RESUME" : "", |
(status & PORT_OCC) ? " OCC" : "", |
(status & PORT_OC) ? " OC" : "", |
(status & PORT_PEC) ? " PEC" : "", |
(status & PORT_PE) ? " PE" : "", |
(status & PORT_CSC) ? " CSC" : "", |
(status & PORT_CONNECT) ? " CONNECT" : "" |
); |
} |
#else |
static inline void __attribute__((__unused__)) |
dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) |
{} |
static inline int __attribute__((__unused__)) |
dbg_status_buf (char *buf, unsigned len, char *label, u32 status) |
{ return 0; } |
static inline int __attribute__((__unused__)) |
dbg_command_buf (char *buf, unsigned len, char *label, u32 command) |
{ return 0; } |
static inline int __attribute__((__unused__)) |
dbg_intr_buf (char *buf, unsigned len, char *label, u32 enable) |
{ return 0; } |
static inline int __attribute__((__unused__)) |
dbg_port_buf (char *buf, unsigned len, char *label, int port, u32 status) |
{ return 0; } |
#endif /* DEBUG */ |
/* functions have the "wrong" filename when they're output... */ |
#define dbg_status(ehci, label, status) { \ |
char _buf [80]; \ |
dbg_status_buf (_buf, sizeof _buf, label, status); \ |
ehci_dbg (ehci, "%s\n", _buf); \ |
} |
#define dbg_cmd(ehci, label, command) { \ |
char _buf [80]; \ |
dbg_command_buf (_buf, sizeof _buf, label, command); \ |
ehci_dbg (ehci, "%s\n", _buf); \ |
} |
#define dbg_port(ehci, label, port, status) { \ |
char _buf [80]; \ |
dbg_port_buf (_buf, sizeof _buf, label, port, status); \ |
ehci_dbg (ehci, "%s\n", _buf); \ |
} |
/*-------------------------------------------------------------------------*/ |
#ifdef STUB_DEBUG_FILES |
static inline void create_debug_files (struct ehci_hcd *bus) { } |
static inline void remove_debug_files (struct ehci_hcd *bus) { } |
#else |
/* troubleshooting help: expose state in driverfs */ |
#define speed_char(info1) ({ char tmp; \ |
switch (info1 & (3 << 12)) { \ |
case 0 << 12: tmp = 'f'; break; \ |
case 1 << 12: tmp = 'l'; break; \ |
case 2 << 12: tmp = 'h'; break; \ |
default: tmp = '?'; break; \ |
}; tmp; }) |
static inline char token_mark (u32 token) |
{ |
token = le32_to_cpu (token); |
if (token & QTD_STS_ACTIVE) |
return '*'; |
if (token & QTD_STS_HALT) |
return '-'; |
if (!IS_SHORT_READ (token)) |
return ' '; |
/* tries to advance through hw_alt_next */ |
return '/'; |
} |
static void qh_lines ( |
struct ehci_hcd *ehci, |
struct ehci_qh *qh, |
char **nextp, |
unsigned *sizep |
) |
{ |
u32 scratch; |
u32 hw_curr; |
struct list_head *entry; |
struct ehci_qtd *td; |
unsigned temp; |
unsigned size = *sizep; |
char *next = *nextp; |
char mark; |
if (qh->hw_qtd_next == EHCI_LIST_END) /* NEC does this */ |
mark = '@'; |
else |
mark = token_mark (qh->hw_token); |
if (mark == '/') { /* qh_alt_next controls qh advance? */ |
if ((qh->hw_alt_next & QTD_MASK) == ehci->async->hw_alt_next) |
mark = '#'; /* blocked */ |
else if (qh->hw_alt_next == EHCI_LIST_END) |
mark = '.'; /* use hw_qtd_next */ |
/* else alt_next points to some other qtd */ |
} |
scratch = cpu_to_le32p (&qh->hw_info1); |
hw_curr = (mark == '*') ? cpu_to_le32p (&qh->hw_current) : 0; |
temp = snprintf26(next, size, |
"qh/%p dev%d %cs ep%d %08x %08x (%08x%c %s nak%d)", |
qh, scratch & 0x007f, |
speed_char (scratch), |
(scratch >> 8) & 0x000f, |
scratch, cpu_to_le32p (&qh->hw_info2), |
cpu_to_le32p (&qh->hw_token), mark, |
(__constant_cpu_to_le32 (QTD_TOGGLE) & qh->hw_token) |
? "data0" : "data1", |
(cpu_to_le32p (&qh->hw_alt_next) >> 1) & 0x0f); |
size -= temp; |
next += temp; |
/* hc may be modifying the list as we read it ... */ |
list_for_each (entry, &qh->qtd_list) { |
td = list_entry (entry, struct ehci_qtd, qtd_list); |
scratch = cpu_to_le32p (&td->hw_token); |
mark = ' '; |
if (hw_curr == td->qtd_dma) |
mark = '*'; |
else if (qh->hw_qtd_next == td->qtd_dma) |
mark = '+'; |
else if (QTD_LENGTH (scratch)) { |
if (td->hw_alt_next == ehci->async->hw_alt_next) |
mark = '#'; |
else if (td->hw_alt_next != EHCI_LIST_END) |
mark = '/'; |
} |
temp = snprintf26(next, size, |
"\n\t%p%c%s len=%d %08x urb %p", |
td, mark, ({ char *tmp; |
switch ((scratch>>8)&0x03) { |
case 0: tmp = "out"; break; |
case 1: tmp = "in"; break; |
case 2: tmp = "setup"; break; |
default: tmp = "?"; break; |
} tmp;}), |
(scratch >> 16) & 0x7fff, |
scratch, |
td->urb); |
if (temp < 0) |
temp = 0; |
else if (size < temp) |
temp = size; |
size -= temp; |
next += temp; |
if (temp == size) |
goto done; |
} |
temp = snprintf26(next, size, "\n"); |
if (temp < 0) |
temp = 0; |
else if (size < temp) |
temp = size; |
size -= temp; |
next += temp; |
done: |
*sizep = size; |
*nextp = next; |
} |
static ssize_t |
show_async (struct class_device *class_dev, char *buf) |
{ |
struct usb_bus *bus; |
struct usb_hcd *hcd; |
struct ehci_hcd *ehci; |
unsigned long flags; |
unsigned temp, size; |
char *next; |
struct ehci_qh *qh; |
*buf = 0; |
bus = to_usb_bus(class_dev); |
hcd = bus->hcpriv; |
ehci = hcd_to_ehci (hcd); |
next = buf; |
size = PAGE_SIZE; |
/* dumps a snapshot of the async schedule. |
* usually empty except for long-term bulk reads, or head. |
* one QH per line, and TDs we know about |
*/ |
spin_lock_irqsave (&ehci->lock, flags); |
for (qh = ehci->async->qh_next.qh; size > 0 && qh; qh = qh->qh_next.qh) |
qh_lines (ehci, qh, &next, &size); |
if (ehci->reclaim && size > 0) { |
temp = snprintf26(next, size, "\nreclaim =\n"); |
size -= temp; |
next += temp; |
for (qh = ehci->reclaim; size > 0 && qh; qh = qh->reclaim) |
qh_lines (ehci, qh, &next, &size); |
} |
spin_unlock_irqrestore (&ehci->lock, flags); |
return strlen (buf); |
} |
static CLASS_DEVICE_ATTR (async, S_IRUGO, show_async, NULL); |
#define DBG_SCHED_LIMIT 64 |
static ssize_t |
show_periodic (struct class_device *class_dev, char *buf) |
{ |
struct usb_bus *bus; |
struct usb_hcd *hcd; |
struct ehci_hcd *ehci; |
unsigned long flags; |
union ehci_shadow p, *seen; |
unsigned temp, size, seen_count; |
char *next; |
unsigned i, tag; |
if (!(seen = kmalloc (DBG_SCHED_LIMIT * sizeof *seen, SLAB_ATOMIC))) |
return 0; |
seen_count = 0; |
bus = to_usb_bus(class_dev); |
hcd = bus->hcpriv; |
ehci = hcd_to_ehci (hcd); |
next = buf; |
size = PAGE_SIZE; |
temp = snprintf26(next, size, "size = %d\n", ehci->periodic_size); |
size -= temp; |
next += temp; |
/* dump a snapshot of the periodic schedule. |
* iso changes, interrupt usually doesn't. |
*/ |
spin_lock_irqsave (&ehci->lock, flags); |
for (i = 0; i < ehci->periodic_size; i++) { |
p = ehci->pshadow [i]; |
if (likely (!p.ptr)) |
continue; |
tag = Q_NEXT_TYPE (ehci->periodic [i]); |
temp = snprintf26(next, size, "%4d: ", i); |
size -= temp; |
next += temp; |
do { |
switch (tag) { |
case Q_TYPE_QH: |
temp = snprintf26(next, size, " qh%d-%04x/%p", |
p.qh->period, |
le32_to_cpup (&p.qh->hw_info2) |
/* uframe masks */ |
& 0xffff, |
p.qh); |
size -= temp; |
next += temp; |
/* don't repeat what follows this qh */ |
for (temp = 0; temp < seen_count; temp++) { |
if (seen [temp].ptr != p.ptr) |
continue; |
if (p.qh->qh_next.ptr) |
temp = snprintf26(next, size, |
" ..."); |
p.ptr = 0; |
break; |
} |
/* show more info the first time around */ |
if (temp == seen_count && p.ptr) { |
u32 scratch = cpu_to_le32p ( |
&p.qh->hw_info1); |
struct ehci_qtd *qtd; |
char *type = ""; |
/* count tds, get ep direction */ |
temp = 0; |
list_for_each_entry (qtd, |
&p.qh->qtd_list, |
qtd_list) { |
temp++; |
switch (0x03 & (le32_to_cpu ( |
qtd->hw_token) >> 8)) { |
case 0: type = "out"; continue; |
case 1: type = "in"; continue; |
} |
} |
temp = snprintf26(next, size, |
" (%c%d ep%d%s " |
"[%d/%d] q%d p%d)", |
speed_char (scratch), |
scratch & 0x007f, |
(scratch >> 8) & 0x000f, type, |
p.qh->usecs, p.qh->c_usecs, |
temp, |
0x7ff & (scratch >> 16)); |
if (seen_count < DBG_SCHED_LIMIT) |
seen [seen_count++].qh = p.qh; |
} else |
temp = 0; |
if (p.qh) { |
tag = Q_NEXT_TYPE (p.qh->hw_next); |
p = p.qh->qh_next; |
} |
break; |
case Q_TYPE_FSTN: |
temp = snprintf26(next, size, |
" fstn-%8x/%p", p.fstn->hw_prev, |
p.fstn); |
tag = Q_NEXT_TYPE (p.fstn->hw_next); |
p = p.fstn->fstn_next; |
break; |
case Q_TYPE_ITD: |
temp = snprintf26(next, size, |
" itd/%p", p.itd); |
tag = Q_NEXT_TYPE (p.itd->hw_next); |
p = p.itd->itd_next; |
break; |
case Q_TYPE_SITD: |
temp = snprintf26(next, size, |
" sitd/%p", p.sitd); |
tag = Q_NEXT_TYPE (p.sitd->hw_next); |
p = p.sitd->sitd_next; |
break; |
} |
size -= temp; |
next += temp; |
} while (p.ptr); |
temp = snprintf26(next, size, "\n"); |
size -= temp; |
next += temp; |
} |
spin_unlock_irqrestore (&ehci->lock, flags); |
kfree (seen); |
return PAGE_SIZE - size; |
} |
static CLASS_DEVICE_ATTR (periodic, S_IRUGO, show_periodic, NULL); |
#undef DBG_SCHED_LIMIT |
static ssize_t |
show_registers (struct class_device *class_dev, char *buf) |
{ |
struct usb_bus *bus; |
struct usb_hcd *hcd; |
struct ehci_hcd *ehci; |
unsigned long flags; |
unsigned temp, size, i; |
char *next, scratch [80]; |
static char fmt [] = "%*s\n"; |
static char label [] = ""; |
bus = to_usb_bus(class_dev); |
hcd = bus->hcpriv; |
ehci = hcd_to_ehci (hcd); |
next = buf; |
size = PAGE_SIZE; |
spin_lock_irqsave (&ehci->lock, flags); |
/* Capability Registers */ |
i = readw (&ehci->caps->hci_version); |
temp = snprintf26(next, size, |
"PCI device %s\nEHCI %x.%02x, hcd state %d (driver " DRIVER_VERSION ")\n", |
pci_name(hcd->pdev), |
i >> 8, i & 0x0ff, ehci->hcd.state); |
size -= temp; |
next += temp; |
// FIXME interpret both types of params |
i = readl (&ehci->caps->hcs_params); |
temp = snprintf26(next, size, "structural params 0x%08x\n", i); |
size -= temp; |
next += temp; |
i = readl (&ehci->caps->hcc_params); |
temp = snprintf26(next, size, "capability params 0x%08x\n", i); |
size -= temp; |
next += temp; |
/* Operational Registers */ |
temp = dbg_status_buf (scratch, sizeof scratch, label, |
readl (&ehci->regs->status)); |
temp = snprintf26(next, size, fmt, temp, scratch); |
size -= temp; |
next += temp; |
temp = dbg_command_buf (scratch, sizeof scratch, label, |
readl (&ehci->regs->command)); |
temp = snprintf26(next, size, fmt, temp, scratch); |
size -= temp; |
next += temp; |
temp = dbg_intr_buf (scratch, sizeof scratch, label, |
readl (&ehci->regs->intr_enable)); |
temp = snprintf26(next, size, fmt, temp, scratch); |
size -= temp; |
next += temp; |
temp = snprintf26(next, size, "uframe %04x\n", |
readl (&ehci->regs->frame_index)); |
size -= temp; |
next += temp; |
for (i = 0; i < HCS_N_PORTS (ehci->hcs_params); i++) { |
temp = dbg_port_buf (scratch, sizeof scratch, label, i, |
readl (&ehci->regs->port_status [i])); |
temp = snprintf26(next, size, fmt, temp, scratch); |
size -= temp; |
next += temp; |
} |
if (ehci->reclaim) { |
temp = snprintf26(next, size, "reclaim qh %p%s\n", |
ehci->reclaim, |
ehci->reclaim_ready ? " ready" : ""); |
size -= temp; |
next += temp; |
} |
#ifdef EHCI_STATS |
temp = snprintf26(next, size, |
"irq normal %ld err %ld reclaim %ld (lost %ld)\n", |
ehci->stats.normal, ehci->stats.error, ehci->stats.reclaim, |
ehci->stats.lost_iaa); |
size -= temp; |
next += temp; |
temp = snprintf26(next, size, "complete %ld unlink %ld\n", |
ehci->stats.complete, ehci->stats.unlink); |
size -= temp; |
next += temp; |
#endif |
spin_unlock_irqrestore (&ehci->lock, flags); |
return PAGE_SIZE - size; |
} |
static CLASS_DEVICE_ATTR (registers, S_IRUGO, show_registers, NULL); |
static inline void create_debug_files (struct ehci_hcd *bus) |
{ |
class_device_create_file(&bus->hcd.self.class_dev, &class_device_attr_async); |
class_device_create_file(&bus->hcd.self.class_dev, &class_device_attr_periodic); |
class_device_create_file(&bus->hcd.self.class_dev, &class_device_attr_registers); |
} |
static inline void remove_debug_files (struct ehci_hcd *bus) |
{ |
class_device_remove_file(&bus->hcd.self.class_dev, &class_device_attr_async); |
class_device_remove_file(&bus->hcd.self.class_dev, &class_device_attr_periodic); |
class_device_remove_file(&bus->hcd.self.class_dev, &class_device_attr_registers); |
} |
#endif /* STUB_DEBUG_FILES */ |
/shark/trunk/drivers/usb/host/uhci-hub.c |
---|
0,0 → 1,184 |
/* |
* Universal Host Controller Interface driver for USB. |
* |
* Maintainer: Johannes Erdfelt <johannes@erdfelt.com> |
* |
* (C) Copyright 1999 Linus Torvalds |
* (C) Copyright 1999-2002 Johannes Erdfelt, johannes@erdfelt.com |
* (C) Copyright 1999 Randy Dunlap |
* (C) Copyright 1999 Georg Acher, acher@in.tum.de |
* (C) Copyright 1999 Deti Fliegl, deti@fliegl.de |
* (C) Copyright 1999 Thomas Sailer, sailer@ife.ee.ethz.ch |
*/ |
static __u8 root_hub_hub_des[] = |
{ |
0x09, /* __u8 bLength; */ |
0x29, /* __u8 bDescriptorType; Hub-descriptor */ |
0x02, /* __u8 bNbrPorts; */ |
0x00, /* __u16 wHubCharacteristics; */ |
0x00, |
0x01, /* __u8 bPwrOn2pwrGood; 2ms */ |
0x00, /* __u8 bHubContrCurrent; 0 mA */ |
0x00, /* __u8 DeviceRemovable; *** 7 Ports max *** */ |
0xff /* __u8 PortPwrCtrlMask; *** 7 ports max *** */ |
}; |
static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf) |
{ |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
unsigned int io_addr = uhci->io_addr; |
int i, len = 1; |
*buf = 0; |
for (i = 0; i < uhci->rh_numports; i++) { |
*buf |= ((inw(io_addr + USBPORTSC1 + i * 2) & 0xa) > 0 ? (1 << (i + 1)) : 0); |
len = (i + 1) / 8 + 1; |
} |
return !!*buf; |
} |
#define OK(x) len = (x); break |
#define CLR_RH_PORTSTAT(x) \ |
status = inw(io_addr + USBPORTSC1 + 2 * (wIndex-1)); \ |
status = (status & 0xfff5) & ~(x); \ |
outw(status, io_addr + USBPORTSC1 + 2 * (wIndex-1)) |
#define SET_RH_PORTSTAT(x) \ |
status = inw(io_addr + USBPORTSC1 + 2 * (wIndex-1)); \ |
status = (status & 0xfff5) | (x); \ |
outw(status, io_addr + USBPORTSC1 + 2 * (wIndex-1)) |
/* size of returned buffer is part of USB spec */ |
static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, |
u16 wIndex, char *buf, u16 wLength) |
{ |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
int i, status, retval = 0, len = 0; |
unsigned int io_addr = uhci->io_addr; |
__u16 cstatus; |
char c_p_r[8]; |
for (i = 0; i < 8; i++) |
c_p_r[i] = 0; |
switch (typeReq) { |
/* Request Destination: |
without flags: Device, |
RH_INTERFACE: interface, |
RH_ENDPOINT: endpoint, |
RH_CLASS means HUB here, |
RH_OTHER | RH_CLASS almost ever means HUB_PORT here |
*/ |
case GetHubStatus: |
*(__u32 *)buf = cpu_to_le32(0); |
OK(4); /* hub power */ |
case GetPortStatus: |
status = inw(io_addr + USBPORTSC1 + 2 * (wIndex - 1)); |
cstatus = ((status & USBPORTSC_CSC) >> (1 - 0)) | |
((status & USBPORTSC_PEC) >> (3 - 1)) | |
(c_p_r[wIndex - 1] << (0 + 4)); |
status = (status & USBPORTSC_CCS) | |
((status & USBPORTSC_PE) >> (2 - 1)) | |
((status & USBPORTSC_SUSP) >> (12 - 2)) | |
((status & USBPORTSC_PR) >> (9 - 4)) | |
(1 << 8) | /* power on */ |
((status & USBPORTSC_LSDA) << (-8 + 9)); |
*(__u16 *)buf = cpu_to_le16(status); |
*(__u16 *)(buf + 2) = cpu_to_le16(cstatus); |
OK(4); |
case SetHubFeature: |
switch (wValue) { |
case C_HUB_OVER_CURRENT: |
case C_HUB_LOCAL_POWER: |
break; |
default: |
goto err; |
} |
break; |
case ClearHubFeature: |
switch (wValue) { |
case C_HUB_OVER_CURRENT: |
OK(0); /* hub power over current */ |
default: |
goto err; |
} |
break; |
case SetPortFeature: |
if (!wIndex || wIndex > uhci->rh_numports) |
goto err; |
switch (wValue) { |
case USB_PORT_FEAT_SUSPEND: |
SET_RH_PORTSTAT(USBPORTSC_SUSP); |
OK(0); |
case USB_PORT_FEAT_RESET: |
SET_RH_PORTSTAT(USBPORTSC_PR); |
mdelay(50); /* USB v1.1 7.1.7.3 */ |
c_p_r[wIndex - 1] = 1; |
CLR_RH_PORTSTAT(USBPORTSC_PR); |
udelay(10); |
SET_RH_PORTSTAT(USBPORTSC_PE); |
mdelay(10); |
SET_RH_PORTSTAT(0xa); |
OK(0); |
case USB_PORT_FEAT_POWER: |
OK(0); /* port power ** */ |
case USB_PORT_FEAT_ENABLE: |
SET_RH_PORTSTAT(USBPORTSC_PE); |
OK(0); |
default: |
goto err; |
} |
break; |
case ClearPortFeature: |
if (!wIndex || wIndex > uhci->rh_numports) |
goto err; |
switch (wValue) { |
case USB_PORT_FEAT_ENABLE: |
CLR_RH_PORTSTAT(USBPORTSC_PE); |
OK(0); |
case USB_PORT_FEAT_C_ENABLE: |
SET_RH_PORTSTAT(USBPORTSC_PEC); |
OK(0); |
case USB_PORT_FEAT_SUSPEND: |
CLR_RH_PORTSTAT(USBPORTSC_SUSP); |
OK(0); |
case USB_PORT_FEAT_C_SUSPEND: |
/*** WR_RH_PORTSTAT(RH_PS_PSSC); */ |
OK(0); |
case USB_PORT_FEAT_POWER: |
OK(0); /* port power */ |
case USB_PORT_FEAT_C_CONNECTION: |
SET_RH_PORTSTAT(USBPORTSC_CSC); |
OK(0); |
case USB_PORT_FEAT_C_OVER_CURRENT: |
OK(0); /* port power over current */ |
case USB_PORT_FEAT_C_RESET: |
c_p_r[wIndex - 1] = 0; |
OK(0); |
default: |
goto err; |
} |
break; |
case GetHubDescriptor: |
len = min_t(unsigned int, wLength, |
min_t(unsigned int, sizeof(root_hub_hub_des), wLength)); |
memcpy(buf, root_hub_hub_des, len); |
if (len > 2) |
buf[2] = uhci->rh_numports; |
OK(len); |
default: |
err: |
retval = -EPIPE; |
} |
return retval; |
} |
/shark/trunk/drivers/usb/host/ehci-hcd.c |
---|
0,0 → 1,1032 |
/* |
* Copyright (c) 2000-2002 by David Brownell |
* |
* 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. |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#ifdef CONFIG_USB_DEBUG |
#define DEBUG |
#else |
#undef DEBUG |
#endif |
#include <linux/module.h> |
#include <linux/pci.h> |
#include <linux/kernel.h> |
#include <linux/delay.h> |
#include <linux/ioport.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/smp_lock.h> |
#include <linux/errno.h> |
#include <linux/init.h> |
#include <linux/timer.h> |
#include <linux/list.h> |
#include <linux/interrupt.h> |
#include <linux/reboot.h> |
#include <linux/usb.h> |
#include <linux/moduleparam.h> |
#include "../core/hcd.h" |
#include <asm/byteorder.h> |
#include <asm/io.h> |
#include <asm/irq.h> |
#include <asm/system.h> |
#include <asm/unaligned.h> |
/*-------------------------------------------------------------------------*/ |
/* |
* EHCI hc_driver implementation ... experimental, incomplete. |
* Based on the final 1.0 register interface specification. |
* |
* USB 2.0 shows up in upcoming www.pcmcia.org technology. |
* First was PCMCIA, like ISA; then CardBus, which is PCI. |
* Next comes "CardBay", using USB 2.0 signals. |
* |
* Contains additional contributions by Brad Hards, Rory Bolt, and others. |
* Special thanks to Intel and VIA for providing host controllers to |
* test this driver on, and Cypress (including In-System Design) for |
* providing early devices for those host controllers to talk to! |
* |
* HISTORY: |
* |
* 2002-11-29 Correct handling for hw async_next register. |
* 2002-08-06 Handling for bulk and interrupt transfers is mostly shared; |
* only scheduling is different, no arbitrary limitations. |
* 2002-07-25 Sanity check PCI reads, mostly for better cardbus support, |
* clean up HC run state handshaking. |
* 2002-05-24 Preliminary FS/LS interrupts, using scheduling shortcuts |
* 2002-05-11 Clear TT errors for FS/LS ctrl/bulk. Fill in some other |
* missing pieces: enabling 64bit dma, handoff from BIOS/SMM. |
* 2002-05-07 Some error path cleanups to report better errors; wmb(); |
* use non-CVS version id; better iso bandwidth claim. |
* 2002-04-19 Control/bulk/interrupt submit no longer uses giveback() on |
* errors in submit path. Bugfixes to interrupt scheduling/processing. |
* 2002-03-05 Initial high-speed ISO support; reduce ITD memory; shift |
* more checking to generic hcd framework (db). Make it work with |
* Philips EHCI; reduce PCI traffic; shorten IRQ path (Rory Bolt). |
* 2002-01-14 Minor cleanup; version synch. |
* 2002-01-08 Fix roothub handoff of FS/LS to companion controllers. |
* 2002-01-04 Control/Bulk queuing behaves. |
* |
* 2001-12-12 Initial patch version for Linux 2.5.1 kernel. |
* 2001-June Works with usb-storage and NEC EHCI on 2.4 |
*/ |
#define DRIVER_VERSION "2003-Jun-13" |
#define DRIVER_AUTHOR "David Brownell" |
#define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver" |
static const char hcd_name [] = "ehci_hcd"; |
// #define EHCI_VERBOSE_DEBUG |
// #define have_split_iso |
#ifdef DEBUG |
#define EHCI_STATS |
#endif |
/* magic numbers that can affect system performance */ |
#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ |
#define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ |
#define EHCI_TUNE_RL_TT 0 |
#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ |
#define EHCI_TUNE_MULT_TT 1 |
#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ |
#define EHCI_IAA_JIFFIES (HZ/100) /* arbitrary; ~10 msec */ |
#define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ |
#define EHCI_ASYNC_JIFFIES (HZ/20) /* async idle timeout */ |
#define EHCI_SHRINK_JIFFIES (HZ/200) /* async qh unlink delay */ |
/* Initial IRQ latency: lower than default */ |
static int log2_irq_thresh = 0; // 0 to 6 |
module_param (log2_irq_thresh, int, S_IRUGO); |
MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); |
#define INTR_MASK (STS_IAA | STS_FATAL | STS_ERR | STS_INT) |
/*-------------------------------------------------------------------------*/ |
#include "ehci.h" |
#include "ehci-dbg.c" |
/*-------------------------------------------------------------------------*/ |
/* |
* handshake - spin reading hc until handshake completes or fails |
* @ptr: address of hc register to be read |
* @mask: bits to look at in result of read |
* @done: value of those bits when handshake succeeds |
* @usec: timeout in microseconds |
* |
* Returns negative errno, or zero on success |
* |
* Success happens when the "mask" bits have the specified value (hardware |
* handshake done). There are two failure modes: "usec" have passed (major |
* hardware flakeout), or the register reads as all-ones (hardware removed). |
* |
* That last failure should_only happen in cases like physical cardbus eject |
* before driver shutdown. But it also seems to be caused by bugs in cardbus |
* bridge shutdown: shutting down the bridge before the devices using it. |
*/ |
static int handshake (u32 *ptr, u32 mask, u32 done, int usec) |
{ |
u32 result; |
do { |
result = readl (ptr); |
if (result == ~(u32)0) /* card removed */ |
return -ENODEV; |
result &= mask; |
if (result == done) |
return 0; |
udelay (1); |
usec--; |
} while (usec > 0); |
return -ETIMEDOUT; |
} |
/* |
* hc states include: unknown, halted, ready, running |
* transitional states are messy just now |
* trying to avoid "running" unless urbs are active |
* a "ready" hc can be finishing prefetched work |
*/ |
/* force HC to halt state from unknown (EHCI spec section 2.3) */ |
static int ehci_halt (struct ehci_hcd *ehci) |
{ |
u32 temp = readl (&ehci->regs->status); |
if ((temp & STS_HALT) != 0) |
return 0; |
temp = readl (&ehci->regs->command); |
temp &= ~CMD_RUN; |
writel (temp, &ehci->regs->command); |
return handshake (&ehci->regs->status, STS_HALT, STS_HALT, 16 * 125); |
} |
/* reset a non-running (STS_HALT == 1) controller */ |
static int ehci_reset (struct ehci_hcd *ehci) |
{ |
u32 command = readl (&ehci->regs->command); |
command |= CMD_RESET; |
dbg_cmd (ehci, "reset", command); |
writel (command, &ehci->regs->command); |
ehci->hcd.state = USB_STATE_HALT; |
return handshake (&ehci->regs->command, CMD_RESET, 0, 250 * 1000); |
} |
/* idle the controller (from running) */ |
static void ehci_ready (struct ehci_hcd *ehci) |
{ |
u32 temp; |
#ifdef DEBUG |
if (!HCD_IS_RUNNING (ehci->hcd.state)) |
BUG (); |
#endif |
/* wait for any schedule enables/disables to take effect */ |
temp = 0; |
if (ehci->async->qh_next.qh) |
temp = STS_ASS; |
if (ehci->next_uframe != -1) |
temp |= STS_PSS; |
if (handshake (&ehci->regs->status, STS_ASS | STS_PSS, |
temp, 16 * 125) != 0) { |
ehci->hcd.state = USB_STATE_HALT; |
return; |
} |
/* then disable anything that's still active */ |
temp = readl (&ehci->regs->command); |
temp &= ~(CMD_ASE | CMD_IAAD | CMD_PSE); |
writel (temp, &ehci->regs->command); |
/* hardware can take 16 microframes to turn off ... */ |
if (handshake (&ehci->regs->status, STS_ASS | STS_PSS, |
0, 16 * 125) != 0) { |
ehci->hcd.state = USB_STATE_HALT; |
return; |
} |
} |
/*-------------------------------------------------------------------------*/ |
#include "ehci-hub.c" |
#include "ehci-mem.c" |
#include "ehci-q.c" |
#include "ehci-sched.c" |
/*-------------------------------------------------------------------------*/ |
static void ehci_work(struct ehci_hcd *ehci, struct pt_regs *regs); |
static void ehci_watchdog (unsigned long param) |
{ |
struct ehci_hcd *ehci = (struct ehci_hcd *) param; |
unsigned long flags; |
spin_lock_irqsave (&ehci->lock, flags); |
/* lost IAA irqs wedge things badly; seen with a vt8235 */ |
if (ehci->reclaim) { |
u32 status = readl (&ehci->regs->status); |
if (status & STS_IAA) { |
ehci_vdbg (ehci, "lost IAA\n"); |
COUNT (ehci->stats.lost_iaa); |
writel (STS_IAA, &ehci->regs->status); |
ehci->reclaim_ready = 1; |
} |
} |
/* stop async processing after it's idled a bit */ |
if (test_bit (TIMER_ASYNC_OFF, &ehci->actions)) |
start_unlink_async (ehci, ehci->async); |
/* ehci could run by timer, without IRQs ... */ |
ehci_work (ehci, NULL); |
spin_unlock_irqrestore (&ehci->lock, flags); |
} |
/* EHCI 0.96 (and later) section 5.1 says how to kick BIOS/SMM/... |
* off the controller (maybe it can boot from highspeed USB disks). |
*/ |
static int bios_handoff (struct ehci_hcd *ehci, int where, u32 cap) |
{ |
if (cap & (1 << 16)) { |
int msec = 500; |
/* request handoff to OS */ |
cap &= 1 << 24; |
pci_write_config_dword (ehci->hcd.pdev, where, cap); |
/* and wait a while for it to happen */ |
do { |
wait_ms (10); |
msec -= 10; |
pci_read_config_dword (ehci->hcd.pdev, where, &cap); |
} while ((cap & (1 << 16)) && msec); |
if (cap & (1 << 16)) { |
ehci_err (ehci, "BIOS handoff failed (%d, %04x)\n", |
where, cap); |
return 1; |
} |
ehci_dbg (ehci, "BIOS handoff succeeded\n"); |
} |
return 0; |
} |
static int |
ehci_reboot (struct notifier_block *self, unsigned long code, void *null) |
{ |
struct ehci_hcd *ehci; |
ehci = container_of (self, struct ehci_hcd, reboot_notifier); |
/* make BIOS/etc use companion controller during reboot */ |
writel (0, &ehci->regs->configured_flag); |
return 0; |
} |
/* called by khubd or root hub init threads */ |
static int ehci_hc_reset (struct usb_hcd *hcd) |
{ |
struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
u32 temp; |
spin_lock_init (&ehci->lock); |
ehci->caps = (struct ehci_caps *) hcd->regs; |
ehci->regs = (struct ehci_regs *) (hcd->regs + |
readb (&ehci->caps->length)); |
dbg_hcs_params (ehci, "reset"); |
dbg_hcc_params (ehci, "reset"); |
/* EHCI 0.96 and later may have "extended capabilities" */ |
temp = HCC_EXT_CAPS (readl (&ehci->caps->hcc_params)); |
while (temp) { |
u32 cap; |
pci_read_config_dword (ehci->hcd.pdev, temp, &cap); |
ehci_dbg (ehci, "capability %04x at %02x\n", cap, temp); |
switch (cap & 0xff) { |
case 1: /* BIOS/SMM/... handoff */ |
if (bios_handoff (ehci, temp, cap) != 0) |
return -EOPNOTSUPP; |
break; |
case 0: /* illegal reserved capability */ |
ehci_warn (ehci, "illegal capability!\n"); |
cap = 0; |
/* FALLTHROUGH */ |
default: /* unknown */ |
break; |
} |
temp = (cap >> 8) & 0xff; |
} |
/* cache this readonly data; minimize PCI reads */ |
ehci->hcs_params = readl (&ehci->caps->hcs_params); |
/* force HC to halt state */ |
return ehci_halt (ehci); |
} |
static int ehci_start (struct usb_hcd *hcd) |
{ |
struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
u32 temp; |
struct usb_device *udev; |
struct usb_bus *bus; |
int retval; |
u32 hcc_params; |
u8 tempbyte; |
/* |
* hw default: 1K periodic list heads, one per frame. |
* periodic_size can shrink by USBCMD update if hcc_params allows. |
*/ |
ehci->periodic_size = DEFAULT_I_TDPS; |
if ((retval = ehci_mem_init (ehci, SLAB_KERNEL)) < 0) |
return retval; |
/* controllers may cache some of the periodic schedule ... */ |
hcc_params = readl (&ehci->caps->hcc_params); |
if (HCC_ISOC_CACHE (hcc_params)) // full frame cache |
ehci->i_thresh = 8; |
else // N microframes cached |
ehci->i_thresh = 2 + HCC_ISOC_THRES (hcc_params); |
ehci->reclaim = 0; |
ehci->next_uframe = -1; |
/* controller state: unknown --> reset */ |
/* EHCI spec section 4.1 */ |
if ((retval = ehci_reset (ehci)) != 0) { |
ehci_mem_cleanup (ehci); |
return retval; |
} |
writel (INTR_MASK, &ehci->regs->intr_enable); |
writel (ehci->periodic_dma, &ehci->regs->frame_list); |
/* |
* dedicate a qh for the async ring head, since we couldn't unlink |
* a 'real' qh without stopping the async schedule [4.8]. use it |
* as the 'reclamation list head' too. |
* its dummy is used in hw_alt_next of many tds, to prevent the qh |
* from automatically advancing to the next td after short reads. |
*/ |
ehci->async->qh_next.qh = 0; |
ehci->async->hw_next = QH_NEXT (ehci->async->qh_dma); |
ehci->async->hw_info1 = cpu_to_le32 (QH_HEAD); |
ehci->async->hw_token = cpu_to_le32 (QTD_STS_HALT); |
ehci->async->hw_qtd_next = EHCI_LIST_END; |
ehci->async->qh_state = QH_STATE_LINKED; |
ehci->async->hw_alt_next = QTD_NEXT (ehci->async->dummy->qtd_dma); |
writel ((u32)ehci->async->qh_dma, &ehci->regs->async_next); |
/* |
* hcc_params controls whether ehci->regs->segment must (!!!) |
* be used; it constrains QH/ITD/SITD and QTD locations. |
* pci_pool consistent memory always uses segment zero. |
* streaming mappings for I/O buffers, like pci_map_single(), |
* can return segments above 4GB, if the device allows. |
* |
* NOTE: the dma mask is visible through dma_supported(), so |
* drivers can pass this info along ... like NETIF_F_HIGHDMA, |
* Scsi_Host.highmem_io, and so forth. It's readonly to all |
* host side drivers though. |
*/ |
if (HCC_64BIT_ADDR (hcc_params)) { |
writel (0, &ehci->regs->segment); |
#if 0 |
// this is deeply broken on almost all architectures |
if (!pci_set_dma_mask (ehci->hcd.pdev, 0xffffffffffffffffULL)) |
ehci_info (ehci, "enabled 64bit PCI DMA\n"); |
#endif |
} |
/* help hc dma work well with cachelines */ |
pci_set_mwi (ehci->hcd.pdev); |
/* clear interrupt enables, set irq latency */ |
temp = readl (&ehci->regs->command) & 0x0fff; |
if (log2_irq_thresh < 0 || log2_irq_thresh > 6) |
log2_irq_thresh = 0; |
temp |= 1 << (16 + log2_irq_thresh); |
// if hc can park (ehci >= 0.96), default is 3 packets per async QH |
if (HCC_PGM_FRAMELISTLEN (hcc_params)) { |
/* periodic schedule size can be smaller than default */ |
temp &= ~(3 << 2); |
temp |= (EHCI_TUNE_FLS << 2); |
switch (EHCI_TUNE_FLS) { |
case 0: ehci->periodic_size = 1024; break; |
case 1: ehci->periodic_size = 512; break; |
case 2: ehci->periodic_size = 256; break; |
default: BUG (); |
} |
} |
temp &= ~(CMD_IAAD | CMD_ASE | CMD_PSE), |
// Philips, Intel, and maybe others need CMD_RUN before the |
// root hub will detect new devices (why?); NEC doesn't |
temp |= CMD_RUN; |
writel (temp, &ehci->regs->command); |
dbg_cmd (ehci, "init", temp); |
/* set async sleep time = 10 us ... ? */ |
init_timer (&ehci->watchdog); |
ehci->watchdog.function = ehci_watchdog; |
ehci->watchdog.data = (unsigned long) ehci; |
/* wire up the root hub */ |
bus = hcd_to_bus (hcd); |
bus->root_hub = udev = usb_alloc_dev (NULL, bus); |
if (!udev) { |
done2: |
ehci_mem_cleanup (ehci); |
return -ENOMEM; |
} |
/* |
* Start, enabling full USB 2.0 functionality ... usb 1.1 devices |
* are explicitly handed to companion controller(s), so no TT is |
* involved with the root hub. |
*/ |
ehci->reboot_notifier.notifier_call = ehci_reboot; |
register_reboot_notifier (&ehci->reboot_notifier); |
ehci->hcd.state = USB_STATE_RUNNING; |
writel (FLAG_CF, &ehci->regs->configured_flag); |
readl (&ehci->regs->command); /* unblock posted write */ |
/* PCI Serial Bus Release Number is at 0x60 offset */ |
pci_read_config_byte (hcd->pdev, 0x60, &tempbyte); |
temp = readw (&ehci->caps->hci_version); |
ehci_info (ehci, |
"USB %x.%x enabled, EHCI %x.%02x, driver %s\n", |
((tempbyte & 0xf0)>>4), (tempbyte & 0x0f), |
temp >> 8, temp & 0xff, DRIVER_VERSION); |
/* |
* From here on, khubd concurrently accesses the root |
* hub; drivers will be talking to enumerated devices. |
* |
* Before this point the HC was idle/ready. After, khubd |
* and device drivers may start it running. |
*/ |
udev->speed = USB_SPEED_HIGH; |
if (hcd_register_root (hcd) != 0) { |
if (hcd->state == USB_STATE_RUNNING) |
ehci_ready (ehci); |
ehci_reset (ehci); |
bus->root_hub = 0; |
usb_put_dev (udev); |
retval = -ENODEV; |
goto done2; |
} |
create_debug_files (ehci); |
return 0; |
} |
/* always called by thread; normally rmmod */ |
static void ehci_stop (struct usb_hcd *hcd) |
{ |
struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
ehci_dbg (ehci, "stop\n"); |
/* no more interrupts ... */ |
if (hcd->state == USB_STATE_RUNNING) |
ehci_ready (ehci); |
if (in_interrupt ()) { /* must not happen!! */ |
ehci_err (ehci, "stopped in_interrupt!\n"); |
return; |
} |
del_timer_sync (&ehci->watchdog); |
ehci_reset (ehci); |
/* let companion controllers work when we aren't */ |
writel (0, &ehci->regs->configured_flag); |
unregister_reboot_notifier (&ehci->reboot_notifier); |
remove_debug_files (ehci); |
/* root hub is shut down separately (first, when possible) */ |
spin_lock_irq (&ehci->lock); |
ehci_work (ehci, NULL); |
spin_unlock_irq (&ehci->lock); |
ehci_mem_cleanup (ehci); |
#ifdef EHCI_STATS |
ehci_dbg (ehci, "irq normal %ld err %ld reclaim %ld (lost %ld)\n", |
ehci->stats.normal, ehci->stats.error, ehci->stats.reclaim, |
ehci->stats.lost_iaa); |
ehci_dbg (ehci, "complete %ld unlink %ld\n", |
ehci->stats.complete, ehci->stats.unlink); |
#endif |
dbg_status (ehci, "ehci_stop completed", readl (&ehci->regs->status)); |
} |
static int ehci_get_frame (struct usb_hcd *hcd) |
{ |
struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
return (readl (&ehci->regs->frame_index) >> 3) % ehci->periodic_size; |
} |
/*-------------------------------------------------------------------------*/ |
#ifdef CONFIG_PM |
/* suspend/resume, section 4.3 */ |
static int ehci_suspend (struct usb_hcd *hcd, u32 state) |
{ |
struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
int ports; |
int i; |
ehci_dbg (ehci, "suspend to %d\n", state); |
ports = HCS_N_PORTS (ehci->hcs_params); |
// FIXME: This assumes what's probably a D3 level suspend... |
// FIXME: usb wakeup events on this bus should resume the machine. |
// pci config register PORTWAKECAP controls which ports can do it; |
// bios may have initted the register... |
/* suspend each port, then stop the hc */ |
for (i = 0; i < ports; i++) { |
int temp = readl (&ehci->regs->port_status [i]); |
if ((temp & PORT_PE) == 0 |
|| (temp & PORT_OWNER) != 0) |
continue; |
ehci_dbg (ehci, "suspend port %d", i); |
temp |= PORT_SUSPEND; |
writel (temp, &ehci->regs->port_status [i]); |
} |
if (hcd->state == USB_STATE_RUNNING) |
ehci_ready (ehci); |
writel (readl (&ehci->regs->command) & ~CMD_RUN, &ehci->regs->command); |
// save pci FLADJ value |
/* who tells PCI to reduce power consumption? */ |
return 0; |
} |
static int ehci_resume (struct usb_hcd *hcd) |
{ |
struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
int ports; |
int i; |
ehci_dbg (ehci, "resume\n"); |
ports = HCS_N_PORTS (ehci->hcs_params); |
// FIXME: if controller didn't retain state, |
// return and let generic code clean it up |
// test configured_flag ? |
/* resume HC and each port */ |
// restore pci FLADJ value |
// khubd and drivers will set HC running, if needed; |
hcd->state = USB_STATE_RUNNING; |
// FIXME Philips/Intel/... etc don't really have a "READY" |
// state ... turn on CMD_RUN too |
for (i = 0; i < ports; i++) { |
int temp = readl (&ehci->regs->port_status [i]); |
if ((temp & PORT_PE) == 0 |
|| (temp & PORT_SUSPEND) != 0) |
continue; |
ehci_dbg (ehci, "resume port %d", i); |
temp |= PORT_RESUME; |
writel (temp, &ehci->regs->port_status [i]); |
readl (&ehci->regs->command); /* unblock posted writes */ |
wait_ms (20); |
temp &= ~PORT_RESUME; |
writel (temp, &ehci->regs->port_status [i]); |
} |
readl (&ehci->regs->command); /* unblock posted writes */ |
return 0; |
} |
#endif |
/*-------------------------------------------------------------------------*/ |
/* |
* ehci_work is called from some interrupts, timers, and so on. |
* it calls driver completion functions, after dropping ehci->lock. |
*/ |
static void ehci_work (struct ehci_hcd *ehci, struct pt_regs *regs) |
{ |
timer_action_done (ehci, TIMER_IO_WATCHDOG); |
if (ehci->reclaim_ready) |
end_unlink_async (ehci, regs); |
scan_async (ehci, regs); |
if (ehci->next_uframe != -1) |
scan_periodic (ehci, regs); |
/* the IO watchdog guards against hardware or driver bugs that |
* misplace IRQs, and should let us run completely without IRQs. |
*/ |
if ((ehci->async->qh_next.ptr != 0) || (ehci->periodic_sched != 0)) |
timer_action (ehci, TIMER_IO_WATCHDOG); |
} |
/*-------------------------------------------------------------------------*/ |
static void ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs) |
{ |
struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
u32 status; |
int bh; |
spin_lock (&ehci->lock); |
status = readl (&ehci->regs->status); |
/* e.g. cardbus physical eject */ |
if (status == ~(u32) 0) { |
ehci_dbg (ehci, "device removed\n"); |
goto dead; |
} |
status &= INTR_MASK; |
if (!status) /* irq sharing? */ |
goto done; |
/* clear (just) interrupts */ |
writel (status, &ehci->regs->status); |
readl (&ehci->regs->command); /* unblock posted write */ |
bh = 0; |
#ifdef EHCI_VERBOSE_DEBUG |
/* unrequested/ignored: Port Change Detect, Frame List Rollover */ |
dbg_status (ehci, "irq", status); |
#endif |
/* INT, ERR, and IAA interrupt rates can be throttled */ |
/* normal [4.15.1.2] or error [4.15.1.1] completion */ |
if (likely ((status & (STS_INT|STS_ERR)) != 0)) { |
if (likely ((status & STS_ERR) == 0)) |
COUNT (ehci->stats.normal); |
else |
COUNT (ehci->stats.error); |
bh = 1; |
} |
/* complete the unlinking of some qh [4.15.2.3] */ |
if (status & STS_IAA) { |
COUNT (ehci->stats.reclaim); |
ehci->reclaim_ready = 1; |
bh = 1; |
} |
/* PCI errors [4.15.2.4] */ |
if (unlikely ((status & STS_FATAL) != 0)) { |
ehci_err (ehci, "fatal error\n"); |
dead: |
ehci_reset (ehci); |
/* generic layer kills/unlinks all urbs, then |
* uses ehci_stop to clean up the rest |
*/ |
bh = 1; |
} |
if (bh) |
ehci_work (ehci, regs); |
done: |
spin_unlock (&ehci->lock); |
} |
/*-------------------------------------------------------------------------*/ |
/* |
* non-error returns are a promise to giveback() the urb later |
* we drop ownership so next owner (or urb unlink) can get it |
* |
* urb + dev is in hcd_dev.urb_list |
* we're queueing TDs onto software and hardware lists |
* |
* hcd-specific init for hcpriv hasn't been done yet |
* |
* NOTE: control, bulk, and interrupt share the same code to append TDs |
* to a (possibly active) QH, and the same QH scanning code. |
*/ |
static int ehci_urb_enqueue ( |
struct usb_hcd *hcd, |
struct urb *urb, |
int mem_flags |
) { |
struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
struct list_head qtd_list; |
urb->transfer_flags &= ~EHCI_STATE_UNLINK; |
INIT_LIST_HEAD (&qtd_list); |
switch (usb_pipetype (urb->pipe)) { |
// case PIPE_CONTROL: |
// case PIPE_BULK: |
default: |
if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) |
return -ENOMEM; |
return submit_async (ehci, urb, &qtd_list, mem_flags); |
case PIPE_INTERRUPT: |
if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) |
return -ENOMEM; |
return intr_submit (ehci, urb, &qtd_list, mem_flags); |
case PIPE_ISOCHRONOUS: |
if (urb->dev->speed == USB_SPEED_HIGH) |
return itd_submit (ehci, urb, mem_flags); |
#ifdef have_split_iso |
else |
return sitd_submit (ehci, urb, mem_flags); |
#else |
dbg ("no split iso support yet"); |
return -ENOSYS; |
#endif /* have_split_iso */ |
} |
} |
/* remove from hardware lists |
* completions normally happen asynchronously |
*/ |
static int ehci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb) |
{ |
struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
struct ehci_qh *qh; |
unsigned long flags; |
spin_lock_irqsave (&ehci->lock, flags); |
switch (usb_pipetype (urb->pipe)) { |
// case PIPE_CONTROL: |
// case PIPE_BULK: |
default: |
qh = (struct ehci_qh *) urb->hcpriv; |
if (!qh) |
break; |
/* if we need to use IAA and it's busy, defer */ |
if (qh->qh_state == QH_STATE_LINKED |
&& ehci->reclaim |
&& HCD_IS_RUNNING (ehci->hcd.state) |
) { |
struct ehci_qh *last; |
for (last = ehci->reclaim; |
last->reclaim; |
last = last->reclaim) |
continue; |
qh->qh_state = QH_STATE_UNLINK_WAIT; |
last->reclaim = qh; |
/* bypass IAA if the hc can't care */ |
} else if (!HCD_IS_RUNNING (ehci->hcd.state) && ehci->reclaim) |
end_unlink_async (ehci, NULL); |
/* something else might have unlinked the qh by now */ |
if (qh->qh_state == QH_STATE_LINKED) |
start_unlink_async (ehci, qh); |
break; |
case PIPE_INTERRUPT: |
qh = (struct ehci_qh *) urb->hcpriv; |
if (!qh) |
break; |
if (qh->qh_state == QH_STATE_LINKED) { |
/* messy, can spin or block a microframe ... */ |
intr_deschedule (ehci, qh, 1); |
/* qh_state == IDLE */ |
} |
qh_completions (ehci, qh, NULL); |
/* reschedule QH iff another request is queued */ |
if (!list_empty (&qh->qtd_list) |
&& HCD_IS_RUNNING (ehci->hcd.state)) { |
int status; |
status = qh_schedule (ehci, qh); |
spin_unlock_irqrestore (&ehci->lock, flags); |
if (status != 0) { |
// shouldn't happen often, but ... |
// FIXME kill those tds' urbs |
err ("can't reschedule qh %p, err %d", |
qh, status); |
} |
return status; |
} |
break; |
case PIPE_ISOCHRONOUS: |
// itd or sitd ... |
// wait till next completion, do it then. |
// completion irqs can wait up to 1024 msec, |
urb->transfer_flags |= EHCI_STATE_UNLINK; |
break; |
} |
spin_unlock_irqrestore (&ehci->lock, flags); |
return 0; |
} |
/*-------------------------------------------------------------------------*/ |
// bulk qh holds the data toggle |
static void |
ehci_endpoint_disable (struct usb_hcd *hcd, struct hcd_dev *dev, int ep) |
{ |
struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
int epnum; |
unsigned long flags; |
struct ehci_qh *qh; |
/* ASSERT: any requests/urbs are being unlinked */ |
/* ASSERT: nobody can be submitting urbs for this any more */ |
epnum = ep & USB_ENDPOINT_NUMBER_MASK; |
if (epnum != 0 && (ep & USB_DIR_IN)) |
epnum |= 0x10; |
rescan: |
spin_lock_irqsave (&ehci->lock, flags); |
qh = (struct ehci_qh *) dev->ep [epnum]; |
if (!qh) |
goto done; |
if (!HCD_IS_RUNNING (ehci->hcd.state)) |
qh->qh_state = QH_STATE_IDLE; |
switch (qh->qh_state) { |
case QH_STATE_UNLINK: /* wait for hw to finish? */ |
spin_unlock_irqrestore (&ehci->lock, flags); |
set_current_state (TASK_UNINTERRUPTIBLE); |
schedule_timeout (1); |
goto rescan; |
case QH_STATE_IDLE: /* fully unlinked */ |
if (list_empty (&qh->qtd_list)) { |
qh_put (ehci, qh); |
break; |
} |
/* else FALL THROUGH */ |
default: |
/* caller was supposed to have unlinked any requests; |
* that's not our job. just leak this memory. |
*/ |
ehci_err (ehci, "qh %p (#%d) state %d%s\n", |
qh, epnum, qh->qh_state, |
list_empty (&qh->qtd_list) ? "" : "(has tds)"); |
break; |
} |
dev->ep [epnum] = 0; |
done: |
spin_unlock_irqrestore (&ehci->lock, flags); |
return; |
} |
/*-------------------------------------------------------------------------*/ |
static const struct hc_driver ehci_driver = { |
.description = hcd_name, |
/* |
* generic hardware linkage |
*/ |
.irq = ehci_irq, |
.flags = HCD_MEMORY | HCD_USB2, |
/* |
* basic lifecycle operations |
*/ |
.reset = ehci_hc_reset, |
.start = ehci_start, |
#ifdef CONFIG_PM |
.suspend = ehci_suspend, |
.resume = ehci_resume, |
#endif |
.stop = ehci_stop, |
/* |
* memory lifecycle (except per-request) |
*/ |
.hcd_alloc = ehci_hcd_alloc, |
.hcd_free = ehci_hcd_free, |
/* |
* managing i/o requests and associated device resources |
*/ |
.urb_enqueue = ehci_urb_enqueue, |
.urb_dequeue = ehci_urb_dequeue, |
.endpoint_disable = ehci_endpoint_disable, |
/* |
* scheduling support |
*/ |
.get_frame_number = ehci_get_frame, |
/* |
* root hub support |
*/ |
.hub_status_data = ehci_hub_status_data, |
.hub_control = ehci_hub_control, |
}; |
/*-------------------------------------------------------------------------*/ |
/* EHCI spec says PCI is required. */ |
/* PCI driver selection metadata; PCI hotplugging uses this */ |
static const struct pci_device_id pci_ids [] = { { |
/* handle any USB 2.0 EHCI controller */ |
PCI_DEVICE_CLASS(((PCI_CLASS_SERIAL_USB << 8) | 0x20), ~0), |
.driver_data = (unsigned long) &ehci_driver, |
}, |
{ /* end: all zeroes */ } |
}; |
MODULE_DEVICE_TABLE (pci, pci_ids); |
/* pci driver glue; this is a "new style" PCI driver module */ |
static struct pci_driver ehci_pci_driver = { |
.name = (char *) hcd_name, |
.id_table = pci_ids, |
.probe = usb_hcd_pci_probe, |
.remove = usb_hcd_pci_remove, |
#ifdef CONFIG_PM |
.suspend = usb_hcd_pci_suspend, |
.resume = usb_hcd_pci_resume, |
#endif |
}; |
#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC |
MODULE_DESCRIPTION (DRIVER_INFO); |
MODULE_AUTHOR (DRIVER_AUTHOR); |
MODULE_LICENSE ("GPL"); |
static int __init init (void) |
{ |
if (usb_disabled()) |
return -ENODEV; |
pr_debug ("%s: block sizes: qh %Zd qtd %Zd itd %Zd sitd %Zd\n", |
hcd_name, |
sizeof (struct ehci_qh), sizeof (struct ehci_qtd), |
sizeof (struct ehci_itd), sizeof (struct ehci_sitd)); |
return pci_module_init (&ehci_pci_driver); |
} |
module_init (init); |
static void __exit cleanup (void) |
{ |
pci_unregister_driver (&ehci_pci_driver); |
} |
module_exit (cleanup); |
/shark/trunk/drivers/usb/host/ehci-q.c |
---|
0,0 → 1,1070 |
/* |
* Copyright (c) 2001-2002 by David Brownell |
* |
* 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. |
*/ |
/* this file is part of ehci-hcd.c */ |
/*-------------------------------------------------------------------------*/ |
/* |
* EHCI hardware queue manipulation ... the core. QH/QTD manipulation. |
* |
* Control, bulk, and interrupt traffic all use "qh" lists. They list "qtd" |
* entries describing USB transactions, max 16-20kB/entry (with 4kB-aligned |
* buffers needed for the larger number). We use one QH per endpoint, queue |
* multiple urbs (all three types) per endpoint. URBs may need several qtds. |
* |
* ISO traffic uses "ISO TD" (itd, and sitd) records, and (along with |
* interrupts) needs careful scheduling. Performance improvements can be |
* an ongoing challenge. That's in "ehci-sched.c". |
* |
* USB 1.1 devices are handled (a) by "companion" OHCI or UHCI root hubs, |
* or otherwise through transaction translators (TTs) in USB 2.0 hubs using |
* (b) special fields in qh entries or (c) split iso entries. TTs will |
* buffer low/full speed data so the host collects it at high speed. |
*/ |
/*-------------------------------------------------------------------------*/ |
/* fill a qtd, returning how much of the buffer we were able to queue up */ |
static int |
qtd_fill (struct ehci_qtd *qtd, dma_addr_t buf, size_t len, |
int token, int maxpacket) |
{ |
int i, count; |
u64 addr = buf; |
/* one buffer entry per 4K ... first might be short or unaligned */ |
qtd->hw_buf [0] = cpu_to_le32 ((u32)addr); |
qtd->hw_buf_hi [0] = cpu_to_le32 ((u32)(addr >> 32)); |
count = 0x1000 - (buf & 0x0fff); /* rest of that page */ |
if (likely (len < count)) /* ... iff needed */ |
count = len; |
else { |
buf += 0x1000; |
buf &= ~0x0fff; |
/* per-qtd limit: from 16K to 20K (best alignment) */ |
for (i = 1; count < len && i < 5; i++) { |
addr = buf; |
qtd->hw_buf [i] = cpu_to_le32 ((u32)addr); |
qtd->hw_buf_hi [i] = cpu_to_le32 ((u32)(addr >> 32)); |
buf += 0x1000; |
if ((count + 0x1000) < len) |
count += 0x1000; |
else |
count = len; |
} |
/* short packets may only terminate transfers */ |
if (count != len) |
count -= (count % maxpacket); |
} |
qtd->hw_token = cpu_to_le32 ((count << 16) | token); |
qtd->length = count; |
return count; |
} |
/*-------------------------------------------------------------------------*/ |
/* update halted (but potentially linked) qh */ |
static inline void |
qh_update (struct ehci_hcd *ehci, struct ehci_qh *qh, struct ehci_qtd *qtd) |
{ |
qh->hw_qtd_next = QTD_NEXT (qtd->qtd_dma); |
qh->hw_alt_next = EHCI_LIST_END; |
/* HC must see latest qtd and qh data before we clear ACTIVE+HALT */ |
wmb (); |
qh->hw_token &= __constant_cpu_to_le32 (QTD_TOGGLE | QTD_STS_PING); |
} |
/*-------------------------------------------------------------------------*/ |
static void qtd_copy_status ( |
struct ehci_hcd *ehci, |
struct urb *urb, |
size_t length, |
u32 token |
) |
{ |
/* count IN/OUT bytes, not SETUP (even short packets) */ |
if (likely (QTD_PID (token) != 2)) |
urb->actual_length += length - QTD_LENGTH (token); |
/* don't modify error codes */ |
if (unlikely (urb->status != -EINPROGRESS)) |
return; |
/* force cleanup after short read; not always an error */ |
if (unlikely (IS_SHORT_READ (token))) |
urb->status = -EREMOTEIO; |
/* serious "can't proceed" faults reported by the hardware */ |
if (token & QTD_STS_HALT) { |
if (token & QTD_STS_BABBLE) { |
/* FIXME "must" disable babbling device's port too */ |
urb->status = -EOVERFLOW; |
} else if (token & QTD_STS_MMF) { |
/* fs/ls interrupt xfer missed the complete-split */ |
urb->status = -EPROTO; |
} else if (token & QTD_STS_DBE) { |
urb->status = (QTD_PID (token) == 1) /* IN ? */ |
? -ENOSR /* hc couldn't read data */ |
: -ECOMM; /* hc couldn't write data */ |
} else if (token & QTD_STS_XACT) { |
/* timeout, bad crc, wrong PID, etc; retried */ |
if (QTD_CERR (token)) |
urb->status = -EPIPE; |
else { |
ehci_dbg (ehci, "devpath %s ep%d%s 3strikes\n", |
urb->dev->devpath, |
usb_pipeendpoint (urb->pipe), |
usb_pipein (urb->pipe) ? "in" : "out"); |
urb->status = -EPROTO; |
} |
/* CERR nonzero + no errors + halt --> stall */ |
} else if (QTD_CERR (token)) |
urb->status = -EPIPE; |
else /* unknown */ |
urb->status = -EPROTO; |
ehci_vdbg (ehci, |
"dev%d ep%d%s qtd token %08x --> status %d\n", |
usb_pipedevice (urb->pipe), |
usb_pipeendpoint (urb->pipe), |
usb_pipein (urb->pipe) ? "in" : "out", |
token, urb->status); |
/* stall indicates some recovery action is needed */ |
if (urb->status == -EPIPE) { |
int pipe = urb->pipe; |
if (!usb_pipecontrol (pipe)) |
usb_endpoint_halt (urb->dev, |
usb_pipeendpoint (pipe), |
usb_pipeout (pipe)); |
/* if async CSPLIT failed, try cleaning out the TT buffer */ |
} else if (urb->dev->tt && !usb_pipeint (urb->pipe) |
&& QTD_CERR(token) == 0) { |
#ifdef DEBUG |
struct usb_device *tt = urb->dev->tt->hub; |
dev_dbg (&tt->dev, |
"clear tt buffer port %d, a%d ep%d t%08x\n", |
urb->dev->ttport, urb->dev->devnum, |
usb_pipeendpoint (urb->pipe), token); |
#endif /* DEBUG */ |
usb_hub_tt_clear_buffer (urb->dev, urb->pipe); |
} |
} |
} |
static void |
ehci_urb_done (struct ehci_hcd *ehci, struct urb *urb, struct pt_regs *regs) |
{ |
if (likely (urb->hcpriv != 0)) { |
struct ehci_qh *qh = (struct ehci_qh *) urb->hcpriv; |
/* S-mask in a QH means it's an interrupt urb */ |
if ((qh->hw_info2 & __constant_cpu_to_le32 (0x00ff)) != 0) { |
/* ... update hc-wide periodic stats (for usbfs) */ |
hcd_to_bus (&ehci->hcd)->bandwidth_int_reqs--; |
} |
qh_put (ehci, qh); |
} |
spin_lock (&urb->lock); |
urb->hcpriv = 0; |
switch (urb->status) { |
case -EINPROGRESS: /* success */ |
urb->status = 0; |
default: /* fault */ |
COUNT (ehci->stats.complete); |
break; |
case -EREMOTEIO: /* fault or normal */ |
if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) |
urb->status = 0; |
COUNT (ehci->stats.complete); |
break; |
case -ECONNRESET: /* canceled */ |
case -ENOENT: |
COUNT (ehci->stats.unlink); |
break; |
} |
spin_unlock (&urb->lock); |
/* complete() can reenter this HCD */ |
spin_unlock (&ehci->lock); |
usb_hcd_giveback_urb (&ehci->hcd, urb, regs); |
spin_lock (&ehci->lock); |
} |
/* |
* Process and free completed qtds for a qh, returning URBs to drivers. |
* Chases up to qh->hw_current. Returns number of completions called, |
* indicating how much "real" work we did. |
*/ |
#define HALT_BIT __constant_cpu_to_le32(QTD_STS_HALT) |
static unsigned |
qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh, struct pt_regs *regs) |
{ |
struct ehci_qtd *last = 0, *end = qh->dummy; |
struct list_head *entry, *tmp; |
int stopped; |
unsigned count = 0; |
int do_status = 0; |
u8 state; |
if (unlikely (list_empty (&qh->qtd_list))) |
return count; |
/* completions (or tasks on other cpus) must never clobber HALT |
* till we've gone through and cleaned everything up, even when |
* they add urbs to this qh's queue or mark them for unlinking. |
* |
* NOTE: unlinking expects to be done in queue order. |
*/ |
state = qh->qh_state; |
qh->qh_state = QH_STATE_COMPLETING; |
stopped = (state == QH_STATE_IDLE); |
/* remove de-activated QTDs from front of queue. |
* after faults (including short reads), cleanup this urb |
* then let the queue advance. |
* if queue is stopped, handles unlinks. |
*/ |
list_for_each_safe (entry, tmp, &qh->qtd_list) { |
struct ehci_qtd *qtd; |
struct urb *urb; |
u32 token = 0; |
qtd = list_entry (entry, struct ehci_qtd, qtd_list); |
urb = qtd->urb; |
/* clean up any state from previous QTD ...*/ |
if (last) { |
if (likely (last->urb != urb)) { |
ehci_urb_done (ehci, last->urb, regs); |
count++; |
} |
ehci_qtd_free (ehci, last); |
last = 0; |
} |
/* ignore urbs submitted during completions we reported */ |
if (qtd == end) |
break; |
/* hardware copies qtd out of qh overlay */ |
rmb (); |
token = le32_to_cpu (qtd->hw_token); |
/* always clean up qtds the hc de-activated */ |
if ((token & QTD_STS_ACTIVE) == 0) { |
if ((token & QTD_STS_HALT) != 0) { |
stopped = 1; |
/* magic dummy for some short reads; qh won't advance */ |
} else if (IS_SHORT_READ (token) |
&& (qh->hw_alt_next & QTD_MASK) |
== ehci->async->hw_alt_next) { |
stopped = 1; |
goto halt; |
} |
/* stop scanning when we reach qtds the hc is using */ |
} else if (likely (!stopped |
&& HCD_IS_RUNNING (ehci->hcd.state))) { |
break; |
} else { |
stopped = 1; |
/* ignore active urbs unless some previous qtd |
* for the urb faulted (including short read) or |
* its urb was canceled. we may patch qh or qtds. |
*/ |
if (likely (urb->status == -EINPROGRESS)) |
continue; |
/* issue status after short control reads */ |
if (unlikely (do_status != 0) |
&& QTD_PID (token) == 0 /* OUT */) { |
do_status = 0; |
continue; |
} |
/* token in overlay may be most current */ |
if (state == QH_STATE_IDLE |
&& cpu_to_le32 (qtd->qtd_dma) |
== qh->hw_current) |
token = le32_to_cpu (qh->hw_token); |
/* force halt for unlinked or blocked qh, so we'll |
* patch the qh later and so that completions can't |
* activate it while we "know" it's stopped. |
*/ |
if ((HALT_BIT & qh->hw_token) == 0) { |
halt: |
qh->hw_token |= HALT_BIT; |
wmb (); |
} |
} |
/* remove it from the queue */ |
spin_lock (&urb->lock); |
qtd_copy_status (ehci, urb, qtd->length, token); |
do_status = (urb->status == -EREMOTEIO) |
&& usb_pipecontrol (urb->pipe); |
spin_unlock (&urb->lock); |
if (stopped && qtd->qtd_list.prev != &qh->qtd_list) { |
last = list_entry (qtd->qtd_list.prev, |
struct ehci_qtd, qtd_list); |
last->hw_next = qtd->hw_next; |
} |
list_del (&qtd->qtd_list); |
last = qtd; |
} |
/* last urb's completion might still need calling */ |
if (likely (last != 0)) { |
ehci_urb_done (ehci, last->urb, regs); |
count++; |
ehci_qtd_free (ehci, last); |
} |
/* restore original state; caller must unlink or relink */ |
qh->qh_state = state; |
/* update qh after fault cleanup */ |
if (unlikely (stopped != 0) |
/* some EHCI 0.95 impls will overlay dummy qtds */ |
|| qh->hw_qtd_next == EHCI_LIST_END) { |
if (list_empty (&qh->qtd_list)) |
end = qh->dummy; |
else { |
end = list_entry (qh->qtd_list.next, |
struct ehci_qtd, qtd_list); |
/* first qtd may already be partially processed */ |
if (cpu_to_le32 (end->qtd_dma) == qh->hw_current) |
end = 0; |
} |
if (end) |
qh_update (ehci, qh, end); |
} |
return count; |
} |
/*-------------------------------------------------------------------------*/ |
// high bandwidth multiplier, as encoded in highspeed endpoint descriptors |
#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) |
// ... and packet size, for any kind of endpoint descriptor |
#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) |
/* |
* reverse of qh_urb_transaction: free a list of TDs. |
* used for cleanup after errors, before HC sees an URB's TDs. |
*/ |
static void qtd_list_free ( |
struct ehci_hcd *ehci, |
struct urb *urb, |
struct list_head *qtd_list |
) { |
struct list_head *entry, *temp; |
list_for_each_safe (entry, temp, qtd_list) { |
struct ehci_qtd *qtd; |
qtd = list_entry (entry, struct ehci_qtd, qtd_list); |
list_del (&qtd->qtd_list); |
ehci_qtd_free (ehci, qtd); |
} |
} |
/* |
* create a list of filled qtds for this URB; won't link into qh. |
*/ |
static struct list_head * |
qh_urb_transaction ( |
struct ehci_hcd *ehci, |
struct urb *urb, |
struct list_head *head, |
int flags |
) { |
struct ehci_qtd *qtd, *qtd_prev; |
dma_addr_t buf; |
int len, maxpacket; |
int is_input; |
u32 token; |
/* |
* URBs map to sequences of QTDs: one logical transaction |
*/ |
qtd = ehci_qtd_alloc (ehci, flags); |
if (unlikely (!qtd)) |
return 0; |
list_add_tail (&qtd->qtd_list, head); |
qtd->urb = urb; |
token = QTD_STS_ACTIVE; |
token |= (EHCI_TUNE_CERR << 10); |
/* for split transactions, SplitXState initialized to zero */ |
len = urb->transfer_buffer_length; |
is_input = usb_pipein (urb->pipe); |
if (usb_pipecontrol (urb->pipe)) { |
/* SETUP pid */ |
qtd_fill (qtd, urb->setup_dma, sizeof (struct usb_ctrlrequest), |
token | (2 /* "setup" */ << 8), 8); |
/* ... and always at least one more pid */ |
token ^= QTD_TOGGLE; |
qtd_prev = qtd; |
qtd = ehci_qtd_alloc (ehci, flags); |
if (unlikely (!qtd)) |
goto cleanup; |
qtd->urb = urb; |
qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma); |
list_add_tail (&qtd->qtd_list, head); |
} |
/* |
* data transfer stage: buffer setup |
*/ |
if (likely (len > 0)) |
buf = urb->transfer_dma; |
else |
buf = 0; |
// FIXME this 'buf' check break some zlps... |
if (!buf || is_input) |
token |= (1 /* "in" */ << 8); |
/* else it's already initted to "out" pid (0 << 8) */ |
maxpacket = max_packet(usb_maxpacket(urb->dev, urb->pipe, !is_input)); |
/* |
* buffer gets wrapped in one or more qtds; |
* last one may be "short" (including zero len) |
* and may serve as a control status ack |
*/ |
for (;;) { |
int this_qtd_len; |
this_qtd_len = qtd_fill (qtd, buf, len, token, maxpacket); |
len -= this_qtd_len; |
buf += this_qtd_len; |
if (is_input) |
qtd->hw_alt_next = ehci->async->hw_alt_next; |
/* qh makes control packets use qtd toggle; maybe switch it */ |
if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0) |
token ^= QTD_TOGGLE; |
if (likely (len <= 0)) |
break; |
qtd_prev = qtd; |
qtd = ehci_qtd_alloc (ehci, flags); |
if (unlikely (!qtd)) |
goto cleanup; |
qtd->urb = urb; |
qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma); |
list_add_tail (&qtd->qtd_list, head); |
} |
/* unless the bulk/interrupt caller wants a chance to clean |
* up after short reads, hc should advance qh past this urb |
*/ |
if (likely ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0 |
|| usb_pipecontrol (urb->pipe))) |
qtd->hw_alt_next = EHCI_LIST_END; |
/* |
* control requests may need a terminating data "status" ack; |
* bulk ones may need a terminating short packet (zero length). |
*/ |
if (likely (buf != 0)) { |
int one_more = 0; |
if (usb_pipecontrol (urb->pipe)) { |
one_more = 1; |
token ^= 0x0100; /* "in" <--> "out" */ |
token |= QTD_TOGGLE; /* force DATA1 */ |
} else if (usb_pipebulk (urb->pipe) |
&& (urb->transfer_flags & URB_ZERO_PACKET) |
&& !(urb->transfer_buffer_length % maxpacket)) { |
one_more = 1; |
} |
if (one_more) { |
qtd_prev = qtd; |
qtd = ehci_qtd_alloc (ehci, flags); |
if (unlikely (!qtd)) |
goto cleanup; |
qtd->urb = urb; |
qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma); |
list_add_tail (&qtd->qtd_list, head); |
/* never any data in such packets */ |
qtd_fill (qtd, 0, 0, token, 0); |
} |
} |
/* by default, enable interrupt on urb completion */ |
if (likely (!(urb->transfer_flags & URB_NO_INTERRUPT))) |
qtd->hw_token |= __constant_cpu_to_le32 (QTD_IOC); |
return head; |
cleanup: |
qtd_list_free (ehci, urb, head); |
return 0; |
} |
/*-------------------------------------------------------------------------*/ |
/* |
* Hardware maintains data toggle (like OHCI) ... here we (re)initialize |
* the hardware data toggle in the QH, and set the pseudo-toggle in udev |
* so we can see if usb_clear_halt() was called. NOP for control, since |
* we set up qh->hw_info1 to always use the QTD toggle bits. |
*/ |
static inline void |
clear_toggle (struct usb_device *udev, int ep, int is_out, struct ehci_qh *qh) |
{ |
vdbg ("clear toggle, dev %d ep 0x%x-%s", |
udev->devnum, ep, is_out ? "out" : "in"); |
qh->hw_token &= ~__constant_cpu_to_le32 (QTD_TOGGLE); |
usb_settoggle (udev, ep, is_out, 1); |
} |
// Would be best to create all qh's from config descriptors, |
// when each interface/altsetting is established. Unlink |
// any previous qh and cancel its urbs first; endpoints are |
// implicitly reset then (data toggle too). |
// That'd mean updating how usbcore talks to HCDs. (2.5?) |
/* |
* Each QH holds a qtd list; a QH is used for everything except iso. |
* |
* For interrupt urbs, the scheduler must set the microframe scheduling |
* mask(s) each time the QH gets scheduled. For highspeed, that's |
* just one microframe in the s-mask. For split interrupt transactions |
* there are additional complications: c-mask, maybe FSTNs. |
*/ |
static struct ehci_qh * |
qh_make ( |
struct ehci_hcd *ehci, |
struct urb *urb, |
int flags |
) { |
struct ehci_qh *qh = ehci_qh_alloc (ehci, flags); |
u32 info1 = 0, info2 = 0; |
int is_input, type; |
int maxp = 0; |
if (!qh) |
return qh; |
/* |
* init endpoint/device data for this QH |
*/ |
info1 |= usb_pipeendpoint (urb->pipe) << 8; |
info1 |= usb_pipedevice (urb->pipe) << 0; |
is_input = usb_pipein (urb->pipe); |
type = usb_pipetype (urb->pipe); |
maxp = usb_maxpacket (urb->dev, urb->pipe, !is_input); |
/* Compute interrupt scheduling parameters just once, and save. |
* - allowing for high bandwidth, how many nsec/uframe are used? |
* - split transactions need a second CSPLIT uframe; same question |
* - splits also need a schedule gap (for full/low speed I/O) |
* - qh has a polling interval |
* |
* For control/bulk requests, the HC or TT handles these. |
*/ |
if (type == PIPE_INTERRUPT) { |
qh->usecs = usb_calc_bus_time (USB_SPEED_HIGH, is_input, 0, |
hb_mult (maxp) * max_packet (maxp)); |
qh->start = NO_FRAME; |
if (urb->dev->speed == USB_SPEED_HIGH) { |
qh->c_usecs = 0; |
qh->gap_uf = 0; |
/* FIXME handle HS periods of less than 1 frame. */ |
qh->period = urb->interval >> 3; |
if (qh->period < 1) { |
dbg ("intr period %d uframes, NYET!", |
urb->interval); |
goto done; |
} |
} else { |
/* gap is f(FS/LS transfer times) */ |
qh->gap_uf = 1 + usb_calc_bus_time (urb->dev->speed, |
is_input, 0, maxp) / (125 * 1000); |
/* FIXME this just approximates SPLIT/CSPLIT times */ |
if (is_input) { // SPLIT, gap, CSPLIT+DATA |
qh->c_usecs = qh->usecs + HS_USECS (0); |
qh->usecs = HS_USECS (1); |
} else { // SPLIT+DATA, gap, CSPLIT |
qh->usecs += HS_USECS (1); |
qh->c_usecs = HS_USECS (0); |
} |
qh->period = urb->interval; |
} |
} |
/* using TT? */ |
switch (urb->dev->speed) { |
case USB_SPEED_LOW: |
info1 |= (1 << 12); /* EPS "low" */ |
/* FALL THROUGH */ |
case USB_SPEED_FULL: |
/* EPS 0 means "full" */ |
if (type != PIPE_INTERRUPT) |
info1 |= (EHCI_TUNE_RL_TT << 28); |
if (type == PIPE_CONTROL) { |
info1 |= (1 << 27); /* for TT */ |
info1 |= 1 << 14; /* toggle from qtd */ |
} |
info1 |= maxp << 16; |
info2 |= (EHCI_TUNE_MULT_TT << 30); |
info2 |= urb->dev->ttport << 23; |
info2 |= urb->dev->tt->hub->devnum << 16; |
/* NOTE: if (PIPE_INTERRUPT) { scheduler sets c-mask } */ |
break; |
case USB_SPEED_HIGH: /* no TT involved */ |
info1 |= (2 << 12); /* EPS "high" */ |
if (type == PIPE_CONTROL) { |
info1 |= (EHCI_TUNE_RL_HS << 28); |
info1 |= 64 << 16; /* usb2 fixed maxpacket */ |
info1 |= 1 << 14; /* toggle from qtd */ |
info2 |= (EHCI_TUNE_MULT_HS << 30); |
} else if (type == PIPE_BULK) { |
info1 |= (EHCI_TUNE_RL_HS << 28); |
info1 |= 512 << 16; /* usb2 fixed maxpacket */ |
info2 |= (EHCI_TUNE_MULT_HS << 30); |
} else { /* PIPE_INTERRUPT */ |
info1 |= max_packet (maxp) << 16; |
info2 |= hb_mult (maxp) << 30; |
} |
break; |
default: |
dbg ("bogus dev %p speed %d", urb->dev, urb->dev->speed); |
done: |
qh_put (ehci, qh); |
return 0; |
} |
/* NOTE: if (PIPE_INTERRUPT) { scheduler sets s-mask } */ |
/* init as live, toggle clear, advance to dummy */ |
qh->qh_state = QH_STATE_IDLE; |
qh->hw_info1 = cpu_to_le32 (info1); |
qh->hw_info2 = cpu_to_le32 (info2); |
qh_update (ehci, qh, qh->dummy); |
usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), !is_input, 1); |
return qh; |
} |
#undef hb_mult |
#undef hb_packet |
/*-------------------------------------------------------------------------*/ |
/* move qh (and its qtds) onto async queue; maybe enable queue. */ |
static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) |
{ |
u32 dma = QH_NEXT (qh->qh_dma); |
struct ehci_qh *head; |
/* (re)start the async schedule? */ |
head = ehci->async; |
timer_action_done (ehci, TIMER_ASYNC_OFF); |
if (!head->qh_next.qh) { |
u32 cmd = readl (&ehci->regs->command); |
if (!(cmd & CMD_ASE)) { |
/* in case a clear of CMD_ASE didn't take yet */ |
(void) handshake (&ehci->regs->status, STS_ASS, 0, 150); |
cmd |= CMD_ASE | CMD_RUN; |
writel (cmd, &ehci->regs->command); |
ehci->hcd.state = USB_STATE_RUNNING; |
/* posted write need not be known to HC yet ... */ |
} |
} |
qh->hw_token &= ~HALT_BIT; |
/* splice right after start */ |
qh->qh_next = head->qh_next; |
qh->hw_next = head->hw_next; |
wmb (); |
head->qh_next.qh = qh; |
head->hw_next = dma; |
qh->qh_state = QH_STATE_LINKED; |
/* qtd completions reported later by interrupt */ |
} |
/*-------------------------------------------------------------------------*/ |
#define QH_ADDR_MASK __constant_le32_to_cpu(0x7f) |
/* |
* For control/bulk/interrupt, return QH with these TDs appended. |
* Allocates and initializes the QH if necessary. |
* Returns null if it can't allocate a QH it needs to. |
* If the QH has TDs (urbs) already, that's great. |
*/ |
static struct ehci_qh *qh_append_tds ( |
struct ehci_hcd *ehci, |
struct urb *urb, |
struct list_head *qtd_list, |
int epnum, |
void **ptr |
) |
{ |
struct ehci_qh *qh = 0; |
qh = (struct ehci_qh *) *ptr; |
if (unlikely (qh == 0)) { |
/* can't sleep here, we have ehci->lock... */ |
qh = qh_make (ehci, urb, SLAB_ATOMIC); |
*ptr = qh; |
} |
if (likely (qh != 0)) { |
struct ehci_qtd *qtd; |
if (unlikely (list_empty (qtd_list))) |
qtd = 0; |
else |
qtd = list_entry (qtd_list->next, struct ehci_qtd, |
qtd_list); |
/* control qh may need patching after enumeration */ |
if (unlikely (epnum == 0)) { |
/* set_address changes the address */ |
if ((qh->hw_info1 & QH_ADDR_MASK) == 0) |
qh->hw_info1 |= cpu_to_le32 ( |
usb_pipedevice (urb->pipe)); |
/* for full speed, ep0 maxpacket can grow */ |
else if (!(qh->hw_info1 |
& __constant_cpu_to_le32 (0x3 << 12))) { |
u32 info, max; |
info = le32_to_cpu (qh->hw_info1); |
max = urb->dev->descriptor.bMaxPacketSize0; |
if (max > (0x07ff & (info >> 16))) { |
info &= ~(0x07ff << 16); |
info |= max << 16; |
qh->hw_info1 = cpu_to_le32 (info); |
} |
} |
/* usb_reset_device() briefly reverts to address 0 */ |
if (usb_pipedevice (urb->pipe) == 0) |
qh->hw_info1 &= ~QH_ADDR_MASK; |
} |
/* usb_clear_halt() means qh data toggle gets reset */ |
if (unlikely (!usb_gettoggle (urb->dev, |
(epnum & 0x0f), !(epnum & 0x10))) |
&& !usb_pipecontrol (urb->pipe)) { |
/* "never happens": drivers do stall cleanup right */ |
if (qh->qh_state != QH_STATE_IDLE |
&& !list_empty (&qh->qtd_list) |
&& qh->qh_state != QH_STATE_COMPLETING) |
ehci_warn (ehci, "clear toggle dev%d " |
"ep%d%s: not idle\n", |
usb_pipedevice (urb->pipe), |
epnum & 0x0f, |
usb_pipein (urb->pipe) |
? "in" : "out"); |
/* else we know this overlay write is safe */ |
clear_toggle (urb->dev, |
epnum & 0x0f, !(epnum & 0x10), qh); |
} |
/* just one way to queue requests: swap with the dummy qtd. |
* only hc or qh_completions() usually modify the overlay. |
*/ |
if (likely (qtd != 0)) { |
struct ehci_qtd *dummy; |
dma_addr_t dma; |
u32 token; |
/* to avoid racing the HC, use the dummy td instead of |
* the first td of our list (becomes new dummy). both |
* tds stay deactivated until we're done, when the |
* HC is allowed to fetch the old dummy (4.10.2). |
*/ |
token = qtd->hw_token; |
qtd->hw_token = HALT_BIT; |
wmb (); |
dummy = qh->dummy; |
dma = dummy->qtd_dma; |
*dummy = *qtd; |
dummy->qtd_dma = dma; |
list_del (&qtd->qtd_list); |
list_add (&dummy->qtd_list, qtd_list); |
__list_splice (qtd_list, qh->qtd_list.prev); |
ehci_qtd_init (qtd, qtd->qtd_dma); |
qh->dummy = qtd; |
/* hc must see the new dummy at list end */ |
dma = qtd->qtd_dma; |
qtd = list_entry (qh->qtd_list.prev, |
struct ehci_qtd, qtd_list); |
qtd->hw_next = QTD_NEXT (dma); |
/* let the hc process these next qtds */ |
wmb (); |
dummy->hw_token = token; |
urb->hcpriv = qh_get (qh); |
} |
} |
return qh; |
} |
/*-------------------------------------------------------------------------*/ |
static int |
submit_async ( |
struct ehci_hcd *ehci, |
struct urb *urb, |
struct list_head *qtd_list, |
int mem_flags |
) { |
struct ehci_qtd *qtd; |
struct hcd_dev *dev; |
int epnum; |
unsigned long flags; |
struct ehci_qh *qh = 0; |
qtd = list_entry (qtd_list->next, struct ehci_qtd, qtd_list); |
dev = (struct hcd_dev *)urb->dev->hcpriv; |
epnum = usb_pipeendpoint (urb->pipe); |
if (usb_pipein (urb->pipe) && !usb_pipecontrol (urb->pipe)) |
epnum |= 0x10; |
ehci_vdbg (ehci, "submit_async urb %p len %d ep%d%s qtd %p [qh %p]\n", |
urb, urb->transfer_buffer_length, |
epnum & 0x0f, (epnum & 0x10) ? "in" : "out", |
qtd, dev ? dev->ep [epnum] : (void *)~0); |
spin_lock_irqsave (&ehci->lock, flags); |
qh = qh_append_tds (ehci, urb, qtd_list, epnum, &dev->ep [epnum]); |
/* Control/bulk operations through TTs don't need scheduling, |
* the HC and TT handle it when the TT has a buffer ready. |
*/ |
if (likely (qh != 0)) { |
if (likely (qh->qh_state == QH_STATE_IDLE)) |
qh_link_async (ehci, qh_get (qh)); |
} |
spin_unlock_irqrestore (&ehci->lock, flags); |
if (unlikely (qh == 0)) { |
qtd_list_free (ehci, urb, qtd_list); |
return -ENOMEM; |
} |
return 0; |
} |
/*-------------------------------------------------------------------------*/ |
/* the async qh for the qtds being reclaimed are now unlinked from the HC */ |
static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh); |
static void end_unlink_async (struct ehci_hcd *ehci, struct pt_regs *regs) |
{ |
struct ehci_qh *qh = ehci->reclaim; |
struct ehci_qh *next; |
timer_action_done (ehci, TIMER_IAA_WATCHDOG); |
// qh->hw_next = cpu_to_le32 (qh->qh_dma); |
qh->qh_state = QH_STATE_IDLE; |
qh->qh_next.qh = 0; |
qh_put (ehci, qh); // refcount from reclaim |
/* other unlink(s) may be pending (in QH_STATE_UNLINK_WAIT) */ |
next = qh->reclaim; |
ehci->reclaim = next; |
ehci->reclaim_ready = 0; |
qh->reclaim = 0; |
qh_completions (ehci, qh, regs); |
if (!list_empty (&qh->qtd_list) |
&& HCD_IS_RUNNING (ehci->hcd.state)) |
qh_link_async (ehci, qh); |
else { |
qh_put (ehci, qh); // refcount from async list |
/* it's not free to turn the async schedule on/off; leave it |
* active but idle for a while once it empties. |
*/ |
if (HCD_IS_RUNNING (ehci->hcd.state) |
&& ehci->async->qh_next.qh == 0) |
timer_action (ehci, TIMER_ASYNC_OFF); |
} |
if (next) { |
ehci->reclaim = 0; |
start_unlink_async (ehci, next); |
} |
} |
/* makes sure the async qh will become idle */ |
/* caller must own ehci->lock */ |
static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) |
{ |
int cmd = readl (&ehci->regs->command); |
struct ehci_qh *prev; |
#ifdef DEBUG |
if (ehci->reclaim |
|| (qh->qh_state != QH_STATE_LINKED |
&& qh->qh_state != QH_STATE_UNLINK_WAIT) |
#ifdef CONFIG_SMP |
// this macro lies except on SMP compiles |
|| !spin_is_locked (&ehci->lock) |
#endif |
) |
BUG (); |
#endif |
/* stop async schedule right now? */ |
if (unlikely (qh == ehci->async)) { |
/* can't get here without STS_ASS set */ |
if (ehci->hcd.state != USB_STATE_HALT) { |
writel (cmd & ~CMD_ASE, &ehci->regs->command); |
wmb (); |
// handshake later, if we need to |
} |
timer_action_done (ehci, TIMER_ASYNC_OFF); |
return; |
} |
qh->qh_state = QH_STATE_UNLINK; |
ehci->reclaim = qh = qh_get (qh); |
prev = ehci->async; |
while (prev->qh_next.qh != qh) |
prev = prev->qh_next.qh; |
prev->hw_next = qh->hw_next; |
prev->qh_next = qh->qh_next; |
wmb (); |
if (unlikely (ehci->hcd.state == USB_STATE_HALT)) { |
/* if (unlikely (qh->reclaim != 0)) |
* this will recurse, probably not much |
*/ |
end_unlink_async (ehci, NULL); |
return; |
} |
ehci->reclaim_ready = 0; |
cmd |= CMD_IAAD; |
writel (cmd, &ehci->regs->command); |
(void) readl (&ehci->regs->command); |
timer_action (ehci, TIMER_IAA_WATCHDOG); |
} |
/*-------------------------------------------------------------------------*/ |
static void |
scan_async (struct ehci_hcd *ehci, struct pt_regs *regs) |
{ |
struct ehci_qh *qh; |
enum ehci_timer_action action = TIMER_IO_WATCHDOG; |
if (!++(ehci->stamp)) |
ehci->stamp++; |
timer_action_done (ehci, TIMER_ASYNC_SHRINK); |
rescan: |
qh = ehci->async->qh_next.qh; |
if (likely (qh != 0)) { |
do { |
/* clean any finished work for this qh */ |
if (!list_empty (&qh->qtd_list) |
&& qh->stamp != ehci->stamp) { |
int temp; |
/* unlinks could happen here; completion |
* reporting drops the lock. rescan using |
* the latest schedule, but don't rescan |
* qhs we already finished (no looping). |
*/ |
qh = qh_get (qh); |
qh->stamp = ehci->stamp; |
temp = qh_completions (ehci, qh, regs); |
qh_put (ehci, qh); |
if (temp != 0) { |
goto rescan; |
} |
} |
/* unlink idle entries, reducing HC PCI usage as well |
* as HCD schedule-scanning costs. delay for any qh |
* we just scanned, there's a not-unusual case that it |
* doesn't stay idle for long. |
* (plus, avoids some kind of re-activation race.) |
*/ |
if (list_empty (&qh->qtd_list)) { |
if (qh->stamp == ehci->stamp) |
action = TIMER_ASYNC_SHRINK; |
else if (!ehci->reclaim |
&& qh->qh_state == QH_STATE_LINKED) |
start_unlink_async (ehci, qh); |
} |
qh = qh->qh_next.qh; |
} while (qh); |
} |
if (action == TIMER_ASYNC_SHRINK) |
timer_action (ehci, TIMER_ASYNC_SHRINK); |
} |
/shark/trunk/drivers/usb/host/ohci-dbg.c |
---|
0,0 → 1,678 |
/* |
* OHCI HCD (Host Controller Driver) for USB. |
* |
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> |
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> |
* |
* This file is licenced under the GPL. |
*/ |
/*-------------------------------------------------------------------------*/ |
#ifdef DEBUG |
#define edstring(ed_type) ({ char *temp; \ |
switch (ed_type) { \ |
case PIPE_CONTROL: temp = "ctrl"; break; \ |
case PIPE_BULK: temp = "bulk"; break; \ |
case PIPE_INTERRUPT: temp = "intr"; break; \ |
default: temp = "isoc"; break; \ |
}; temp;}) |
#define pipestring(pipe) edstring(usb_pipetype(pipe)) |
/* debug| print the main components of an URB |
* small: 0) header + data packets 1) just header |
*/ |
static void __attribute__((unused)) |
urb_print (struct urb * urb, char * str, int small) |
{ |
unsigned int pipe= urb->pipe; |
if (!urb->dev || !urb->dev->bus) { |
dbg("%s URB: no dev", str); |
return; |
} |
#ifndef OHCI_VERBOSE_DEBUG |
if (urb->status != 0) |
#endif |
dbg("%s %p dev=%d ep=%d%s-%s flags=%x len=%d/%d stat=%d", |
str, |
urb, |
usb_pipedevice (pipe), |
usb_pipeendpoint (pipe), |
usb_pipeout (pipe)? "out" : "in", |
pipestring (pipe), |
urb->transfer_flags, |
urb->actual_length, |
urb->transfer_buffer_length, |
urb->status); |
#ifdef OHCI_VERBOSE_DEBUG |
if (!small) { |
int i, len; |
if (usb_pipecontrol (pipe)) { |
printk (KERN_DEBUG __FILE__ ": setup(8):"); |
for (i = 0; i < 8 ; i++) |
printk (" %02x", ((__u8 *) urb->setup_packet) [i]); |
printk ("\n"); |
} |
if (urb->transfer_buffer_length > 0 && urb->transfer_buffer) { |
printk (KERN_DEBUG __FILE__ ": data(%d/%d):", |
urb->actual_length, |
urb->transfer_buffer_length); |
len = usb_pipeout (pipe)? |
urb->transfer_buffer_length: urb->actual_length; |
for (i = 0; i < 16 && i < len; i++) |
printk (" %02x", ((__u8 *) urb->transfer_buffer) [i]); |
printk ("%s stat:%d\n", i < len? "...": "", urb->status); |
} |
} |
#endif |
} |
#define ohci_dbg_sw(ohci, next, size, format, arg...) \ |
do { \ |
if (next) { \ |
unsigned s_len; \ |
s_len = snprintf26(*next, *size, format, ## arg ); \ |
*size -= s_len; *next += s_len; \ |
} else \ |
ohci_dbg(ohci,format, ## arg ); \ |
} while (0); |
static void ohci_dump_intr_mask ( |
struct ohci_hcd *ohci, |
char *label, |
u32 mask, |
char **next, |
unsigned *size) |
{ |
ohci_dbg_sw (ohci, next, size, "%s 0x%08x%s%s%s%s%s%s%s%s%s\n", |
label, |
mask, |
(mask & OHCI_INTR_MIE) ? " MIE" : "", |
(mask & OHCI_INTR_OC) ? " OC" : "", |
(mask & OHCI_INTR_RHSC) ? " RHSC" : "", |
(mask & OHCI_INTR_FNO) ? " FNO" : "", |
(mask & OHCI_INTR_UE) ? " UE" : "", |
(mask & OHCI_INTR_RD) ? " RD" : "", |
(mask & OHCI_INTR_SF) ? " SF" : "", |
(mask & OHCI_INTR_WDH) ? " WDH" : "", |
(mask & OHCI_INTR_SO) ? " SO" : "" |
); |
} |
static void maybe_print_eds ( |
struct ohci_hcd *ohci, |
char *label, |
u32 value, |
char **next, |
unsigned *size) |
{ |
if (value) |
ohci_dbg_sw (ohci, next, size, "%s %08x\n", label, value); |
} |
static char *hcfs2string (int state) |
{ |
switch (state) { |
case OHCI_USB_RESET: return "reset"; |
case OHCI_USB_RESUME: return "resume"; |
case OHCI_USB_OPER: return "operational"; |
case OHCI_USB_SUSPEND: return "suspend"; |
} |
return "?"; |
} |
// dump control and status registers |
static void |
ohci_dump_status (struct ohci_hcd *controller, char **next, unsigned *size) |
{ |
struct ohci_regs *regs = controller->regs; |
u32 temp; |
temp = readl (®s->revision) & 0xff; |
ohci_dbg_sw (controller, next, size, |
"OHCI %d.%d, %s legacy support registers\n", |
0x03 & (temp >> 4), (temp & 0x0f), |
(temp & 0x10) ? "with" : "NO"); |
temp = readl (®s->control); |
ohci_dbg_sw (controller, next, size, |
"control 0x%03x%s%s%s HCFS=%s%s%s%s%s CBSR=%d\n", |
temp, |
(temp & OHCI_CTRL_RWE) ? " RWE" : "", |
(temp & OHCI_CTRL_RWC) ? " RWC" : "", |
(temp & OHCI_CTRL_IR) ? " IR" : "", |
hcfs2string (temp & OHCI_CTRL_HCFS), |
(temp & OHCI_CTRL_BLE) ? " BLE" : "", |
(temp & OHCI_CTRL_CLE) ? " CLE" : "", |
(temp & OHCI_CTRL_IE) ? " IE" : "", |
(temp & OHCI_CTRL_PLE) ? " PLE" : "", |
temp & OHCI_CTRL_CBSR |
); |
temp = readl (®s->cmdstatus); |
ohci_dbg_sw (controller, next, size, |
"cmdstatus 0x%05x SOC=%d%s%s%s%s\n", temp, |
(temp & OHCI_SOC) >> 16, |
(temp & OHCI_OCR) ? " OCR" : "", |
(temp & OHCI_BLF) ? " BLF" : "", |
(temp & OHCI_CLF) ? " CLF" : "", |
(temp & OHCI_HCR) ? " HCR" : "" |
); |
ohci_dump_intr_mask (controller, "intrstatus", |
readl (®s->intrstatus), next, size); |
ohci_dump_intr_mask (controller, "intrenable", |
readl (®s->intrenable), next, size); |
// intrdisable always same as intrenable |
maybe_print_eds (controller, "ed_periodcurrent", |
readl (®s->ed_periodcurrent), next, size); |
maybe_print_eds (controller, "ed_controlhead", |
readl (®s->ed_controlhead), next, size); |
maybe_print_eds (controller, "ed_controlcurrent", |
readl (®s->ed_controlcurrent), next, size); |
maybe_print_eds (controller, "ed_bulkhead", |
readl (®s->ed_bulkhead), next, size); |
maybe_print_eds (controller, "ed_bulkcurrent", |
readl (®s->ed_bulkcurrent), next, size); |
maybe_print_eds (controller, "donehead", |
readl (®s->donehead), next, size); |
} |
#define dbg_port_sw(hc,num,value,next,size) \ |
ohci_dbg_sw (hc, next, size, \ |
"roothub.portstatus [%d] " \ |
"0x%08x%s%s%s%s%s%s%s%s%s%s%s%s\n", \ |
num, temp, \ |
(temp & RH_PS_PRSC) ? " PRSC" : "", \ |
(temp & RH_PS_OCIC) ? " OCIC" : "", \ |
(temp & RH_PS_PSSC) ? " PSSC" : "", \ |
(temp & RH_PS_PESC) ? " PESC" : "", \ |
(temp & RH_PS_CSC) ? " CSC" : "", \ |
\ |
(temp & RH_PS_LSDA) ? " LSDA" : "", \ |
(temp & RH_PS_PPS) ? " PPS" : "", \ |
(temp & RH_PS_PRS) ? " PRS" : "", \ |
(temp & RH_PS_POCI) ? " POCI" : "", \ |
(temp & RH_PS_PSS) ? " PSS" : "", \ |
\ |
(temp & RH_PS_PES) ? " PES" : "", \ |
(temp & RH_PS_CCS) ? " CCS" : "" \ |
); |
static void |
ohci_dump_roothub ( |
struct ohci_hcd *controller, |
int verbose, |
char **next, |
unsigned *size) |
{ |
u32 temp, ndp, i; |
temp = roothub_a (controller); |
if (temp == ~(u32)0) |
return; |
ndp = (temp & RH_A_NDP); |
if (verbose) { |
ohci_dbg_sw (controller, next, size, |
"roothub.a %08x POTPGT=%d%s%s%s%s%s NDP=%d\n", temp, |
((temp & RH_A_POTPGT) >> 24) & 0xff, |
(temp & RH_A_NOCP) ? " NOCP" : "", |
(temp & RH_A_OCPM) ? " OCPM" : "", |
(temp & RH_A_DT) ? " DT" : "", |
(temp & RH_A_NPS) ? " NPS" : "", |
(temp & RH_A_PSM) ? " PSM" : "", |
ndp |
); |
temp = roothub_b (controller); |
ohci_dbg_sw (controller, next, size, |
"roothub.b %08x PPCM=%04x DR=%04x\n", |
temp, |
(temp & RH_B_PPCM) >> 16, |
(temp & RH_B_DR) |
); |
temp = roothub_status (controller); |
ohci_dbg_sw (controller, next, size, |
"roothub.status %08x%s%s%s%s%s%s\n", |
temp, |
(temp & RH_HS_CRWE) ? " CRWE" : "", |
(temp & RH_HS_OCIC) ? " OCIC" : "", |
(temp & RH_HS_LPSC) ? " LPSC" : "", |
(temp & RH_HS_DRWE) ? " DRWE" : "", |
(temp & RH_HS_OCI) ? " OCI" : "", |
(temp & RH_HS_LPS) ? " LPS" : "" |
); |
} |
for (i = 0; i < ndp; i++) { |
temp = roothub_portstatus (controller, i); |
dbg_port_sw (controller, i, temp, next, size); |
} |
} |
static void ohci_dump (struct ohci_hcd *controller, int verbose) |
{ |
ohci_dbg (controller, "OHCI controller state\n"); |
// dumps some of the state we know about |
ohci_dump_status (controller, NULL, 0); |
if (controller->hcca) |
ohci_dbg (controller, |
"hcca frame #%04x\n", controller->hcca->frame_no); |
ohci_dump_roothub (controller, 1, NULL, 0); |
} |
static const char data0 [] = "DATA0"; |
static const char data1 [] = "DATA1"; |
static void ohci_dump_td (struct ohci_hcd *ohci, char *label, struct td *td) |
{ |
u32 tmp = le32_to_cpup (&td->hwINFO); |
ohci_dbg (ohci, "%s td %p%s; urb %p index %d; hw next td %08x", |
label, td, |
(tmp & TD_DONE) ? " (DONE)" : "", |
td->urb, td->index, |
le32_to_cpup (&td->hwNextTD)); |
if ((tmp & TD_ISO) == 0) { |
const char *toggle, *pid; |
u32 cbp, be; |
switch (tmp & TD_T) { |
case TD_T_DATA0: toggle = data0; break; |
case TD_T_DATA1: toggle = data1; break; |
case TD_T_TOGGLE: toggle = "(CARRY)"; break; |
default: toggle = "(?)"; break; |
} |
switch (tmp & TD_DP) { |
case TD_DP_SETUP: pid = "SETUP"; break; |
case TD_DP_IN: pid = "IN"; break; |
case TD_DP_OUT: pid = "OUT"; break; |
default: pid = "(bad pid)"; break; |
} |
ohci_dbg (ohci, " info %08x CC=%x %s DI=%d %s %s", tmp, |
TD_CC_GET(tmp), /* EC, */ toggle, |
(tmp & TD_DI) >> 21, pid, |
(tmp & TD_R) ? "R" : ""); |
cbp = le32_to_cpup (&td->hwCBP); |
be = le32_to_cpup (&td->hwBE); |
ohci_dbg (ohci, " cbp %08x be %08x (len %d)", cbp, be, |
cbp ? (be + 1 - cbp) : 0); |
} else { |
unsigned i; |
ohci_dbg (ohci, " info %08x CC=%x FC=%d DI=%d SF=%04x", tmp, |
TD_CC_GET(tmp), |
(tmp >> 24) & 0x07, |
(tmp & TD_DI) >> 21, |
tmp & 0x0000ffff); |
ohci_dbg (ohci, " bp0 %08x be %08x", |
le32_to_cpup (&td->hwCBP) & ~0x0fff, |
le32_to_cpup (&td->hwBE)); |
for (i = 0; i < MAXPSW; i++) { |
u16 psw = le16_to_cpup (&td->hwPSW [i]); |
int cc = (psw >> 12) & 0x0f; |
ohci_dbg (ohci, " psw [%d] = %2x, CC=%x %s=%d", i, |
psw, cc, |
(cc >= 0x0e) ? "OFFSET" : "SIZE", |
psw & 0x0fff); |
} |
} |
} |
/* caller MUST own hcd spinlock if verbose is set! */ |
static void __attribute__((unused)) |
ohci_dump_ed (struct ohci_hcd *ohci, char *label, struct ed *ed, int verbose) |
{ |
u32 tmp = ed->hwINFO; |
char *type = ""; |
ohci_dbg (ohci, "%s, ed %p state 0x%x type %s; next ed %08x", |
label, |
ed, ed->state, edstring (ed->type), |
le32_to_cpup (&ed->hwNextED)); |
switch (tmp & (ED_IN|ED_OUT)) { |
case ED_OUT: type = "-OUT"; break; |
case ED_IN: type = "-IN"; break; |
/* else from TDs ... control */ |
} |
ohci_dbg (ohci, |
" info %08x MAX=%d%s%s%s%s EP=%d%s DEV=%d", le32_to_cpu (tmp), |
0x03ff & (le32_to_cpu (tmp) >> 16), |
(tmp & ED_DEQUEUE) ? " DQ" : "", |
(tmp & ED_ISO) ? " ISO" : "", |
(tmp & ED_SKIP) ? " SKIP" : "", |
(tmp & ED_LOWSPEED) ? " LOW" : "", |
0x000f & (le32_to_cpu (tmp) >> 7), |
type, |
0x007f & le32_to_cpu (tmp)); |
ohci_dbg (ohci, " tds: head %08x %s%s tail %08x%s", |
tmp = le32_to_cpup (&ed->hwHeadP), |
(ed->hwHeadP & ED_C) ? data1 : data0, |
(ed->hwHeadP & ED_H) ? " HALT" : "", |
le32_to_cpup (&ed->hwTailP), |
verbose ? "" : " (not listing)"); |
if (verbose) { |
struct list_head *tmp; |
/* use ed->td_list because HC concurrently modifies |
* hwNextTD as it accumulates ed_donelist. |
*/ |
list_for_each (tmp, &ed->td_list) { |
struct td *td; |
td = list_entry (tmp, struct td, td_list); |
ohci_dump_td (ohci, " ->", td); |
} |
} |
} |
#else |
static inline void ohci_dump (struct ohci_hcd *controller, int verbose) {} |
#undef OHCI_VERBOSE_DEBUG |
#endif /* DEBUG */ |
/*-------------------------------------------------------------------------*/ |
#ifdef STUB_DEBUG_FILES |
static inline void create_debug_files (struct ohci_hcd *bus) { } |
static inline void remove_debug_files (struct ohci_hcd *bus) { } |
#else |
static inline struct ohci_hcd *dev_to_ohci (struct device *dev) |
{ |
struct usb_hcd *hcd = dev_get_drvdata (dev); |
return hcd_to_ohci (hcd); |
} |
static ssize_t |
show_list (struct ohci_hcd *ohci, char *buf, size_t count, struct ed *ed) |
{ |
unsigned temp, size = count; |
if (!ed) |
return 0; |
/* print first --> last */ |
while (ed->ed_prev) |
ed = ed->ed_prev; |
/* dump a snapshot of the bulk or control schedule */ |
while (ed) { |
u32 info = ed->hwINFO; |
u32 scratch = cpu_to_le32p (&ed->hwINFO); |
struct list_head *entry; |
struct td *td; |
temp = snprintf26(buf, size, |
"ed/%p %cs dev%d ep%d%s max %d %08x%s%s %s", |
ed, |
(info & ED_LOWSPEED) ? 'l' : 'f', |
scratch & 0x7f, |
(scratch >> 7) & 0xf, |
(info & ED_IN) ? "in" : "out", |
0x03ff & (scratch >> 16), |
scratch, |
(info & ED_SKIP) ? " s" : "", |
(ed->hwHeadP & ED_H) ? " H" : "", |
(ed->hwHeadP & ED_C) ? data1 : data0); |
size -= temp; |
buf += temp; |
list_for_each (entry, &ed->td_list) { |
u32 cbp, be; |
td = list_entry (entry, struct td, td_list); |
scratch = cpu_to_le32p (&td->hwINFO); |
cbp = le32_to_cpup (&td->hwCBP); |
be = le32_to_cpup (&td->hwBE); |
temp = snprintf26(buf, size, |
"\n\ttd %p %s %d cc=%x urb %p (%08x)", |
td, |
({ char *pid; |
switch (scratch & TD_DP) { |
case TD_DP_SETUP: pid = "setup"; break; |
case TD_DP_IN: pid = "in"; break; |
case TD_DP_OUT: pid = "out"; break; |
default: pid = "(?)"; break; |
} pid;}), |
cbp ? (be + 1 - cbp) : 0, |
TD_CC_GET (scratch), td->urb, scratch); |
size -= temp; |
buf += temp; |
} |
temp = snprintf26(buf, size, "\n"); |
size -= temp; |
buf += temp; |
ed = ed->ed_next; |
} |
return count - size; |
} |
static ssize_t |
show_async (struct class_device *class_dev, char *buf) |
{ |
struct usb_bus *bus; |
struct usb_hcd *hcd; |
struct ohci_hcd *ohci; |
size_t temp; |
unsigned long flags; |
bus = to_usb_bus(class_dev); |
hcd = bus->hcpriv; |
ohci = hcd_to_ohci(hcd); |
/* display control and bulk lists together, for simplicity */ |
spin_lock_irqsave (&ohci->lock, flags); |
temp = show_list (ohci, buf, PAGE_SIZE, ohci->ed_controltail); |
temp += show_list (ohci, buf + temp, PAGE_SIZE - temp, ohci->ed_bulktail); |
spin_unlock_irqrestore (&ohci->lock, flags); |
return temp; |
} |
static CLASS_DEVICE_ATTR (async, S_IRUGO, show_async, NULL); |
#define DBG_SCHED_LIMIT 64 |
static ssize_t |
show_periodic (struct class_device *class_dev, char *buf) |
{ |
struct usb_bus *bus; |
struct usb_hcd *hcd; |
struct ohci_hcd *ohci; |
struct ed **seen, *ed; |
unsigned long flags; |
unsigned temp, size, seen_count; |
char *next; |
unsigned i; |
if (!(seen = kmalloc (DBG_SCHED_LIMIT * sizeof *seen, SLAB_ATOMIC))) |
return 0; |
seen_count = 0; |
bus = to_usb_bus(class_dev); |
hcd = bus->hcpriv; |
ohci = hcd_to_ohci(hcd); |
next = buf; |
size = PAGE_SIZE; |
temp = snprintf26(next, size, "size = %d\n", NUM_INTS); |
size -= temp; |
next += temp; |
/* dump a snapshot of the periodic schedule (and load) */ |
spin_lock_irqsave (&ohci->lock, flags); |
for (i = 0; i < NUM_INTS; i++) { |
if (!(ed = ohci->periodic [i])) |
continue; |
temp = snprintf26(next, size, "%2d [%3d]:", i, ohci->load [i]); |
size -= temp; |
next += temp; |
do { |
temp = snprintf26(next, size, " ed%d/%p", |
ed->interval, ed); |
size -= temp; |
next += temp; |
for (temp = 0; temp < seen_count; temp++) { |
if (seen [temp] == ed) |
break; |
} |
/* show more info the first time around */ |
if (temp == seen_count) { |
u32 info = ed->hwINFO; |
u32 scratch = cpu_to_le32p (&ed->hwINFO); |
temp = snprintf26(next, size, |
" (%cs dev%d%s ep%d%s" |
" max %d %08x%s%s)", |
(info & ED_LOWSPEED) ? 'l' : 'f', |
scratch & 0x7f, |
(info & ED_ISO) ? " iso" : "", |
(scratch >> 7) & 0xf, |
(info & ED_IN) ? "in" : "out", |
0x03ff & (scratch >> 16), |
scratch, |
(info & ED_SKIP) ? " s" : "", |
(ed->hwHeadP & ED_H) ? " H" : ""); |
size -= temp; |
next += temp; |
// FIXME some TD info too |
if (seen_count < DBG_SCHED_LIMIT) |
seen [seen_count++] = ed; |
ed = ed->ed_next; |
} else { |
/* we've seen it and what's after */ |
temp = 0; |
ed = 0; |
} |
} while (ed); |
temp = snprintf26(next, size, "\n"); |
size -= temp; |
next += temp; |
} |
spin_unlock_irqrestore (&ohci->lock, flags); |
kfree (seen); |
return PAGE_SIZE - size; |
} |
static CLASS_DEVICE_ATTR (periodic, S_IRUGO, show_periodic, NULL); |
#undef DBG_SCHED_LIMIT |
static ssize_t |
show_registers (struct class_device *class_dev, char *buf) |
{ |
struct usb_bus *bus; |
struct usb_hcd *hcd; |
struct ohci_hcd *ohci; |
struct ohci_regs *regs; |
unsigned long flags; |
unsigned temp, size; |
char *next; |
u32 rdata; |
bus = to_usb_bus(class_dev); |
hcd = bus->hcpriv; |
ohci = hcd_to_ohci(hcd); |
regs = ohci->regs; |
next = buf; |
size = PAGE_SIZE; |
spin_lock_irqsave (&ohci->lock, flags); |
/* dump driver info, then registers in spec order */ |
ohci_dbg_sw (ohci, &next, &size, |
"%s version " DRIVER_VERSION "\n", hcd_name); |
ohci_dump_status(ohci, &next, &size); |
/* hcca */ |
if (ohci->hcca) |
ohci_dbg_sw (ohci, &next, &size, |
"hcca frame 0x%04x\n", ohci->hcca->frame_no); |
/* other registers mostly affect frame timings */ |
rdata = readl (®s->fminterval); |
temp = snprintf26(next, size, |
"fmintvl 0x%08x %sFSMPS=0x%04x FI=0x%04x\n", |
rdata, (rdata >> 31) ? " FIT" : "", |
(rdata >> 16) & 0xefff, rdata & 0xffff); |
size -= temp; |
next += temp; |
rdata = readl (®s->fmremaining); |
temp = snprintf26(next, size, "fmremaining 0x%08x %sFR=0x%04x\n", |
rdata, (rdata >> 31) ? " FRT" : "", |
rdata & 0x3fff); |
size -= temp; |
next += temp; |
rdata = readl (®s->periodicstart); |
temp = snprintf26(next, size, "periodicstart 0x%04x\n", |
rdata & 0x3fff); |
size -= temp; |
next += temp; |
rdata = readl (®s->lsthresh); |
temp = snprintf26(next, size, "lsthresh 0x%04x\n", |
rdata & 0x3fff); |
size -= temp; |
next += temp; |
/* roothub */ |
ohci_dump_roothub (ohci, 1, &next, &size); |
spin_unlock_irqrestore (&ohci->lock, flags); |
return PAGE_SIZE - size; |
} |
static CLASS_DEVICE_ATTR (registers, S_IRUGO, show_registers, NULL); |
static inline void create_debug_files (struct ohci_hcd *bus) |
{ |
class_device_create_file(&bus->hcd.self.class_dev, &class_device_attr_async); |
class_device_create_file(&bus->hcd.self.class_dev, &class_device_attr_periodic); |
class_device_create_file(&bus->hcd.self.class_dev, &class_device_attr_registers); |
ohci_dbg (bus, "created debug files\n"); |
} |
static inline void remove_debug_files (struct ohci_hcd *bus) |
{ |
class_device_remove_file(&bus->hcd.self.class_dev, &class_device_attr_async); |
class_device_remove_file(&bus->hcd.self.class_dev, &class_device_attr_periodic); |
class_device_remove_file(&bus->hcd.self.class_dev, &class_device_attr_registers); |
} |
#endif |
/*-------------------------------------------------------------------------*/ |
/shark/trunk/drivers/usb/host/uhci-debug.c |
---|
0,0 → 1,625 |
/* |
* UHCI-specific debugging code. Invaluable when something |
* goes wrong, but don't get in my face. |
* |
* Kernel visible pointers are surrounded in []'s and bus |
* visible pointers are surrounded in ()'s |
* |
* (C) Copyright 1999 Linus Torvalds |
* (C) Copyright 1999-2001 Johannes Erdfelt |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/kernel.h> |
#include <linux/proc_fs.h> |
#include <linux/smp_lock.h> |
#include <asm/io.h> |
#include "uhci-hcd.h" |
/* Handle REALLY large printk's so we don't overflow buffers */ |
static inline void lprintk(char *buf) |
{ |
char *p; |
/* Just write one line at a time */ |
while (buf) { |
p = strchr(buf, '\n'); |
if (p) |
*p = 0; |
printk("%s\n", buf); |
buf = p; |
if (buf) |
buf++; |
} |
} |
static inline int uhci_is_skeleton_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) |
{ |
int i; |
for (i = 0; i < UHCI_NUM_SKELQH; i++) |
if (qh == uhci->skelqh[i]) |
return 1; |
return 0; |
} |
static int uhci_show_td(struct uhci_td *td, char *buf, int len, int space) |
{ |
char *out = buf; |
char *spid; |
u32 status, token; |
/* Try to make sure there's enough memory */ |
if (len < 160) |
return 0; |
status = td_status(td); |
out += sprintf26(out, "%*s[%p] link (%08x) ", space, "", td, le32_to_cpu(td->link)); |
out += sprintf26(out, "e%d %s%s%s%s%s%s%s%s%s%sLength=%x ", |
((status >> 27) & 3), |
(status & TD_CTRL_SPD) ? "SPD " : "", |
(status & TD_CTRL_LS) ? "LS " : "", |
(status & TD_CTRL_IOC) ? "IOC " : "", |
(status & TD_CTRL_ACTIVE) ? "Active " : "", |
(status & TD_CTRL_STALLED) ? "Stalled " : "", |
(status & TD_CTRL_DBUFERR) ? "DataBufErr " : "", |
(status & TD_CTRL_BABBLE) ? "Babble " : "", |
(status & TD_CTRL_NAK) ? "NAK " : "", |
(status & TD_CTRL_CRCTIMEO) ? "CRC/Timeo " : "", |
(status & TD_CTRL_BITSTUFF) ? "BitStuff " : "", |
status & 0x7ff); |
token = td_token(td); |
switch (uhci_packetid(token)) { |
case USB_PID_SETUP: |
spid = "SETUP"; |
break; |
case USB_PID_OUT: |
spid = "OUT"; |
break; |
case USB_PID_IN: |
spid = "IN"; |
break; |
default: |
spid = "?"; |
break; |
} |
out += sprintf26(out, "MaxLen=%x DT%d EndPt=%x Dev=%x, PID=%x(%s) ", |
token >> 21, |
((token >> 19) & 1), |
(token >> 15) & 15, |
(token >> 8) & 127, |
(token & 0xff), |
spid); |
out += sprintf26(out, "(buf=%08x)\n", le32_to_cpu(td->buffer)); |
return out - buf; |
} |
static int uhci_show_qh(struct uhci_qh *qh, char *buf, int len, int space) |
{ |
char *out = buf; |
struct urb_priv *urbp; |
struct list_head *head, *tmp; |
struct uhci_td *td; |
int i = 0, checked = 0, prevactive = 0; |
/* Try to make sure there's enough memory */ |
if (len < 80 * 6) |
return 0; |
out += sprintf26(out, "%*s[%p] link (%08x) element (%08x)\n", space, "", |
qh, le32_to_cpu(qh->link), le32_to_cpu(qh->element)); |
if (qh->element & UHCI_PTR_QH) |
out += sprintf26(out, "%*s Element points to QH (bug?)\n", space, ""); |
if (qh->element & UHCI_PTR_DEPTH) |
out += sprintf26(out, "%*s Depth traverse\n", space, ""); |
if (qh->element & cpu_to_le32(8)) |
out += sprintf26(out, "%*s Bit 3 set (bug?)\n", space, ""); |
if (!(qh->element & ~(UHCI_PTR_QH | UHCI_PTR_DEPTH))) |
out += sprintf26(out, "%*s Element is NULL (bug?)\n", space, ""); |
if (!qh->urbp) { |
out += sprintf26(out, "%*s urbp == NULL\n", space, ""); |
goto out; |
} |
urbp = qh->urbp; |
head = &urbp->td_list; |
tmp = head->next; |
td = list_entry(tmp, struct uhci_td, list); |
if (cpu_to_le32(td->dma_handle) != (qh->element & ~UHCI_PTR_BITS)) |
out += sprintf26(out, "%*s Element != First TD\n", space, ""); |
while (tmp != head) { |
struct uhci_td *td = list_entry(tmp, struct uhci_td, list); |
tmp = tmp->next; |
out += sprintf26(out, "%*s%d: ", space + 2, "", i++); |
out += uhci_show_td(td, out, len - (out - buf), 0); |
if (i > 10 && !checked && prevactive && tmp != head && |
debug <= 2) { |
struct list_head *ntmp = tmp; |
struct uhci_td *ntd = td; |
int active = 1, ni = i; |
checked = 1; |
while (ntmp != head && ntmp->next != head && active) { |
ntd = list_entry(ntmp, struct uhci_td, list); |
ntmp = ntmp->next; |
active = td_status(ntd) & TD_CTRL_ACTIVE; |
ni++; |
} |
if (active && ni > i) { |
out += sprintf26(out, "%*s[skipped %d active TD's]\n", space, "", ni - i); |
tmp = ntmp; |
td = ntd; |
i = ni; |
} |
} |
prevactive = td_status(td) & TD_CTRL_ACTIVE; |
} |
if (list_empty(&urbp->queue_list) || urbp->queued) |
goto out; |
out += sprintf26(out, "%*sQueued QH's:\n", -space, "--"); |
head = &urbp->queue_list; |
tmp = head->next; |
while (tmp != head) { |
struct urb_priv *nurbp = list_entry(tmp, struct urb_priv, |
queue_list); |
tmp = tmp->next; |
out += uhci_show_qh(nurbp->qh, out, len - (out - buf), space); |
} |
out: |
return out - buf; |
} |
#define show_frame_num() \ |
if (!shown) { \ |
shown = 1; \ |
out += sprintf26(out, "- Frame %d\n", i); \ |
} |
#ifdef CONFIG_PROC_FS |
static const char *qh_names[] = { |
"skel_int128_qh", "skel_int64_qh", |
"skel_int32_qh", "skel_int16_qh", |
"skel_int8_qh", "skel_int4_qh", |
"skel_int2_qh", "skel_int1_qh", |
"skel_ls_control_qh", "skel_hs_control_qh", |
"skel_bulk_qh", "skel_term_qh" |
}; |
#define show_qh_name() \ |
if (!shown) { \ |
shown = 1; \ |
out += sprintf26(out, "- %s\n", qh_names[i]); \ |
} |
static int uhci_show_sc(int port, unsigned short status, char *buf, int len) |
{ |
char *out = buf; |
/* Try to make sure there's enough memory */ |
if (len < 80) |
return 0; |
out += sprintf26(out, " stat%d = %04x %s%s%s%s%s%s%s%s\n", |
port, |
status, |
(status & USBPORTSC_SUSP) ? "PortSuspend " : "", |
(status & USBPORTSC_PR) ? "PortReset " : "", |
(status & USBPORTSC_LSDA) ? "LowSpeed " : "", |
(status & USBPORTSC_RD) ? "ResumeDetect " : "", |
(status & USBPORTSC_PEC) ? "EnableChange " : "", |
(status & USBPORTSC_PE) ? "PortEnabled " : "", |
(status & USBPORTSC_CSC) ? "ConnectChange " : "", |
(status & USBPORTSC_CCS) ? "PortConnected " : ""); |
return out - buf; |
} |
static int uhci_show_status(struct uhci_hcd *uhci, char *buf, int len) |
{ |
char *out = buf; |
unsigned int io_addr = uhci->io_addr; |
unsigned short usbcmd, usbstat, usbint, usbfrnum; |
unsigned int flbaseadd; |
unsigned char sof; |
unsigned short portsc1, portsc2; |
/* Try to make sure there's enough memory */ |
if (len < 80 * 6) |
return 0; |
usbcmd = inw(io_addr + 0); |
usbstat = inw(io_addr + 2); |
usbint = inw(io_addr + 4); |
usbfrnum = inw(io_addr + 6); |
flbaseadd = inl(io_addr + 8); |
sof = inb(io_addr + 12); |
portsc1 = inw(io_addr + 16); |
portsc2 = inw(io_addr + 18); |
out += sprintf26(out, " usbcmd = %04x %s%s%s%s%s%s%s%s\n", |
usbcmd, |
(usbcmd & USBCMD_MAXP) ? "Maxp64 " : "Maxp32 ", |
(usbcmd & USBCMD_CF) ? "CF " : "", |
(usbcmd & USBCMD_SWDBG) ? "SWDBG " : "", |
(usbcmd & USBCMD_FGR) ? "FGR " : "", |
(usbcmd & USBCMD_EGSM) ? "EGSM " : "", |
(usbcmd & USBCMD_GRESET) ? "GRESET " : "", |
(usbcmd & USBCMD_HCRESET) ? "HCRESET " : "", |
(usbcmd & USBCMD_RS) ? "RS " : ""); |
out += sprintf26(out, " usbstat = %04x %s%s%s%s%s%s\n", |
usbstat, |
(usbstat & USBSTS_HCH) ? "HCHalted " : "", |
(usbstat & USBSTS_HCPE) ? "HostControllerProcessError " : "", |
(usbstat & USBSTS_HSE) ? "HostSystemError " : "", |
(usbstat & USBSTS_RD) ? "ResumeDetect " : "", |
(usbstat & USBSTS_ERROR) ? "USBError " : "", |
(usbstat & USBSTS_USBINT) ? "USBINT " : ""); |
out += sprintf26(out, " usbint = %04x\n", usbint); |
out += sprintf26(out, " usbfrnum = (%d)%03x\n", (usbfrnum >> 10) & 1, |
0xfff & (4*(unsigned int)usbfrnum)); |
out += sprintf26(out, " flbaseadd = %08x\n", flbaseadd); |
out += sprintf26(out, " sof = %02x\n", sof); |
out += uhci_show_sc(1, portsc1, out, len - (out - buf)); |
out += uhci_show_sc(2, portsc2, out, len - (out - buf)); |
return out - buf; |
} |
static int uhci_show_urbp(struct uhci_hcd *uhci, struct urb_priv *urbp, char *buf, int len) |
{ |
struct list_head *tmp; |
char *out = buf; |
int count = 0; |
if (len < 200) |
return 0; |
out += sprintf26(out, "urb_priv [%p] ", urbp); |
out += sprintf26(out, "urb [%p] ", urbp->urb); |
out += sprintf26(out, "qh [%p] ", urbp->qh); |
out += sprintf26(out, "Dev=%d ", usb_pipedevice(urbp->urb->pipe)); |
out += sprintf26(out, "EP=%x(%s) ", usb_pipeendpoint(urbp->urb->pipe), (usb_pipein(urbp->urb->pipe) ? "IN" : "OUT")); |
switch (usb_pipetype(urbp->urb->pipe)) { |
case PIPE_ISOCHRONOUS: out += sprintf26(out, "ISO "); break; |
case PIPE_INTERRUPT: out += sprintf26(out, "INT "); break; |
case PIPE_BULK: out += sprintf26(out, "BLK "); break; |
case PIPE_CONTROL: out += sprintf26(out, "CTL "); break; |
} |
out += sprintf26(out, "%s", (urbp->fsbr ? "FSBR " : "")); |
out += sprintf26(out, "%s", (urbp->fsbr_timeout ? "FSBR_TO " : "")); |
if (urbp->status != -EINPROGRESS) |
out += sprintf26(out, "Status=%d ", urbp->status); |
//out += sprintf26(out, "Inserttime=%lx ",urbp->inserttime); |
//out += sprintf26(out, "FSBRtime=%lx ",urbp->fsbrtime); |
spin_lock(&urbp->urb->lock); |
count = 0; |
list_for_each(tmp, &urbp->td_list) |
count++; |
spin_unlock(&urbp->urb->lock); |
out += sprintf26(out, "TDs=%d ",count); |
if (urbp->queued) |
out += sprintf26(out, "queued\n"); |
else { |
spin_lock(&uhci->frame_list_lock); |
count = 0; |
list_for_each(tmp, &urbp->queue_list) |
count++; |
spin_unlock(&uhci->frame_list_lock); |
out += sprintf26(out, "queued URBs=%d\n", count); |
} |
return out - buf; |
} |
static int uhci_show_lists(struct uhci_hcd *uhci, char *buf, int len) |
{ |
char *out = buf; |
unsigned long flags; |
struct list_head *head, *tmp; |
int count; |
out += sprintf26(out, "Main list URBs:"); |
spin_lock_irqsave(&uhci->urb_list_lock, flags); |
if (list_empty(&uhci->urb_list)) |
out += sprintf26(out, " Empty\n"); |
else { |
out += sprintf26(out, "\n"); |
count = 0; |
head = &uhci->urb_list; |
tmp = head->next; |
while (tmp != head) { |
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, urb_list); |
out += sprintf26(out, " %d: ", ++count); |
out += uhci_show_urbp(uhci, urbp, out, len - (out - buf)); |
tmp = tmp->next; |
} |
} |
spin_unlock_irqrestore(&uhci->urb_list_lock, flags); |
out += sprintf26(out, "Remove list URBs:"); |
spin_lock_irqsave(&uhci->urb_remove_list_lock, flags); |
if (list_empty(&uhci->urb_remove_list)) |
out += sprintf26(out, " Empty\n"); |
else { |
out += sprintf26(out, "\n"); |
count = 0; |
head = &uhci->urb_remove_list; |
tmp = head->next; |
while (tmp != head) { |
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, urb_list); |
out += sprintf26(out, " %d: ", ++count); |
out += uhci_show_urbp(uhci, urbp, out, len - (out - buf)); |
tmp = tmp->next; |
} |
} |
spin_unlock_irqrestore(&uhci->urb_remove_list_lock, flags); |
out += sprintf26(out, "Complete list URBs:"); |
spin_lock_irqsave(&uhci->complete_list_lock, flags); |
if (list_empty(&uhci->complete_list)) |
out += sprintf26(out, " Empty\n"); |
else { |
out += sprintf26(out, "\n"); |
count = 0; |
head = &uhci->complete_list; |
tmp = head->next; |
while (tmp != head) { |
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, complete_list); |
out += sprintf26(out, " %d: ", ++count); |
out += uhci_show_urbp(uhci, urbp, out, len - (out - buf)); |
tmp = tmp->next; |
} |
} |
spin_unlock_irqrestore(&uhci->complete_list_lock, flags); |
return out - buf; |
} |
static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len) |
{ |
unsigned long flags; |
char *out = buf; |
int i; |
struct uhci_qh *qh; |
struct uhci_td *td; |
struct list_head *tmp, *head; |
spin_lock_irqsave(&uhci->frame_list_lock, flags); |
out += sprintf26(out, "HC status\n"); |
out += uhci_show_status(uhci, out, len - (out - buf)); |
out += sprintf26(out, "Frame List\n"); |
for (i = 0; i < UHCI_NUMFRAMES; ++i) { |
int shown = 0; |
td = uhci->fl->frame_cpu[i]; |
if (!td) |
continue; |
if (td->dma_handle != (dma_addr_t)uhci->fl->frame[i]) { |
show_frame_num(); |
out += sprintf26(out, " frame list does not match td->dma_handle!\n"); |
} |
show_frame_num(); |
head = &td->fl_list; |
tmp = head; |
do { |
td = list_entry(tmp, struct uhci_td, fl_list); |
tmp = tmp->next; |
out += uhci_show_td(td, out, len - (out - buf), 4); |
} while (tmp != head); |
} |
out += sprintf26(out, "Skeleton QH's\n"); |
for (i = 0; i < UHCI_NUM_SKELQH; ++i) { |
int shown = 0; |
qh = uhci->skelqh[i]; |
if (debug > 1) { |
show_qh_name(); |
out += uhci_show_qh(qh, out, len - (out - buf), 4); |
} |
/* Last QH is the Terminating QH, it's different */ |
if (i == UHCI_NUM_SKELQH - 1) { |
if (qh->link != UHCI_PTR_TERM) |
out += sprintf26(out, " bandwidth reclamation on!\n"); |
if (qh->element != cpu_to_le32(uhci->term_td->dma_handle)) |
out += sprintf26(out, " skel_term_qh element is not set to term_td!\n"); |
continue; |
} |
if (list_empty(&qh->list)) { |
if (i < UHCI_NUM_SKELQH - 1) { |
if (qh->link != |
(cpu_to_le32(uhci->skelqh[i + 1]->dma_handle) | UHCI_PTR_QH)) { |
show_qh_name(); |
out += sprintf26(out, " skeleton QH not linked to next skeleton QH!\n"); |
} |
} |
continue; |
} |
show_qh_name(); |
head = &qh->list; |
tmp = head->next; |
while (tmp != head) { |
qh = list_entry(tmp, struct uhci_qh, list); |
tmp = tmp->next; |
out += uhci_show_qh(qh, out, len - (out - buf), 4); |
} |
if (i < UHCI_NUM_SKELQH - 1) { |
if (qh->link != |
(cpu_to_le32(uhci->skelqh[i + 1]->dma_handle) | UHCI_PTR_QH)) |
out += sprintf26(out, " last QH not linked to next skeleton!\n"); |
} |
} |
spin_unlock_irqrestore(&uhci->frame_list_lock, flags); |
if (debug > 2) |
out += uhci_show_lists(uhci, out, len - (out - buf)); |
return out - buf; |
} |
#define MAX_OUTPUT (64 * 1024) |
static struct proc_dir_entry *uhci_proc_root = NULL; |
struct uhci_proc { |
int size; |
char *data; |
struct uhci_hcd *uhci; |
}; |
static int uhci_proc_open(struct inode *inode, struct file *file) |
{ |
const struct proc_dir_entry *dp = PDE(inode); |
struct uhci_hcd *uhci = dp->data; |
struct uhci_proc *up; |
int ret = -ENOMEM; |
lock_kernel(); |
up = kmalloc(sizeof(*up), GFP_KERNEL); |
if (!up) |
goto out; |
up->data = kmalloc(MAX_OUTPUT, GFP_KERNEL); |
if (!up->data) { |
kfree(up); |
goto out; |
} |
up->size = uhci_sprint_schedule(uhci, up->data, MAX_OUTPUT); |
file->private_data = up; |
ret = 0; |
out: |
unlock_kernel(); |
return ret; |
} |
static loff_t uhci_proc_lseek(struct file *file, loff_t off, int whence) |
{ |
struct uhci_proc *up; |
loff_t new = -1; |
lock_kernel(); |
up = file->private_data; |
switch (whence) { |
case 0: |
new = off; |
break; |
case 1: |
new = file->f_pos + off; |
break; |
} |
if (new < 0 || new > up->size) { |
unlock_kernel(); |
return -EINVAL; |
} |
unlock_kernel(); |
return (file->f_pos = new); |
} |
static ssize_t uhci_proc_read(struct file *file, char *buf, size_t nbytes, |
loff_t *ppos) |
{ |
struct uhci_proc *up = file->private_data; |
unsigned int pos; |
unsigned int size; |
pos = *ppos; |
size = up->size; |
if (pos >= size) |
return 0; |
if (nbytes >= size) |
nbytes = size; |
if (pos + nbytes > size) |
nbytes = size - pos; |
if (!access_ok(VERIFY_WRITE, buf, nbytes)) |
return -EINVAL; |
if (copy_to_user(buf, up->data + pos, nbytes)) |
return -EFAULT; |
*ppos += nbytes; |
return nbytes; |
} |
static int uhci_proc_release(struct inode *inode, struct file *file) |
{ |
struct uhci_proc *up = file->private_data; |
kfree(up->data); |
kfree(up); |
return 0; |
} |
static struct file_operations uhci_proc_operations = { |
.open = uhci_proc_open, |
.llseek = uhci_proc_lseek, |
.read = uhci_proc_read, |
// write: uhci_proc_write, |
.release = uhci_proc_release, |
}; |
#endif |
/shark/trunk/drivers/usb/host/ehci.h |
---|
0,0 → 1,481 |
/* |
* Copyright (c) 2001-2002 by David Brownell |
* |
* 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. |
*/ |
#ifndef __LINUX_EHCI_HCD_H |
#define __LINUX_EHCI_HCD_H |
/* definitions used for the EHCI driver */ |
/* statistics can be kept for for tuning/monitoring */ |
struct ehci_stats { |
/* irq usage */ |
unsigned long normal; |
unsigned long error; |
unsigned long reclaim; |
unsigned long lost_iaa; |
/* termination of urbs from core */ |
unsigned long complete; |
unsigned long unlink; |
}; |
/* ehci_hcd->lock guards shared data against other CPUs: |
* ehci_hcd: async, reclaim, periodic (and shadow), ... |
* hcd_dev: ep[] |
* ehci_qh: qh_next, qtd_list |
* ehci_qtd: qtd_list |
* |
* Also, hold this lock when talking to HC registers or |
* when updating hw_* fields in shared qh/qtd/... structures. |
*/ |
#define EHCI_MAX_ROOT_PORTS 15 /* see HCS_N_PORTS */ |
struct ehci_hcd { /* one per controller */ |
spinlock_t lock; |
/* async schedule support */ |
struct ehci_qh *async; |
struct ehci_qh *reclaim; |
int reclaim_ready : 1; |
/* periodic schedule support */ |
#define DEFAULT_I_TDPS 1024 /* some HCs can do less */ |
unsigned periodic_size; |
u32 *periodic; /* hw periodic table */ |
dma_addr_t periodic_dma; |
unsigned i_thresh; /* uframes HC might cache */ |
union ehci_shadow *pshadow; /* mirror hw periodic table */ |
int next_uframe; /* scan periodic, start here */ |
unsigned periodic_sched; /* periodic activity count */ |
/* per root hub port */ |
unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; |
/* glue to PCI and HCD framework */ |
struct usb_hcd hcd; |
struct ehci_caps *caps; |
struct ehci_regs *regs; |
u32 hcs_params; /* cached register copy */ |
/* per-HC memory pools (could be per-PCI-bus, but ...) */ |
struct pci_pool *qh_pool; /* qh per active urb */ |
struct pci_pool *qtd_pool; /* one or more per qh */ |
struct pci_pool *itd_pool; /* itd per iso urb */ |
struct pci_pool *sitd_pool; /* sitd per split iso urb */ |
struct timer_list watchdog; |
struct notifier_block reboot_notifier; |
unsigned long actions; |
unsigned stamp; |
/* irq statistics */ |
#ifdef EHCI_STATS |
struct ehci_stats stats; |
# define COUNT(x) do { (x)++; } while (0) |
#else |
# define COUNT(x) do {} while (0) |
#endif |
}; |
/* unwrap an HCD pointer to get an EHCI_HCD pointer */ |
#define hcd_to_ehci(hcd_ptr) container_of(hcd_ptr, struct ehci_hcd, hcd) |
/* NOTE: urb->transfer_flags expected to not use this bit !!! */ |
#define EHCI_STATE_UNLINK 0x8000 /* urb being unlinked */ |
enum ehci_timer_action { |
TIMER_IO_WATCHDOG, |
TIMER_IAA_WATCHDOG, |
TIMER_ASYNC_SHRINK, |
TIMER_ASYNC_OFF, |
}; |
static inline void |
timer_action_done (struct ehci_hcd *ehci, enum ehci_timer_action action) |
{ |
clear_bit (action, &ehci->actions); |
} |
static inline void |
timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action) |
{ |
if (!test_and_set_bit (action, &ehci->actions)) { |
unsigned long t; |
switch (action) { |
case TIMER_IAA_WATCHDOG: |
t = EHCI_IAA_JIFFIES; |
break; |
case TIMER_IO_WATCHDOG: |
t = EHCI_IO_JIFFIES; |
break; |
case TIMER_ASYNC_OFF: |
t = EHCI_ASYNC_JIFFIES; |
break; |
// case TIMER_ASYNC_SHRINK: |
default: |
t = EHCI_SHRINK_JIFFIES; |
break; |
} |
t += jiffies; |
// all timings except IAA watchdog can be overridden. |
// async queue SHRINK often precedes IAA. while it's ready |
// to go OFF neither can matter, and afterwards the IO |
// watchdog stops unless there's still periodic traffic. |
if (action != TIMER_IAA_WATCHDOG |
&& t > ehci->watchdog.expires |
&& timer_pending (&ehci->watchdog)) |
return; |
mod_timer (&ehci->watchdog, t); |
} |
} |
/*-------------------------------------------------------------------------*/ |
/* EHCI register interface, corresponds to EHCI Revision 0.95 specification */ |
/* Section 2.2 Host Controller Capability Registers */ |
struct ehci_caps { |
u8 length; /* CAPLENGTH - size of this struct */ |
u8 reserved; /* offset 0x1 */ |
u16 hci_version; /* HCIVERSION - offset 0x2 */ |
u32 hcs_params; /* HCSPARAMS - offset 0x4 */ |
#define HCS_DEBUG_PORT(p) (((p)>>20)&0xf) /* bits 23:20, debug port? */ |
#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ |
#define HCS_N_CC(p) (((p)>>12)&0xf) /* bits 15:12, #companion HCs */ |
#define HCS_N_PCC(p) (((p)>>8)&0xf) /* bits 11:8, ports per CC */ |
#define HCS_PORTROUTED(p) ((p)&(1 << 7)) /* true: port routing */ |
#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ |
#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ |
u32 hcc_params; /* HCCPARAMS - offset 0x8 */ |
#define HCC_EXT_CAPS(p) (((p)>>8)&0xff) /* for pci extended caps */ |
#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */ |
#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */ |
#define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */ |
#define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/ |
#define HCC_64BIT_ADDR(p) ((p)&(1)) /* true: can use 64-bit addr */ |
u8 portroute [8]; /* nibbles for routing - offset 0xC */ |
} __attribute__ ((packed)); |
/* Section 2.3 Host Controller Operational Registers */ |
struct ehci_regs { |
/* USBCMD: offset 0x00 */ |
u32 command; |
/* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */ |
#define CMD_PARK (1<<11) /* enable "park" on async qh */ |
#define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */ |
#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ |
#define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */ |
#define CMD_ASE (1<<5) /* async schedule enable */ |
#define CMD_PSE (1<<4) /* periodic schedule enable */ |
/* 3:2 is periodic frame list size */ |
#define CMD_RESET (1<<1) /* reset HC not bus */ |
#define CMD_RUN (1<<0) /* start/stop HC */ |
/* USBSTS: offset 0x04 */ |
u32 status; |
#define STS_ASS (1<<15) /* Async Schedule Status */ |
#define STS_PSS (1<<14) /* Periodic Schedule Status */ |
#define STS_RECL (1<<13) /* Reclamation */ |
#define STS_HALT (1<<12) /* Not running (any reason) */ |
/* some bits reserved */ |
/* these STS_* flags are also intr_enable bits (USBINTR) */ |
#define STS_IAA (1<<5) /* Interrupted on async advance */ |
#define STS_FATAL (1<<4) /* such as some PCI access errors */ |
#define STS_FLR (1<<3) /* frame list rolled over */ |
#define STS_PCD (1<<2) /* port change detect */ |
#define STS_ERR (1<<1) /* "error" completion (overflow, ...) */ |
#define STS_INT (1<<0) /* "normal" completion (short, ...) */ |
/* USBINTR: offset 0x08 */ |
u32 intr_enable; |
/* FRINDEX: offset 0x0C */ |
u32 frame_index; /* current microframe number */ |
/* CTRLDSSEGMENT: offset 0x10 */ |
u32 segment; /* address bits 63:32 if needed */ |
/* PERIODICLISTBASE: offset 0x14 */ |
u32 frame_list; /* points to periodic list */ |
/* ASYNCICLISTADDR: offset 0x18 */ |
u32 async_next; /* address of next async queue head */ |
u32 reserved [9]; |
/* CONFIGFLAG: offset 0x40 */ |
u32 configured_flag; |
#define FLAG_CF (1<<0) /* true: we'll support "high speed" */ |
/* PORTSC: offset 0x44 */ |
u32 port_status [0]; /* up to N_PORTS */ |
/* 31:23 reserved */ |
#define PORT_WKOC_E (1<<22) /* wake on overcurrent (enable) */ |
#define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */ |
#define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */ |
/* 19:16 for port testing */ |
/* 15:14 for using port indicator leds (if HCS_INDICATOR allows) */ |
#define PORT_OWNER (1<<13) /* true: companion hc owns this port */ |
#define PORT_POWER (1<<12) /* true: has power (see PPC) */ |
#define PORT_USB11(x) (((x)&(3<<10))==(1<<10)) /* USB 1.1 device */ |
/* 11:10 for detecting lowspeed devices (reset vs release ownership) */ |
/* 9 reserved */ |
#define PORT_RESET (1<<8) /* reset port */ |
#define PORT_SUSPEND (1<<7) /* suspend port */ |
#define PORT_RESUME (1<<6) /* resume it */ |
#define PORT_OCC (1<<5) /* over current change */ |
#define PORT_OC (1<<4) /* over current active */ |
#define PORT_PEC (1<<3) /* port enable change */ |
#define PORT_PE (1<<2) /* port enable */ |
#define PORT_CSC (1<<1) /* connect status change */ |
#define PORT_CONNECT (1<<0) /* device connected */ |
} __attribute__ ((packed)); |
/*-------------------------------------------------------------------------*/ |
#define QTD_NEXT(dma) cpu_to_le32((u32)dma) |
/* |
* EHCI Specification 0.95 Section 3.5 |
* QTD: describe data transfer components (buffer, direction, ...) |
* See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram". |
* |
* These are associated only with "QH" (Queue Head) structures, |
* used with control, bulk, and interrupt transfers. |
*/ |
struct ehci_qtd { |
/* first part defined by EHCI spec */ |
u32 hw_next; /* see EHCI 3.5.1 */ |
u32 hw_alt_next; /* see EHCI 3.5.2 */ |
u32 hw_token; /* see EHCI 3.5.3 */ |
#define QTD_TOGGLE (1 << 31) /* data toggle */ |
#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) |
#define QTD_IOC (1 << 15) /* interrupt on complete */ |
#define QTD_CERR(tok) (((tok)>>10) & 0x3) |
#define QTD_PID(tok) (((tok)>>8) & 0x3) |
#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ |
#define QTD_STS_HALT (1 << 6) /* halted on error */ |
#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ |
#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ |
#define QTD_STS_XACT (1 << 3) /* device gave illegal response */ |
#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ |
#define QTD_STS_STS (1 << 1) /* split transaction state */ |
#define QTD_STS_PING (1 << 0) /* issue PING? */ |
u32 hw_buf [5]; /* see EHCI 3.5.4 */ |
u32 hw_buf_hi [5]; /* Appendix B */ |
/* the rest is HCD-private */ |
dma_addr_t qtd_dma; /* qtd address */ |
struct list_head qtd_list; /* sw qtd list */ |
struct urb *urb; /* qtd's urb */ |
size_t length; /* length of buffer */ |
} __attribute__ ((aligned (32))); |
/* mask NakCnt+T in qh->hw_alt_next */ |
#define QTD_MASK __constant_cpu_to_le32 (~0x1f) |
#define IS_SHORT_READ(token) (QTD_LENGTH (token) != 0 && QTD_PID (token) == 1) |
/*-------------------------------------------------------------------------*/ |
/* type tag from {qh,itd,sitd,fstn}->hw_next */ |
#define Q_NEXT_TYPE(dma) ((dma) & __constant_cpu_to_le32 (3 << 1)) |
/* values for that type tag */ |
#define Q_TYPE_ITD __constant_cpu_to_le32 (0 << 1) |
#define Q_TYPE_QH __constant_cpu_to_le32 (1 << 1) |
#define Q_TYPE_SITD __constant_cpu_to_le32 (2 << 1) |
#define Q_TYPE_FSTN __constant_cpu_to_le32 (3 << 1) |
/* next async queue entry, or pointer to interrupt/periodic QH */ |
#define QH_NEXT(dma) (cpu_to_le32(((u32)dma)&~0x01f)|Q_TYPE_QH) |
/* for periodic/async schedules and qtd lists, mark end of list */ |
#define EHCI_LIST_END __constant_cpu_to_le32(1) /* "null pointer" to hw */ |
/* |
* Entries in periodic shadow table are pointers to one of four kinds |
* of data structure. That's dictated by the hardware; a type tag is |
* encoded in the low bits of the hardware's periodic schedule. Use |
* Q_NEXT_TYPE to get the tag. |
* |
* For entries in the async schedule, the type tag always says "qh". |
*/ |
union ehci_shadow { |
struct ehci_qh *qh; /* Q_TYPE_QH */ |
struct ehci_itd *itd; /* Q_TYPE_ITD */ |
struct ehci_sitd *sitd; /* Q_TYPE_SITD */ |
struct ehci_fstn *fstn; /* Q_TYPE_FSTN */ |
u32 *hw_next; /* (all types) */ |
void *ptr; |
}; |
/*-------------------------------------------------------------------------*/ |
/* |
* EHCI Specification 0.95 Section 3.6 |
* QH: describes control/bulk/interrupt endpoints |
* See Fig 3-7 "Queue Head Structure Layout". |
* |
* These appear in both the async and (for interrupt) periodic schedules. |
*/ |
struct ehci_qh { |
/* first part defined by EHCI spec */ |
u32 hw_next; /* see EHCI 3.6.1 */ |
u32 hw_info1; /* see EHCI 3.6.2 */ |
#define QH_HEAD 0x00008000 |
u32 hw_info2; /* see EHCI 3.6.2 */ |
u32 hw_current; /* qtd list - see EHCI 3.6.4 */ |
/* qtd overlay (hardware parts of a struct ehci_qtd) */ |
u32 hw_qtd_next; |
u32 hw_alt_next; |
u32 hw_token; |
u32 hw_buf [5]; |
u32 hw_buf_hi [5]; |
/* the rest is HCD-private */ |
dma_addr_t qh_dma; /* address of qh */ |
union ehci_shadow qh_next; /* ptr to qh; or periodic */ |
struct list_head qtd_list; /* sw qtd list */ |
struct ehci_qtd *dummy; |
struct ehci_qh *reclaim; /* next to reclaim */ |
atomic_t refcount; |
unsigned stamp; |
u8 qh_state; |
#define QH_STATE_LINKED 1 /* HC sees this */ |
#define QH_STATE_UNLINK 2 /* HC may still see this */ |
#define QH_STATE_IDLE 3 /* HC doesn't see this */ |
#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on reclaim q */ |
#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */ |
/* periodic schedule info */ |
u8 usecs; /* intr bandwidth */ |
u8 gap_uf; /* uframes split/csplit gap */ |
u8 c_usecs; /* ... split completion bw */ |
unsigned short period; /* polling interval */ |
unsigned short start; /* where polling starts */ |
#define NO_FRAME ((unsigned short)~0) /* pick new start */ |
} __attribute__ ((aligned (32))); |
/*-------------------------------------------------------------------------*/ |
/* |
* EHCI Specification 0.95 Section 3.3 |
* Fig 3-4 "Isochronous Transaction Descriptor (iTD)" |
* |
* Schedule records for high speed iso xfers |
*/ |
struct ehci_itd { |
/* first part defined by EHCI spec */ |
u32 hw_next; /* see EHCI 3.3.1 */ |
u32 hw_transaction [8]; /* see EHCI 3.3.2 */ |
#define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ |
#define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */ |
#define EHCI_ISOC_BABBLE (1<<29) /* babble detected */ |
#define EHCI_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ |
#define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x7fff) |
#define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */ |
u32 hw_bufp [7]; /* see EHCI 3.3.3 */ |
u32 hw_bufp_hi [7]; /* Appendix B */ |
/* the rest is HCD-private */ |
dma_addr_t itd_dma; /* for this itd */ |
union ehci_shadow itd_next; /* ptr to periodic q entry */ |
struct urb *urb; |
struct list_head itd_list; /* list of urb frames' itds */ |
dma_addr_t buf_dma; /* frame's buffer address */ |
/* for now, only one hw_transaction per itd */ |
u32 transaction; |
u16 index; /* in urb->iso_frame_desc */ |
u16 uframe; /* in periodic schedule */ |
u16 usecs; |
} __attribute__ ((aligned (32))); |
/*-------------------------------------------------------------------------*/ |
/* |
* EHCI Specification 0.95 Section 3.4 |
* siTD, aka split-transaction isochronous Transfer Descriptor |
* ... describe low/full speed iso xfers through TT in hubs |
* see Figure 3-5 "Split-transaction Isochronous Transaction Descriptor (siTD) |
*/ |
struct ehci_sitd { |
/* first part defined by EHCI spec */ |
u32 hw_next; |
/* uses bit field macros above - see EHCI 0.95 Table 3-8 */ |
u32 hw_fullspeed_ep; /* see EHCI table 3-9 */ |
u32 hw_uframe; /* see EHCI table 3-10 */ |
u32 hw_tx_results1; /* see EHCI table 3-11 */ |
u32 hw_tx_results2; /* see EHCI table 3-12 */ |
u32 hw_tx_results3; /* see EHCI table 3-12 */ |
u32 hw_backpointer; /* see EHCI table 3-13 */ |
u32 hw_buf_hi [2]; /* Appendix B */ |
/* the rest is HCD-private */ |
dma_addr_t sitd_dma; |
union ehci_shadow sitd_next; /* ptr to periodic q entry */ |
struct urb *urb; |
dma_addr_t buf_dma; /* buffer address */ |
unsigned short usecs; /* start bandwidth */ |
unsigned short c_usecs; /* completion bandwidth */ |
} __attribute__ ((aligned (32))); |
/*-------------------------------------------------------------------------*/ |
/* |
* EHCI Specification 0.96 Section 3.7 |
* Periodic Frame Span Traversal Node (FSTN) |
* |
* Manages split interrupt transactions (using TT) that span frame boundaries |
* into uframes 0/1; see 4.12.2.2. In those uframes, a "save place" FSTN |
* makes the HC jump (back) to a QH to scan for fs/ls QH completions until |
* it hits a "restore" FSTN; then it returns to finish other uframe 0/1 work. |
*/ |
struct ehci_fstn { |
u32 hw_next; /* any periodic q entry */ |
u32 hw_prev; /* qh or EHCI_LIST_END */ |
/* the rest is HCD-private */ |
dma_addr_t fstn_dma; |
union ehci_shadow fstn_next; /* ptr to periodic q entry */ |
} __attribute__ ((aligned (32))); |
/*-------------------------------------------------------------------------*/ |
#define SUBMIT_URB(urb,mem_flags) usb_submit_urb(urb,mem_flags) |
#ifndef DEBUG |
#define STUB_DEBUG_FILES |
#endif /* DEBUG */ |
/*-------------------------------------------------------------------------*/ |
#endif /* __LINUX_EHCI_HCD_H */ |
/shark/trunk/drivers/usb/host/ohci-hcd.c |
---|
0,0 → 1,691 |
/* |
* OHCI HCD (Host Controller Driver) for USB. |
* |
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> |
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> |
* |
* [ Initialisation is based on Linus' ] |
* [ uhci code and gregs ohci fragments ] |
* [ (C) Copyright 1999 Linus Torvalds ] |
* [ (C) Copyright 1999 Gregory P. Smith] |
* |
* |
* OHCI is the main "non-Intel/VIA" standard for USB 1.1 host controller |
* interfaces (though some non-x86 Intel chips use it). It supports |
* smarter hardware than UHCI. A download link for the spec available |
* through the http://www.usb.org website. |
* |
* History: |
* |
* 2003/02/24 show registers in sysfs (Kevin Brosius) |
* |
* 2002/09/03 get rid of ed hashtables, rework periodic scheduling and |
* bandwidth accounting; if debugging, show schedules in driverfs |
* 2002/07/19 fixes to management of ED and schedule state. |
* 2002/06/09 SA-1111 support (Christopher Hoover) |
* 2002/06/01 remember frame when HC won't see EDs any more; use that info |
* to fix urb unlink races caused by interrupt latency assumptions; |
* minor ED field and function naming updates |
* 2002/01/18 package as a patch for 2.5.3; this should match the |
* 2.4.17 kernel modulo some bugs being fixed. |
* |
* 2001/10/18 merge pmac cleanup (Benjamin Herrenschmidt) and bugfixes |
* from post-2.4.5 patches. |
* 2001/09/20 URB_ZERO_PACKET support; hcca_dma portability, OPTi warning |
* 2001/09/07 match PCI PM changes, errnos from Linus' tree |
* 2001/05/05 fork 2.4.5 version into "hcd" framework, cleanup, simplify; |
* pbook pci quirks gone (please fix pbook pci sw!) (db) |
* |
* 2001/04/08 Identify version on module load (gb) |
* 2001/03/24 td/ed hashing to remove bus_to_virt (Steve Longerbeam); |
pci_map_single (db) |
* 2001/03/21 td and dev/ed allocation uses new pci_pool API (db) |
* 2001/03/07 hcca allocation uses pci_alloc_consistent (Steve Longerbeam) |
* |
* 2000/09/26 fixed races in removing the private portion of the urb |
* 2000/09/07 disable bulk and control lists when unlinking the last |
* endpoint descriptor in order to avoid unrecoverable errors on |
* the Lucent chips. (rwc@sgi) |
* 2000/08/29 use bandwidth claiming hooks (thanks Randy!), fix some |
* urb unlink probs, indentation fixes |
* 2000/08/11 various oops fixes mostly affecting iso and cleanup from |
* device unplugs. |
* 2000/06/28 use PCI hotplug framework, for better power management |
* and for Cardbus support (David Brownell) |
* 2000/earlier: fixes for NEC/Lucent chips; suspend/resume handling |
* when the controller loses power; handle UE; cleanup; ... |
* |
* v5.2 1999/12/07 URB 3rd preview, |
* v5.1 1999/11/30 URB 2nd preview, cpia, (usb-scsi) |
* v5.0 1999/11/22 URB Technical preview, Paul Mackerras powerbook susp/resume |
* i386: HUB, Keyboard, Mouse, Printer |
* |
* v4.3 1999/10/27 multiple HCs, bulk_request |
* v4.2 1999/09/05 ISO API alpha, new dev alloc, neg Error-codes |
* v4.1 1999/08/27 Randy Dunlap's - ISO API first impl. |
* v4.0 1999/08/18 |
* v3.0 1999/06/25 |
* v2.1 1999/05/09 code clean up |
* v2.0 1999/05/04 |
* v1.0 1999/04/27 initial release |
* |
* This file is licenced under the GPL. |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#ifdef CONFIG_USB_DEBUG |
# define DEBUG |
#else |
# undef DEBUG |
#endif |
#include <linux/module.h> |
#include <linux/pci.h> |
#include <linux/kernel.h> |
#include <linux/delay.h> |
#include <linux/ioport.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/smp_lock.h> |
#include <linux/errno.h> |
#include <linux/init.h> |
#include <linux/timer.h> |
#include <linux/list.h> |
#include <linux/interrupt.h> /* for in_interrupt () */ |
#include <linux/usb.h> |
#include "../core/hcd.h" |
#include <asm/io.h> |
#include <asm/irq.h> |
#include <asm/system.h> |
#include <asm/unaligned.h> |
#include <asm/byteorder.h> |
#define DRIVER_VERSION "2003 Oct 13" |
#define DRIVER_AUTHOR "Roman Weissgaerber, David Brownell" |
#define DRIVER_DESC "USB 1.1 'Open' Host Controller (OHCI) Driver" |
/*-------------------------------------------------------------------------*/ |
//#define OHCI_VERBOSE_DEBUG /* not always helpful */ |
/* For initializing controller (mask in an HCFS mode too) */ |
#define OHCI_CONTROL_INIT \ |
(OHCI_CTRL_CBSR & 0x3) | OHCI_CTRL_IE | OHCI_CTRL_PLE |
#define OHCI_UNLINK_TIMEOUT (HZ / 10) |
/*-------------------------------------------------------------------------*/ |
static const char hcd_name [] = "ohci_hcd"; |
#include "ohci.h" |
static inline void disable (struct ohci_hcd *ohci) |
{ |
ohci->hcd.state = USB_STATE_HALT; |
} |
#include "ohci-hub.c" |
#include "ohci-dbg.c" |
#include "ohci-mem.c" |
#include "ohci-q.c" |
/*-------------------------------------------------------------------------*/ |
/* |
* queue up an urb for anything except the root hub |
*/ |
static int ohci_urb_enqueue ( |
struct usb_hcd *hcd, |
struct urb *urb, |
int mem_flags |
) { |
struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
struct ed *ed; |
urb_priv_t *urb_priv; |
unsigned int pipe = urb->pipe; |
int i, size = 0; |
unsigned long flags; |
int retval = 0; |
#ifdef OHCI_VERBOSE_DEBUG |
urb_print (urb, "SUB", usb_pipein (pipe)); |
#endif |
/* every endpoint has a ed, locate and maybe (re)initialize it */ |
if (! (ed = ed_get (ohci, urb->dev, pipe, urb->interval))) |
return -ENOMEM; |
/* for the private part of the URB we need the number of TDs (size) */ |
switch (ed->type) { |
case PIPE_CONTROL: |
/* td_submit_urb() doesn't yet handle these */ |
if (urb->transfer_buffer_length > 4096) |
return -EMSGSIZE; |
/* 1 TD for setup, 1 for ACK, plus ... */ |
size = 2; |
/* FALLTHROUGH */ |
// case PIPE_INTERRUPT: |
// case PIPE_BULK: |
default: |
/* one TD for every 4096 Bytes (can be upto 8K) */ |
size += urb->transfer_buffer_length / 4096; |
/* ... and for any remaining bytes ... */ |
if ((urb->transfer_buffer_length % 4096) != 0) |
size++; |
/* ... and maybe a zero length packet to wrap it up */ |
if (size == 0) |
size++; |
else if ((urb->transfer_flags & URB_ZERO_PACKET) != 0 |
&& (urb->transfer_buffer_length |
% usb_maxpacket (urb->dev, pipe, |
usb_pipeout (pipe))) == 0) |
size++; |
break; |
case PIPE_ISOCHRONOUS: /* number of packets from URB */ |
size = urb->number_of_packets; |
break; |
} |
/* allocate the private part of the URB */ |
urb_priv = kmalloc (sizeof (urb_priv_t) + size * sizeof (struct td *), |
mem_flags); |
if (!urb_priv) |
return -ENOMEM; |
memset (urb_priv, 0, sizeof (urb_priv_t) + size * sizeof (struct td *)); |
/* fill the private part of the URB */ |
urb_priv->length = size; |
urb_priv->ed = ed; |
/* allocate the TDs (deferring hash chain updates) */ |
for (i = 0; i < size; i++) { |
urb_priv->td [i] = td_alloc (ohci, mem_flags); |
if (!urb_priv->td [i]) { |
urb_priv->length = i; |
urb_free_priv (ohci, urb_priv); |
return -ENOMEM; |
} |
} |
spin_lock_irqsave (&ohci->lock, flags); |
/* don't submit to a dead HC */ |
if (!HCD_IS_RUNNING(ohci->hcd.state)) { |
retval = -ENODEV; |
goto fail; |
} |
/* schedule the ed if needed */ |
if (ed->state == ED_IDLE) { |
retval = ed_schedule (ohci, ed); |
if (retval < 0) |
goto fail; |
if (ed->type == PIPE_ISOCHRONOUS) { |
u16 frame = le16_to_cpu (ohci->hcca->frame_no); |
/* delay a few frames before the first TD */ |
frame += max_t (u16, 8, ed->interval); |
frame &= ~(ed->interval - 1); |
frame |= ed->branch; |
urb->start_frame = frame; |
/* yes, only URB_ISO_ASAP is supported, and |
* urb->start_frame is never used as input. |
*/ |
} |
} else if (ed->type == PIPE_ISOCHRONOUS) |
urb->start_frame = ed->last_iso + ed->interval; |
/* fill the TDs and link them to the ed; and |
* enable that part of the schedule, if needed |
* and update count of queued periodic urbs |
*/ |
urb->hcpriv = urb_priv; |
td_submit_urb (ohci, urb); |
fail: |
if (retval) |
urb_free_priv (ohci, urb_priv); |
spin_unlock_irqrestore (&ohci->lock, flags); |
return retval; |
} |
/* |
* decouple the URB from the HC queues (TDs, urb_priv); it's |
* already marked using urb->status. reporting is always done |
* asynchronously, and we might be dealing with an urb that's |
* partially transferred, or an ED with other urbs being unlinked. |
*/ |
static int ohci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb) |
{ |
struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
unsigned long flags; |
#ifdef OHCI_VERBOSE_DEBUG |
urb_print (urb, "UNLINK", 1); |
#endif |
spin_lock_irqsave (&ohci->lock, flags); |
if (HCD_IS_RUNNING(ohci->hcd.state)) { |
urb_priv_t *urb_priv; |
/* Unless an IRQ completed the unlink while it was being |
* handed to us, flag it for unlink and giveback, and force |
* some upcoming INTR_SF to call finish_unlinks() |
*/ |
urb_priv = urb->hcpriv; |
if (urb_priv) { |
if (urb_priv->ed->state == ED_OPER) |
start_urb_unlink (ohci, urb_priv->ed); |
} |
} else { |
/* |
* with HC dead, we won't respect hc queue pointers |
* any more ... just clean up every urb's memory. |
*/ |
if (urb->hcpriv) { |
spin_unlock (&ohci->lock); |
finish_urb (ohci, urb, NULL); |
spin_lock (&ohci->lock); |
} |
} |
spin_unlock_irqrestore (&ohci->lock, flags); |
return 0; |
} |
/*-------------------------------------------------------------------------*/ |
/* frees config/altsetting state for endpoints, |
* including ED memory, dummy TD, and bulk/intr data toggle |
*/ |
static void |
ohci_endpoint_disable (struct usb_hcd *hcd, struct hcd_dev *dev, int ep) |
{ |
struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
int epnum = ep & USB_ENDPOINT_NUMBER_MASK; |
unsigned long flags; |
struct ed *ed; |
unsigned limit = 1000; |
/* ASSERT: any requests/urbs are being unlinked */ |
/* ASSERT: nobody can be submitting urbs for this any more */ |
epnum <<= 1; |
if (epnum != 0 && !(ep & USB_DIR_IN)) |
epnum |= 1; |
rescan: |
spin_lock_irqsave (&ohci->lock, flags); |
ed = dev->ep [epnum]; |
if (!ed) |
goto done; |
if (!HCD_IS_RUNNING (ohci->hcd.state)) |
ed->state = ED_IDLE; |
switch (ed->state) { |
case ED_UNLINK: /* wait for hw to finish? */ |
/* major IRQ delivery trouble loses INTR_SF too... */ |
WARN_ON (limit-- == 0); |
spin_unlock_irqrestore (&ohci->lock, flags); |
set_current_state (TASK_UNINTERRUPTIBLE); |
schedule_timeout (1); |
goto rescan; |
case ED_IDLE: /* fully unlinked */ |
if (list_empty (&ed->td_list)) { |
td_free (ohci, ed->dummy); |
ed_free (ohci, ed); |
break; |
} |
/* else FALL THROUGH */ |
default: |
/* caller was supposed to have unlinked any requests; |
* that's not our job. can't recover; must leak ed. |
*/ |
ohci_err (ohci, "leak ed %p (#%d) state %d%s\n", |
ed, epnum, ed->state, |
list_empty (&ed->td_list) ? "" : " (has tds)"); |
td_free (ohci, ed->dummy); |
break; |
} |
dev->ep [epnum] = 0; |
done: |
spin_unlock_irqrestore (&ohci->lock, flags); |
return; |
} |
static int ohci_get_frame (struct usb_hcd *hcd) |
{ |
struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
return le16_to_cpu (ohci->hcca->frame_no); |
} |
/*-------------------------------------------------------------------------* |
* HC functions |
*-------------------------------------------------------------------------*/ |
/* reset the HC and BUS */ |
static int hc_reset (struct ohci_hcd *ohci) |
{ |
u32 temp; |
/* SMM owns the HC? not for long! |
* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. |
*/ |
#ifndef __hppa__ |
if (readl (&ohci->regs->control) & OHCI_CTRL_IR) { |
ohci_dbg (ohci, "USB HC TakeOver from BIOS/SMM\n"); |
/* this timeout is arbitrary. we make it long, so systems |
* depending on usb keyboards may be usable even if the |
* BIOS/SMM code seems pretty broken. |
*/ |
temp = 500; /* arbitrary: five seconds */ |
writel (OHCI_INTR_OC, &ohci->regs->intrenable); |
writel (OHCI_OCR, &ohci->regs->cmdstatus); |
while (readl (&ohci->regs->control) & OHCI_CTRL_IR) { |
wait_ms (10); |
if (--temp == 0) { |
ohci_err (ohci, "USB HC TakeOver failed!\n"); |
return -1; |
} |
} |
} |
#endif |
/* Disable HC interrupts */ |
writel (OHCI_INTR_MIE, &ohci->regs->intrdisable); |
ohci_dbg (ohci, "reset, control = 0x%x\n", |
readl (&ohci->regs->control)); |
/* Reset USB (needed by some controllers); RemoteWakeupConnected |
* saved if boot firmware (BIOS/SMM/...) told us it's connected |
*/ |
ohci->hc_control = readl (&ohci->regs->control); |
ohci->hc_control &= OHCI_CTRL_RWC; /* hcfs 0 = RESET */ |
writel (ohci->hc_control, &ohci->regs->control); |
// flush those pci writes |
(void) readl (&ohci->regs->control); |
wait_ms (50); |
/* HC Reset requires max 10 us delay */ |
writel (OHCI_HCR, &ohci->regs->cmdstatus); |
temp = 30; /* ... allow extra time */ |
while ((readl (&ohci->regs->cmdstatus) & OHCI_HCR) != 0) { |
if (--temp == 0) { |
ohci_err (ohci, "USB HC reset timed out!\n"); |
return -1; |
} |
udelay (1); |
} |
/* now we're in the SUSPEND state ... must go OPERATIONAL |
* within 2msec else HC enters RESUME |
* |
* ... but some hardware won't init fmInterval "by the book" |
* (SiS, OPTi ...), so reset again instead. SiS doesn't need |
* this if we write fmInterval after we're OPERATIONAL. |
*/ |
writel (ohci->hc_control, &ohci->regs->control); |
// flush those pci writes |
(void) readl (&ohci->regs->control); |
return 0; |
} |
/*-------------------------------------------------------------------------*/ |
#define FI 0x2edf /* 12000 bits per frame (-1) */ |
#define LSTHRESH 0x628 /* lowspeed bit threshold */ |
/* Start an OHCI controller, set the BUS operational |
* enable interrupts |
* connect the virtual root hub |
*/ |
static int hc_start (struct ohci_hcd *ohci) |
{ |
u32 mask, tmp; |
struct usb_device *udev; |
struct usb_bus *bus; |
spin_lock_init (&ohci->lock); |
disable (ohci); |
/* Tell the controller where the control and bulk lists are |
* The lists are empty now. */ |
writel (0, &ohci->regs->ed_controlhead); |
writel (0, &ohci->regs->ed_bulkhead); |
/* a reset clears this */ |
writel ((u32) ohci->hcca_dma, &ohci->regs->hcca); |
/* force default fmInterval (we won't adjust it); init thresholds |
* for last FS and LS packets, reserve 90% for periodic. |
*/ |
writel ((((6 * (FI - 210)) / 7) << 16) | FI, &ohci->regs->fminterval); |
writel (((9 * FI) / 10) & 0x3fff, &ohci->regs->periodicstart); |
writel (LSTHRESH, &ohci->regs->lsthresh); |
/* some OHCI implementations are finicky about how they init. |
* bogus values here mean not even enumeration could work. |
*/ |
if ((readl (&ohci->regs->fminterval) & 0x3fff0000) == 0 |
|| !readl (&ohci->regs->periodicstart)) { |
ohci_err (ohci, "init err\n"); |
return -EOVERFLOW; |
} |
/* start controller operations */ |
ohci->hc_control &= OHCI_CTRL_RWC; |
ohci->hc_control |= OHCI_CONTROL_INIT | OHCI_USB_OPER; |
writel (ohci->hc_control, &ohci->regs->control); |
ohci->hcd.state = USB_STATE_RUNNING; |
/* Choose the interrupts we care about now, others later on demand */ |
mask = OHCI_INTR_MIE | OHCI_INTR_UE | OHCI_INTR_WDH; |
writel (mask, &ohci->regs->intrstatus); |
writel (mask, &ohci->regs->intrenable); |
/* handle root hub init quirks ... */ |
tmp = roothub_a (ohci); |
tmp &= ~(RH_A_PSM | RH_A_OCPM); |
if (ohci->flags & OHCI_QUIRK_SUPERIO) { |
/* NSC 87560 and maybe others */ |
tmp |= RH_A_NOCP; |
tmp &= ~(RH_A_POTPGT | RH_A_NPS); |
} else { |
/* hub power always on; required for AMD-756 and some |
* Mac platforms, use this mode everywhere by default |
*/ |
tmp |= RH_A_NPS; |
} |
writel (tmp, &ohci->regs->roothub.a); |
writel (RH_HS_LPSC, &ohci->regs->roothub.status); |
writel (0, &ohci->regs->roothub.b); |
// flush those pci writes |
(void) readl (&ohci->regs->control); |
// POTPGT delay is bits 24-31, in 2 ms units. |
mdelay ((roothub_a (ohci) >> 23) & 0x1fe); |
/* connect the virtual root hub */ |
bus = hcd_to_bus (&ohci->hcd); |
bus->root_hub = udev = usb_alloc_dev (NULL, bus); |
ohci->hcd.state = USB_STATE_RUNNING; |
if (!udev) { |
disable (ohci); |
ohci->hc_control &= ~OHCI_CTRL_HCFS; |
writel (ohci->hc_control, &ohci->regs->control); |
return -ENOMEM; |
} |
udev->speed = USB_SPEED_FULL; |
if (hcd_register_root (&ohci->hcd) != 0) { |
usb_put_dev (udev); |
bus->root_hub = NULL; |
disable (ohci); |
ohci->hc_control &= ~OHCI_CTRL_HCFS; |
writel (ohci->hc_control, &ohci->regs->control); |
return -ENODEV; |
} |
return 0; |
} |
/*-------------------------------------------------------------------------*/ |
/* an interrupt happens */ |
static void ohci_irq (struct usb_hcd *hcd, struct pt_regs *ptregs) |
{ |
struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
struct ohci_regs *regs = ohci->regs; |
int ints; |
/* we can eliminate a (slow) readl() if _only_ WDH caused this irq */ |
if ((ohci->hcca->done_head != 0) |
&& ! (le32_to_cpup (&ohci->hcca->done_head) & 0x01)) { |
ints = OHCI_INTR_WDH; |
/* cardbus/... hardware gone before remove() */ |
} else if ((ints = readl (®s->intrstatus)) == ~(u32)0) { |
disable (ohci); |
ohci_dbg (ohci, "device removed!\n"); |
return; |
/* interrupt for some other device? */ |
} else if ((ints &= readl (®s->intrenable)) == 0) { |
return; |
} |
if (ints & OHCI_INTR_UE) { |
disable (ohci); |
ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n"); |
// e.g. due to PCI Master/Target Abort |
ohci_dump (ohci, 1); |
hc_reset (ohci); |
} |
if (ints & OHCI_INTR_WDH) { |
if (HCD_IS_RUNNING(hcd->state)) |
writel (OHCI_INTR_WDH, ®s->intrdisable); |
dl_done_list (ohci, dl_reverse_done_list (ohci), ptregs); |
if (HCD_IS_RUNNING(hcd->state)) |
writel (OHCI_INTR_WDH, ®s->intrenable); |
} |
/* could track INTR_SO to reduce available PCI/... bandwidth */ |
/* handle any pending URB/ED unlinks, leaving INTR_SF enabled |
* when there's still unlinking to be done (next frame). |
*/ |
spin_lock (&ohci->lock); |
if (ohci->ed_rm_list) |
finish_unlinks (ohci, le16_to_cpu (ohci->hcca->frame_no), |
ptregs); |
if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list |
&& HCD_IS_RUNNING(ohci->hcd.state)) |
writel (OHCI_INTR_SF, ®s->intrdisable); |
spin_unlock (&ohci->lock); |
if (HCD_IS_RUNNING(ohci->hcd.state)) { |
writel (ints, ®s->intrstatus); |
writel (OHCI_INTR_MIE, ®s->intrenable); |
// flush those pci writes |
(void) readl (&ohci->regs->control); |
} |
} |
/*-------------------------------------------------------------------------*/ |
static void ohci_stop (struct usb_hcd *hcd) |
{ |
struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
ohci_dbg (ohci, "stop %s controller (state 0x%02x)\n", |
hcfs2string (ohci->hc_control & OHCI_CTRL_HCFS), |
ohci->hcd.state); |
ohci_dump (ohci, 1); |
if (HCD_IS_RUNNING(ohci->hcd.state)) |
hc_reset (ohci); |
remove_debug_files (ohci); |
ohci_mem_cleanup (ohci); |
if (ohci->hcca) { |
pci_free_consistent (ohci->hcd.pdev, sizeof *ohci->hcca, |
ohci->hcca, ohci->hcca_dma); |
ohci->hcca = NULL; |
ohci->hcca_dma = 0; |
} |
} |
/*-------------------------------------------------------------------------*/ |
// FIXME: this restart logic should be generic, |
// and handle full hcd state cleanup |
/* controller died; cleanup debris, then restart */ |
/* must not be called from interrupt context */ |
#ifdef CONFIG_PM |
static int hc_restart (struct ohci_hcd *ohci) |
{ |
int temp; |
int i; |
disable (ohci); |
if (hcd_to_bus (&ohci->hcd)->root_hub) |
usb_disconnect (&hcd_to_bus (&ohci->hcd)->root_hub); |
/* empty the interrupt branches */ |
for (i = 0; i < NUM_INTS; i++) ohci->load [i] = 0; |
for (i = 0; i < NUM_INTS; i++) ohci->hcca->int_table [i] = 0; |
/* no EDs to remove */ |
ohci->ed_rm_list = NULL; |
/* empty control and bulk lists */ |
ohci->ed_controltail = NULL; |
ohci->ed_bulktail = NULL; |
if ((temp = hc_reset (ohci)) < 0 || (temp = hc_start (ohci)) < 0) { |
ohci_err (ohci, "can't restart, %d\n", temp); |
return temp; |
} else |
ohci_dbg (ohci, "restart complete\n"); |
return 0; |
} |
#endif |
/*-------------------------------------------------------------------------*/ |
#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC |
MODULE_AUTHOR (DRIVER_AUTHOR); |
MODULE_DESCRIPTION (DRIVER_INFO); |
MODULE_LICENSE ("GPL"); |
#ifdef CONFIG_PCI |
#include "ohci-pci.c" |
#endif |
#ifdef CONFIG_SA1111 |
#include "ohci-sa1111.c" |
#endif |
#if !(defined(CONFIG_PCI) || defined(CONFIG_SA1111)) |
#error "missing bus glue for ohci-hcd" |
#endif |
/shark/trunk/drivers/usb/host/ohci-q.c |
---|
0,0 → 1,1048 |
/* |
* OHCI HCD (Host Controller Driver) for USB. |
* |
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> |
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> |
* |
* This file is licenced under the GPL. |
*/ |
static void urb_free_priv (struct ohci_hcd *hc, urb_priv_t *urb_priv) |
{ |
int last = urb_priv->length - 1; |
if (last >= 0) { |
int i; |
struct td *td; |
for (i = 0; i <= last; i++) { |
td = urb_priv->td [i]; |
if (td) |
td_free (hc, td); |
} |
} |
kfree (urb_priv); |
} |
/*-------------------------------------------------------------------------*/ |
/* |
* URB goes back to driver, and isn't reissued. |
* It's completely gone from HC data structures. |
* PRECONDITION: no locks held, irqs blocked (Giveback can call into HCD.) |
*/ |
static void |
finish_urb (struct ohci_hcd *ohci, struct urb *urb, struct pt_regs *regs) |
{ |
// ASSERT (urb->hcpriv != 0); |
urb_free_priv (ohci, urb->hcpriv); |
urb->hcpriv = NULL; |
spin_lock (&urb->lock); |
if (likely (urb->status == -EINPROGRESS)) |
urb->status = 0; |
/* report short control reads right even though the data TD always |
* has TD_R set. (much simpler, but creates the 1-td limit.) |
*/ |
if (unlikely (urb->transfer_flags & URB_SHORT_NOT_OK) |
&& unlikely (usb_pipecontrol (urb->pipe)) |
&& urb->actual_length < urb->transfer_buffer_length |
&& usb_pipein (urb->pipe) |
&& urb->status == 0) { |
urb->status = -EREMOTEIO; |
} |
spin_unlock (&urb->lock); |
// what lock protects these? |
switch (usb_pipetype (urb->pipe)) { |
case PIPE_ISOCHRONOUS: |
hcd_to_bus (&ohci->hcd)->bandwidth_isoc_reqs--; |
break; |
case PIPE_INTERRUPT: |
hcd_to_bus (&ohci->hcd)->bandwidth_int_reqs--; |
break; |
} |
#ifdef OHCI_VERBOSE_DEBUG |
urb_print (urb, "RET", usb_pipeout (urb->pipe)); |
#endif |
usb_hcd_giveback_urb (&ohci->hcd, urb, regs); |
} |
/*-------------------------------------------------------------------------* |
* ED handling functions |
*-------------------------------------------------------------------------*/ |
/* search for the right schedule branch to use for a periodic ed. |
* does some load balancing; returns the branch, or negative errno. |
*/ |
static int balance (struct ohci_hcd *ohci, int interval, int load) |
{ |
int i, branch = -ENOSPC; |
/* iso periods can be huge; iso tds specify frame numbers */ |
if (interval > NUM_INTS) |
interval = NUM_INTS; |
/* search for the least loaded schedule branch of that period |
* that has enough bandwidth left unreserved. |
*/ |
for (i = 0; i < interval ; i++) { |
if (branch < 0 || ohci->load [branch] > ohci->load [i]) { |
#if 1 /* CONFIG_USB_BANDWIDTH */ |
int j; |
/* usb 1.1 says 90% of one frame */ |
for (j = i; j < NUM_INTS; j += interval) { |
if ((ohci->load [j] + load) > 900) |
break; |
} |
if (j < NUM_INTS) |
continue; |
#endif |
branch = i; |
} |
} |
return branch; |
} |
/*-------------------------------------------------------------------------*/ |
/* both iso and interrupt requests have periods; this routine puts them |
* into the schedule tree in the apppropriate place. most iso devices use |
* 1msec periods, but that's not required. |
*/ |
static void periodic_link (struct ohci_hcd *ohci, struct ed *ed) |
{ |
unsigned i; |
ohci_vdbg (ohci, "link %sed %p branch %d [%dus.], interval %d\n", |
(ed->hwINFO & ED_ISO) ? "iso " : "", |
ed, ed->branch, ed->load, ed->interval); |
for (i = ed->branch; i < NUM_INTS; i += ed->interval) { |
struct ed **prev = &ohci->periodic [i]; |
u32 *prev_p = &ohci->hcca->int_table [i]; |
struct ed *here = *prev; |
/* sorting each branch by period (slow before fast) |
* lets us share the faster parts of the tree. |
* (plus maybe: put interrupt eds before iso) |
*/ |
while (here && ed != here) { |
if (ed->interval > here->interval) |
break; |
prev = &here->ed_next; |
prev_p = &here->hwNextED; |
here = *prev; |
} |
if (ed != here) { |
ed->ed_next = here; |
if (here) |
ed->hwNextED = *prev_p; |
wmb (); |
*prev = ed; |
*prev_p = cpu_to_le32p (&ed->dma); |
} |
ohci->load [i] += ed->load; |
} |
hcd_to_bus (&ohci->hcd)->bandwidth_allocated += ed->load / ed->interval; |
} |
/* link an ed into one of the HC chains */ |
static int ed_schedule (struct ohci_hcd *ohci, struct ed *ed) |
{ |
int branch; |
ed->state = ED_OPER; |
ed->ed_prev = 0; |
ed->ed_next = 0; |
ed->hwNextED = 0; |
wmb (); |
/* we care about rm_list when setting CLE/BLE in case the HC was at |
* work on some TD when CLE/BLE was turned off, and isn't quiesced |
* yet. finish_unlinks() restarts as needed, some upcoming INTR_SF. |
* |
* control and bulk EDs are doubly linked (ed_next, ed_prev), but |
* periodic ones are singly linked (ed_next). that's because the |
* periodic schedule encodes a tree like figure 3-5 in the ohci |
* spec: each qh can have several "previous" nodes, and the tree |
* doesn't have unused/idle descriptors. |
*/ |
switch (ed->type) { |
case PIPE_CONTROL: |
if (ohci->ed_controltail == NULL) { |
writel (ed->dma, &ohci->regs->ed_controlhead); |
} else { |
ohci->ed_controltail->ed_next = ed; |
ohci->ed_controltail->hwNextED = cpu_to_le32 (ed->dma); |
} |
ed->ed_prev = ohci->ed_controltail; |
if (!ohci->ed_controltail && !ohci->ed_rm_list) { |
ohci->hc_control |= OHCI_CTRL_CLE; |
writel (0, &ohci->regs->ed_controlcurrent); |
writel (ohci->hc_control, &ohci->regs->control); |
} |
ohci->ed_controltail = ed; |
break; |
case PIPE_BULK: |
if (ohci->ed_bulktail == NULL) { |
writel (ed->dma, &ohci->regs->ed_bulkhead); |
} else { |
ohci->ed_bulktail->ed_next = ed; |
ohci->ed_bulktail->hwNextED = cpu_to_le32 (ed->dma); |
} |
ed->ed_prev = ohci->ed_bulktail; |
if (!ohci->ed_bulktail && !ohci->ed_rm_list) { |
ohci->hc_control |= OHCI_CTRL_BLE; |
writel (0, &ohci->regs->ed_bulkcurrent); |
writel (ohci->hc_control, &ohci->regs->control); |
} |
ohci->ed_bulktail = ed; |
break; |
// case PIPE_INTERRUPT: |
// case PIPE_ISOCHRONOUS: |
default: |
branch = balance (ohci, ed->interval, ed->load); |
if (branch < 0) { |
ohci_dbg (ohci, |
"ERR %d, interval %d msecs, load %d\n", |
branch, ed->interval, ed->load); |
// FIXME if there are TDs queued, fail them! |
return branch; |
} |
ed->branch = branch; |
periodic_link (ohci, ed); |
} |
/* the HC may not see the schedule updates yet, but if it does |
* then they'll be properly ordered. |
*/ |
return 0; |
} |
/*-------------------------------------------------------------------------*/ |
/* scan the periodic table to find and unlink this ED */ |
static void periodic_unlink (struct ohci_hcd *ohci, struct ed *ed) |
{ |
int i; |
for (i = ed->branch; i < NUM_INTS; i += ed->interval) { |
struct ed *temp; |
struct ed **prev = &ohci->periodic [i]; |
u32 *prev_p = &ohci->hcca->int_table [i]; |
while (*prev && (temp = *prev) != ed) { |
prev_p = &temp->hwNextED; |
prev = &temp->ed_next; |
} |
if (*prev) { |
*prev_p = ed->hwNextED; |
*prev = ed->ed_next; |
} |
ohci->load [i] -= ed->load; |
} |
hcd_to_bus (&ohci->hcd)->bandwidth_allocated -= ed->load / ed->interval; |
ohci_vdbg (ohci, "unlink %sed %p branch %d [%dus.], interval %d\n", |
(ed->hwINFO & ED_ISO) ? "iso " : "", |
ed, ed->branch, ed->load, ed->interval); |
} |
/* unlink an ed from one of the HC chains. |
* just the link to the ed is unlinked. |
* the link from the ed still points to another operational ed or 0 |
* so the HC can eventually finish the processing of the unlinked ed |
*/ |
static void ed_deschedule (struct ohci_hcd *ohci, struct ed *ed) |
{ |
ed->hwINFO |= ED_SKIP; |
switch (ed->type) { |
case PIPE_CONTROL: |
if (ed->ed_prev == NULL) { |
if (!ed->hwNextED) { |
ohci->hc_control &= ~OHCI_CTRL_CLE; |
writel (ohci->hc_control, &ohci->regs->control); |
writel (0, &ohci->regs->ed_controlcurrent); |
// post those pci writes |
(void) readl (&ohci->regs->control); |
} |
writel (le32_to_cpup (&ed->hwNextED), |
&ohci->regs->ed_controlhead); |
} else { |
ed->ed_prev->ed_next = ed->ed_next; |
ed->ed_prev->hwNextED = ed->hwNextED; |
} |
if (ohci->ed_controltail == ed) { |
ohci->ed_controltail = ed->ed_prev; |
if (ohci->ed_controltail) |
ohci->ed_controltail->ed_next = 0; |
} else if (ed->ed_next) { |
ed->ed_next->ed_prev = ed->ed_prev; |
} |
break; |
case PIPE_BULK: |
if (ed->ed_prev == NULL) { |
if (!ed->hwNextED) { |
ohci->hc_control &= ~OHCI_CTRL_BLE; |
writel (ohci->hc_control, &ohci->regs->control); |
writel (0, &ohci->regs->ed_bulkcurrent); |
// post those pci writes |
(void) readl (&ohci->regs->control); |
} |
writel (le32_to_cpup (&ed->hwNextED), |
&ohci->regs->ed_bulkhead); |
} else { |
ed->ed_prev->ed_next = ed->ed_next; |
ed->ed_prev->hwNextED = ed->hwNextED; |
} |
if (ohci->ed_bulktail == ed) { |
ohci->ed_bulktail = ed->ed_prev; |
if (ohci->ed_bulktail) |
ohci->ed_bulktail->ed_next = 0; |
} else if (ed->ed_next) { |
ed->ed_next->ed_prev = ed->ed_prev; |
} |
break; |
// case PIPE_INTERRUPT: |
// case PIPE_ISOCHRONOUS: |
default: |
periodic_unlink (ohci, ed); |
break; |
} |
/* NOTE: Except for a couple of exceptionally clean unlink cases |
* (like unlinking the only c/b ED, with no TDs) HCs may still be |
* caching this operational ED (or its address). Safe unlinking |
* involves not marking it ED_IDLE till INTR_SF; we always do that |
* if td_list isn't empty. Otherwise the race is small; but ... |
*/ |
if (ed->state == ED_OPER) { |
ed->state = ED_IDLE; |
ed->hwINFO &= ~(ED_SKIP | ED_DEQUEUE); |
ed->hwHeadP &= ~ED_H; |
wmb (); |
} |
} |
/*-------------------------------------------------------------------------*/ |
/* get and maybe (re)init an endpoint. init _should_ be done only as part |
* of usb_set_configuration() or usb_set_interface() ... but the USB stack |
* isn't very stateful, so we re-init whenever the HC isn't looking. |
*/ |
static struct ed *ed_get ( |
struct ohci_hcd *ohci, |
struct usb_device *udev, |
unsigned int pipe, |
int interval |
) { |
int is_out = !usb_pipein (pipe); |
int type = usb_pipetype (pipe); |
struct hcd_dev *dev = (struct hcd_dev *) udev->hcpriv; |
struct ed *ed; |
unsigned ep; |
unsigned long flags; |
ep = usb_pipeendpoint (pipe) << 1; |
if (type != PIPE_CONTROL && is_out) |
ep |= 1; |
spin_lock_irqsave (&ohci->lock, flags); |
if (!(ed = dev->ep [ep])) { |
struct td *td; |
ed = ed_alloc (ohci, SLAB_ATOMIC); |
if (!ed) { |
/* out of memory */ |
goto done; |
} |
dev->ep [ep] = ed; |
/* dummy td; end of td list for ed */ |
td = td_alloc (ohci, SLAB_ATOMIC); |
if (!td) { |
/* out of memory */ |
ed_free (ohci, ed); |
ed = 0; |
goto done; |
} |
ed->dummy = td; |
ed->hwTailP = cpu_to_le32 (td->td_dma); |
ed->hwHeadP = ed->hwTailP; /* ED_C, ED_H zeroed */ |
ed->state = ED_IDLE; |
ed->type = type; |
} |
/* NOTE: only ep0 currently needs this "re"init logic, during |
* enumeration (after set_address, or if ep0 maxpacket >8). |
*/ |
if (ed->state == ED_IDLE) { |
u32 info; |
info = usb_pipedevice (pipe); |
info |= (ep >> 1) << 7; |
info |= usb_maxpacket (udev, pipe, is_out) << 16; |
info = cpu_to_le32 (info); |
if (udev->speed == USB_SPEED_LOW) |
info |= ED_LOWSPEED; |
/* only control transfers store pids in tds */ |
if (type != PIPE_CONTROL) { |
info |= is_out ? ED_OUT : ED_IN; |
if (type != PIPE_BULK) { |
/* periodic transfers... */ |
if (type == PIPE_ISOCHRONOUS) |
info |= ED_ISO; |
else if (interval > 32) /* iso can be bigger */ |
interval = 32; |
ed->interval = interval; |
ed->load = usb_calc_bus_time ( |
udev->speed, !is_out, |
type == PIPE_ISOCHRONOUS, |
usb_maxpacket (udev, pipe, is_out)) |
/ 1000; |
} |
} |
ed->hwINFO = info; |
} |
done: |
spin_unlock_irqrestore (&ohci->lock, flags); |
return ed; |
} |
/*-------------------------------------------------------------------------*/ |
/* request unlinking of an endpoint from an operational HC. |
* put the ep on the rm_list |
* real work is done at the next start frame (SF) hardware interrupt |
*/ |
static void start_urb_unlink (struct ohci_hcd *ohci, struct ed *ed) |
{ |
ed->hwINFO |= ED_DEQUEUE; |
ed->state = ED_UNLINK; |
ed_deschedule (ohci, ed); |
/* SF interrupt might get delayed; record the frame counter value that |
* indicates when the HC isn't looking at it, so concurrent unlinks |
* behave. frame_no wraps every 2^16 msec, and changes right before |
* SF is triggered. |
*/ |
ed->tick = le16_to_cpu (ohci->hcca->frame_no) + 1; |
/* rm_list is just singly linked, for simplicity */ |
ed->ed_next = ohci->ed_rm_list; |
ed->ed_prev = 0; |
ohci->ed_rm_list = ed; |
/* enable SOF interrupt */ |
if (HCD_IS_RUNNING (ohci->hcd.state)) { |
writel (OHCI_INTR_SF, &ohci->regs->intrstatus); |
writel (OHCI_INTR_SF, &ohci->regs->intrenable); |
// flush those pci writes |
(void) readl (&ohci->regs->control); |
} |
} |
/*-------------------------------------------------------------------------* |
* TD handling functions |
*-------------------------------------------------------------------------*/ |
/* enqueue next TD for this URB (OHCI spec 5.2.8.2) */ |
static void |
td_fill (struct ohci_hcd *ohci, u32 info, |
dma_addr_t data, int len, |
struct urb *urb, int index) |
{ |
struct td *td, *td_pt; |
struct urb_priv *urb_priv = urb->hcpriv; |
int is_iso = info & TD_ISO; |
int hash; |
// ASSERT (index < urb_priv->length); |
/* aim for only one interrupt per urb. mostly applies to control |
* and iso; other urbs rarely need more than one TD per urb. |
* this way, only final tds (or ones with an error) cause IRQs. |
* at least immediately; use DI=6 in case any control request is |
* tempted to die part way through. |
* |
* NOTE: could delay interrupts even for the last TD, and get fewer |
* interrupts ... increasing per-urb latency by sharing interrupts. |
* Drivers that queue bulk urbs may request that behavior. |
*/ |
if (index != (urb_priv->length - 1) |
|| (urb->transfer_flags & URB_NO_INTERRUPT)) |
info |= TD_DI_SET (6); |
/* use this td as the next dummy */ |
td_pt = urb_priv->td [index]; |
/* fill the old dummy TD */ |
td = urb_priv->td [index] = urb_priv->ed->dummy; |
urb_priv->ed->dummy = td_pt; |
td->ed = urb_priv->ed; |
td->next_dl_td = NULL; |
td->index = index; |
td->urb = urb; |
td->data_dma = data; |
if (!len) |
data = 0; |
td->hwINFO = cpu_to_le32 (info); |
if (is_iso) { |
td->hwCBP = cpu_to_le32 (data & 0xFFFFF000); |
td->hwPSW [0] = cpu_to_le16 ((data & 0x0FFF) | 0xE000); |
td->ed->last_iso = info & 0xffff; |
} else { |
td->hwCBP = cpu_to_le32 (data); |
} |
if (data) |
td->hwBE = cpu_to_le32 (data + len - 1); |
else |
td->hwBE = 0; |
td->hwNextTD = cpu_to_le32 (td_pt->td_dma); |
/* append to queue */ |
list_add_tail (&td->td_list, &td->ed->td_list); |
/* hash it for later reverse mapping */ |
hash = TD_HASH_FUNC (td->td_dma); |
td->td_hash = ohci->td_hash [hash]; |
ohci->td_hash [hash] = td; |
/* HC might read the TD (or cachelines) right away ... */ |
wmb (); |
td->ed->hwTailP = td->hwNextTD; |
} |
/*-------------------------------------------------------------------------*/ |
/* Prepare all TDs of a transfer, and queue them onto the ED. |
* Caller guarantees HC is active. |
* Usually the ED is already on the schedule, so TDs might be |
* processed as soon as they're queued. |
*/ |
static void td_submit_urb ( |
struct ohci_hcd *ohci, |
struct urb *urb |
) { |
struct urb_priv *urb_priv = urb->hcpriv; |
dma_addr_t data; |
int data_len = urb->transfer_buffer_length; |
int cnt = 0; |
u32 info = 0; |
int is_out = usb_pipeout (urb->pipe); |
/* OHCI handles the bulk/interrupt data toggles itself. We just |
* use the device toggle bits for resetting, and rely on the fact |
* that resetting toggle is meaningless if the endpoint is active. |
*/ |
if (!usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), is_out)) { |
usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), |
is_out, 1); |
urb_priv->ed->hwHeadP &= ~ED_C; |
} |
urb_priv->td_cnt = 0; |
if (data_len) |
data = urb->transfer_dma; |
else |
data = 0; |
/* NOTE: TD_CC is set so we can tell which TDs the HC processed by |
* using TD_CC_GET, as well as by seeing them on the done list. |
* (CC = NotAccessed ... 0x0F, or 0x0E in PSWs for ISO.) |
*/ |
switch (urb_priv->ed->type) { |
/* Bulk and interrupt are identical except for where in the schedule |
* their EDs live. |
*/ |
case PIPE_INTERRUPT: |
/* ... and periodic urbs have extra accounting */ |
hcd_to_bus (&ohci->hcd)->bandwidth_int_reqs++; |
/* FALLTHROUGH */ |
case PIPE_BULK: |
info = is_out |
? TD_T_TOGGLE | TD_CC | TD_DP_OUT |
: TD_T_TOGGLE | TD_CC | TD_DP_IN; |
/* TDs _could_ transfer up to 8K each */ |
while (data_len > 4096) { |
td_fill (ohci, info, data, 4096, urb, cnt); |
data += 4096; |
data_len -= 4096; |
cnt++; |
} |
/* maybe avoid ED halt on final TD short read */ |
if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) |
info |= TD_R; |
td_fill (ohci, info, data, data_len, urb, cnt); |
cnt++; |
if ((urb->transfer_flags & URB_ZERO_PACKET) |
&& cnt < urb_priv->length) { |
td_fill (ohci, info, 0, 0, urb, cnt); |
cnt++; |
} |
/* maybe kickstart bulk list */ |
if (urb_priv->ed->type == PIPE_BULK) { |
wmb (); |
writel (OHCI_BLF, &ohci->regs->cmdstatus); |
} |
break; |
/* control manages DATA0/DATA1 toggle per-request; SETUP resets it, |
* any DATA phase works normally, and the STATUS ack is special. |
*/ |
case PIPE_CONTROL: |
info = TD_CC | TD_DP_SETUP | TD_T_DATA0; |
td_fill (ohci, info, urb->setup_dma, 8, urb, cnt++); |
if (data_len > 0) { |
info = TD_CC | TD_R | TD_T_DATA1; |
info |= is_out ? TD_DP_OUT : TD_DP_IN; |
/* NOTE: mishandles transfers >8K, some >4K */ |
td_fill (ohci, info, data, data_len, urb, cnt++); |
} |
info = is_out |
? TD_CC | TD_DP_IN | TD_T_DATA1 |
: TD_CC | TD_DP_OUT | TD_T_DATA1; |
td_fill (ohci, info, data, 0, urb, cnt++); |
/* maybe kickstart control list */ |
wmb (); |
writel (OHCI_CLF, &ohci->regs->cmdstatus); |
break; |
/* ISO has no retransmit, so no toggle; and it uses special TDs. |
* Each TD could handle multiple consecutive frames (interval 1); |
* we could often reduce the number of TDs here. |
*/ |
case PIPE_ISOCHRONOUS: |
for (cnt = 0; cnt < urb->number_of_packets; cnt++) { |
int frame = urb->start_frame; |
// FIXME scheduling should handle frame counter |
// roll-around ... exotic case (and OHCI has |
// a 2^16 iso range, vs other HCs max of 2^10) |
frame += cnt * urb->interval; |
frame &= 0xffff; |
td_fill (ohci, TD_CC | TD_ISO | frame, |
data + urb->iso_frame_desc [cnt].offset, |
urb->iso_frame_desc [cnt].length, urb, cnt); |
} |
hcd_to_bus (&ohci->hcd)->bandwidth_isoc_reqs++; |
break; |
} |
// ASSERT (urb_priv->length == cnt); |
} |
/*-------------------------------------------------------------------------* |
* Done List handling functions |
*-------------------------------------------------------------------------*/ |
/* calculate transfer length/status and update the urb |
* PRECONDITION: irqsafe (only for urb->status locking) |
*/ |
static void td_done (struct ohci_hcd *ohci, struct urb *urb, struct td *td) |
{ |
u32 tdINFO = le32_to_cpup (&td->hwINFO); |
int cc = 0; |
list_del (&td->td_list); |
/* ISO ... drivers see per-TD length/status */ |
if (tdINFO & TD_ISO) { |
u16 tdPSW = le16_to_cpu (td->hwPSW [0]); |
int dlen = 0; |
/* NOTE: assumes FC in tdINFO == 0 (and MAXPSW == 1) */ |
cc = (tdPSW >> 12) & 0xF; |
if (tdINFO & TD_CC) /* hc didn't touch? */ |
return; |
if (usb_pipeout (urb->pipe)) |
dlen = urb->iso_frame_desc [td->index].length; |
else { |
/* short reads are always OK for ISO */ |
if (cc == TD_DATAUNDERRUN) |
cc = TD_CC_NOERROR; |
dlen = tdPSW & 0x3ff; |
} |
urb->actual_length += dlen; |
urb->iso_frame_desc [td->index].actual_length = dlen; |
urb->iso_frame_desc [td->index].status = cc_to_error [cc]; |
if (cc != TD_CC_NOERROR) |
ohci_vdbg (ohci, |
"urb %p iso td %p (%d) len %d cc %d\n", |
urb, td, 1 + td->index, dlen, cc); |
/* BULK, INT, CONTROL ... drivers see aggregate length/status, |
* except that "setup" bytes aren't counted and "short" transfers |
* might not be reported as errors. |
*/ |
} else { |
int type = usb_pipetype (urb->pipe); |
u32 tdBE = le32_to_cpup (&td->hwBE); |
cc = TD_CC_GET (tdINFO); |
/* control endpoints only have soft stalls */ |
if (type != PIPE_CONTROL && cc == TD_CC_STALL) |
usb_endpoint_halt (urb->dev, |
usb_pipeendpoint (urb->pipe), |
usb_pipeout (urb->pipe)); |
/* update packet status if needed (short is normally ok) */ |
if (cc == TD_DATAUNDERRUN |
&& !(urb->transfer_flags & URB_SHORT_NOT_OK)) |
cc = TD_CC_NOERROR; |
if (cc != TD_CC_NOERROR && cc < 0x0E) { |
spin_lock (&urb->lock); |
if (urb->status == -EINPROGRESS) |
urb->status = cc_to_error [cc]; |
spin_unlock (&urb->lock); |
} |
/* count all non-empty packets except control SETUP packet */ |
if ((type != PIPE_CONTROL || td->index != 0) && tdBE != 0) { |
if (td->hwCBP == 0) |
urb->actual_length += tdBE - td->data_dma + 1; |
else |
urb->actual_length += |
le32_to_cpup (&td->hwCBP) |
- td->data_dma; |
} |
if (cc != TD_CC_NOERROR && cc < 0x0E) |
ohci_vdbg (ohci, |
"urb %p td %p (%d) cc %d, len=%d/%d\n", |
urb, td, 1 + td->index, cc, |
urb->actual_length, |
urb->transfer_buffer_length); |
} |
} |
/*-------------------------------------------------------------------------*/ |
static inline struct td * |
ed_halted (struct ohci_hcd *ohci, struct td *td, int cc, struct td *rev) |
{ |
struct urb *urb = td->urb; |
struct ed *ed = td->ed; |
struct list_head *tmp = td->td_list.next; |
u32 toggle = ed->hwHeadP & ED_C; |
/* clear ed halt; this is the td that caused it, but keep it inactive |
* until its urb->complete() has a chance to clean up. |
*/ |
ed->hwINFO |= ED_SKIP; |
wmb (); |
ed->hwHeadP &= ~ED_H; |
/* put any later tds from this urb onto the donelist, after 'td', |
* order won't matter here: no errors, and nothing was transferred. |
* also patch the ed so it looks as if those tds completed normally. |
*/ |
while (tmp != &ed->td_list) { |
struct td *next; |
u32 info; |
next = list_entry (tmp, struct td, td_list); |
tmp = next->td_list.next; |
if (next->urb != urb) |
break; |
/* NOTE: if multi-td control DATA segments get supported, |
* this urb had one of them, this td wasn't the last td |
* in that segment (TD_R clear), this ed halted because |
* of a short read, _and_ URB_SHORT_NOT_OK is clear ... |
* then we need to leave the control STATUS packet queued |
* and clear ED_SKIP. |
*/ |
info = next->hwINFO; |
info |= cpu_to_le32 (TD_DONE); |
info &= ~cpu_to_le32 (TD_CC); |
next->hwINFO = info; |
next->next_dl_td = rev; |
rev = next; |
if (ed->hwTailP == cpu_to_le32 (next->td_dma)) |
ed->hwTailP = next->hwNextTD; |
ed->hwHeadP = next->hwNextTD | toggle; |
} |
/* help for troubleshooting: report anything that |
* looks odd ... that doesn't include protocol stalls |
* (or maybe some other things) |
*/ |
switch (cc) { |
case TD_DATAUNDERRUN: |
if ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0) |
break; |
/* fallthrough */ |
case TD_CC_STALL: |
if (usb_pipecontrol (urb->pipe)) |
break; |
/* fallthrough */ |
default: |
ohci_dbg (ohci, |
"urb %p path %s ep%d%s %08x cc %d --> status %d\n", |
urb, urb->dev->devpath, |
usb_pipeendpoint (urb->pipe), |
usb_pipein (urb->pipe) ? "in" : "out", |
le32_to_cpu (td->hwINFO), |
cc, cc_to_error [cc]); |
} |
return rev; |
} |
/* replies to the request have to be on a FIFO basis so |
* we unreverse the hc-reversed done-list |
*/ |
static struct td *dl_reverse_done_list (struct ohci_hcd *ohci) |
{ |
u32 td_dma; |
struct td *td_rev = NULL; |
struct td *td = NULL; |
unsigned long flags; |
spin_lock_irqsave (&ohci->lock, flags); |
td_dma = le32_to_cpup (&ohci->hcca->done_head); |
ohci->hcca->done_head = 0; |
/* get TD from hc's singly linked list, and |
* prepend to ours. ed->td_list changes later. |
*/ |
while (td_dma) { |
int cc; |
td = dma_to_td (ohci, td_dma); |
if (!td) { |
ohci_err (ohci, "bad entry %8x\n", td_dma); |
break; |
} |
td->hwINFO |= cpu_to_le32 (TD_DONE); |
cc = TD_CC_GET (le32_to_cpup (&td->hwINFO)); |
/* Non-iso endpoints can halt on error; un-halt, |
* and dequeue any other TDs from this urb. |
* No other TD could have caused the halt. |
*/ |
if (cc != TD_CC_NOERROR && (td->ed->hwHeadP & ED_H)) |
td_rev = ed_halted (ohci, td, cc, td_rev); |
td->next_dl_td = td_rev; |
td_rev = td; |
td_dma = le32_to_cpup (&td->hwNextTD); |
} |
spin_unlock_irqrestore (&ohci->lock, flags); |
return td_rev; |
} |
/*-------------------------------------------------------------------------*/ |
/* wrap-aware logic stolen from <linux/jiffies26.h> */ |
#define tick_before(t1,t2) ((((s16)(t1))-((s16)(t2))) < 0) |
/* there are some urbs/eds to unlink; called in_irq(), with HCD locked */ |
static void |
finish_unlinks (struct ohci_hcd *ohci, u16 tick, struct pt_regs *regs) |
{ |
struct ed *ed, **last; |
rescan_all: |
for (last = &ohci->ed_rm_list, ed = *last; ed != NULL; ed = *last) { |
struct list_head *entry, *tmp; |
int completed, modified; |
u32 *prev; |
/* only take off EDs that the HC isn't using, accounting for |
* frame counter wraps. |
*/ |
if (tick_before (tick, ed->tick) |
&& HCD_IS_RUNNING(ohci->hcd.state)) { |
last = &ed->ed_next; |
continue; |
} |
/* reentrancy: if we drop the schedule lock, someone might |
* have modified this list. normally it's just prepending |
* entries (which we'd ignore), but paranoia won't hurt. |
*/ |
*last = ed->ed_next; |
ed->ed_next = 0; |
modified = 0; |
/* unlink urbs as requested, but rescan the list after |
* we call a completion since it might have unlinked |
* another (earlier) urb |
*/ |
rescan_this: |
completed = 0; |
prev = &ed->hwHeadP; |
list_for_each_safe (entry, tmp, &ed->td_list) { |
struct td *td; |
struct urb *urb; |
urb_priv_t *urb_priv; |
u32 savebits; |
td = list_entry (entry, struct td, td_list); |
urb = td->urb; |
urb_priv = td->urb->hcpriv; |
if (urb->status == -EINPROGRESS) { |
prev = &td->hwNextTD; |
continue; |
} |
/* patch pointers hc uses ... tail, if we're removing |
* an otherwise active td, and whatever td pointer |
* points to this td |
*/ |
if (ed->hwTailP == cpu_to_le32 (td->td_dma)) |
ed->hwTailP = td->hwNextTD; |
savebits = *prev & ~cpu_to_le32 (TD_MASK); |
*prev = td->hwNextTD | savebits; |
/* HC may have partly processed this TD */ |
td_done (ohci, urb, td); |
urb_priv->td_cnt++; |
/* if URB is done, clean up */ |
if (urb_priv->td_cnt == urb_priv->length) { |
modified = completed = 1; |
spin_unlock (&ohci->lock); |
finish_urb (ohci, urb, regs); |
spin_lock (&ohci->lock); |
} |
} |
if (completed && !list_empty (&ed->td_list)) |
goto rescan_this; |
/* ED's now officially unlinked, hc doesn't see */ |
ed->state = ED_IDLE; |
ed->hwINFO &= ~(ED_SKIP | ED_DEQUEUE); |
ed->hwHeadP &= ~ED_H; |
ed->hwNextED = 0; |
/* but if there's work queued, reschedule */ |
if (!list_empty (&ed->td_list)) { |
if (HCD_IS_RUNNING(ohci->hcd.state)) |
ed_schedule (ohci, ed); |
} |
if (modified) |
goto rescan_all; |
} |
/* maybe reenable control and bulk lists */ |
if (HCD_IS_RUNNING(ohci->hcd.state) && !ohci->ed_rm_list) { |
u32 command = 0, control = 0; |
if (ohci->ed_controltail) { |
command |= OHCI_CLF; |
if (!(ohci->hc_control & OHCI_CTRL_CLE)) { |
control |= OHCI_CTRL_CLE; |
writel (0, &ohci->regs->ed_controlcurrent); |
} |
} |
if (ohci->ed_bulktail) { |
command |= OHCI_BLF; |
if (!(ohci->hc_control & OHCI_CTRL_BLE)) { |
control |= OHCI_CTRL_BLE; |
writel (0, &ohci->regs->ed_bulkcurrent); |
} |
} |
/* CLE/BLE to enable, CLF/BLF to (maybe) kickstart */ |
if (control) { |
ohci->hc_control |= control; |
writel (ohci->hc_control, &ohci->regs->control); |
} |
if (command) |
writel (command, &ohci->regs->cmdstatus); |
} |
} |
/*-------------------------------------------------------------------------*/ |
/* |
* Process normal completions (error or success) and clean the schedules. |
* |
* This is the main path for handing urbs back to drivers. The only other |
* path is finish_unlinks(), which unlinks URBs using ed_rm_list, instead of |
* scanning the (re-reversed) donelist as this does. |
*/ |
static void |
dl_done_list (struct ohci_hcd *ohci, struct td *td, struct pt_regs *regs) |
{ |
unsigned long flags; |
spin_lock_irqsave (&ohci->lock, flags); |
while (td) { |
struct td *td_next = td->next_dl_td; |
struct urb *urb = td->urb; |
urb_priv_t *urb_priv = urb->hcpriv; |
struct ed *ed = td->ed; |
/* update URB's length and status from TD */ |
td_done (ohci, urb, td); |
urb_priv->td_cnt++; |
/* If all this urb's TDs are done, call complete() */ |
if (urb_priv->td_cnt == urb_priv->length) { |
spin_unlock (&ohci->lock); |
finish_urb (ohci, urb, regs); |
spin_lock (&ohci->lock); |
} |
/* clean schedule: unlink EDs that are no longer busy */ |
if (list_empty (&ed->td_list)) |
ed_deschedule (ohci, ed); |
/* ... reenabling halted EDs only after fault cleanup */ |
else if ((ed->hwINFO & (ED_SKIP | ED_DEQUEUE)) == ED_SKIP) { |
td = list_entry (ed->td_list.next, struct td, td_list); |
if (!(td->hwINFO & TD_DONE)) { |
ed->hwINFO &= ~ED_SKIP; |
/* ... hc may need waking-up */ |
switch (ed->type) { |
case PIPE_CONTROL: |
writel (OHCI_CLF, |
&ohci->regs->cmdstatus); |
break; |
case PIPE_BULK: |
writel (OHCI_BLF, |
&ohci->regs->cmdstatus); |
break; |
} |
} |
} |
td = td_next; |
} |
spin_unlock_irqrestore (&ohci->lock, flags); |
} |
/shark/trunk/drivers/usb/host/ehci-hub.c |
---|
0,0 → 1,344 |
/* |
* Copyright (c) 2001-2002 by David Brownell |
* |
* 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. |
*/ |
/* this file is part of ehci-hcd.c */ |
/*-------------------------------------------------------------------------*/ |
/* |
* EHCI Root Hub ... the nonsharable stuff |
* |
* Registers don't need cpu_to_le32, that happens transparently |
*/ |
/*-------------------------------------------------------------------------*/ |
static int check_reset_complete ( |
struct ehci_hcd *ehci, |
int index, |
int port_status |
) { |
if (!(port_status & PORT_CONNECT)) { |
ehci->reset_done [index] = 0; |
return port_status; |
} |
/* if reset finished and it's still not enabled -- handoff */ |
if (!(port_status & PORT_PE)) { |
ehci_dbg (ehci, "port %d full speed --> companion\n", |
index + 1); |
// what happens if HCS_N_CC(params) == 0 ? |
port_status |= PORT_OWNER; |
writel (port_status, &ehci->regs->port_status [index]); |
} else |
ehci_dbg (ehci, "port %d high speed\n", index + 1); |
return port_status; |
} |
/*-------------------------------------------------------------------------*/ |
/* build "status change" packet (one or two bytes) from HC registers */ |
static int |
ehci_hub_status_data (struct usb_hcd *hcd, char *buf) |
{ |
struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
u32 temp, status = 0; |
int ports, i, retval = 1; |
unsigned long flags; |
/* init status to no-changes */ |
buf [0] = 0; |
ports = HCS_N_PORTS (ehci->hcs_params); |
if (ports > 7) { |
buf [1] = 0; |
retval++; |
} |
/* no hub change reports (bit 0) for now (power, ...) */ |
/* port N changes (bit N)? */ |
spin_lock_irqsave (&ehci->lock, flags); |
for (i = 0; i < ports; i++) { |
temp = readl (&ehci->regs->port_status [i]); |
if (temp & PORT_OWNER) { |
/* don't report this in GetPortStatus */ |
if (temp & PORT_CSC) { |
temp &= ~PORT_CSC; |
writel (temp, &ehci->regs->port_status [i]); |
} |
continue; |
} |
if (!(temp & PORT_CONNECT)) |
ehci->reset_done [i] = 0; |
if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0) { |
if (i < 7) |
buf [0] |= 1 << (i + 1); |
else |
buf [1] |= 1 << (i - 7); |
status = STS_PCD; |
} |
} |
spin_unlock_irqrestore (&ehci->lock, flags); |
return status ? retval : 0; |
} |
/*-------------------------------------------------------------------------*/ |
static void |
ehci_hub_descriptor ( |
struct ehci_hcd *ehci, |
struct usb_hub_descriptor *desc |
) { |
int ports = HCS_N_PORTS (ehci->hcs_params); |
u16 temp; |
desc->bDescriptorType = 0x29; |
desc->bPwrOn2PwrGood = 0; /* FIXME: f(system power) */ |
desc->bHubContrCurrent = 0; |
desc->bNbrPorts = ports; |
temp = 1 + (ports / 8); |
desc->bDescLength = 7 + 2 * temp; |
/* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ |
memset (&desc->bitmap [0], 0, temp); |
memset (&desc->bitmap [temp], 0xff, temp); |
temp = 0x0008; /* per-port overcurrent reporting */ |
if (HCS_PPC (ehci->hcs_params)) |
temp |= 0x0001; /* per-port power control */ |
if (HCS_INDICATOR (ehci->hcs_params)) |
temp |= 0x0080; /* per-port indicators (LEDs) */ |
desc->wHubCharacteristics = cpu_to_le16 (temp); |
} |
/*-------------------------------------------------------------------------*/ |
static int ehci_hub_control ( |
struct usb_hcd *hcd, |
u16 typeReq, |
u16 wValue, |
u16 wIndex, |
char *buf, |
u16 wLength |
) { |
struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
int ports = HCS_N_PORTS (ehci->hcs_params); |
u32 temp, status; |
unsigned long flags; |
int retval = 0; |
/* |
* FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. |
* HCS_INDICATOR may say we can change LEDs to off/amber/green. |
* (track current state ourselves) ... blink for diagnostics, |
* power, "this is the one", etc. EHCI spec supports this. |
*/ |
spin_lock_irqsave (&ehci->lock, flags); |
switch (typeReq) { |
case ClearHubFeature: |
switch (wValue) { |
case C_HUB_LOCAL_POWER: |
case C_HUB_OVER_CURRENT: |
/* no hub-wide feature/status flags */ |
break; |
default: |
goto error; |
} |
break; |
case ClearPortFeature: |
if (!wIndex || wIndex > ports) |
goto error; |
wIndex--; |
temp = readl (&ehci->regs->port_status [wIndex]); |
if (temp & PORT_OWNER) |
break; |
switch (wValue) { |
case USB_PORT_FEAT_ENABLE: |
writel (temp & ~PORT_PE, |
&ehci->regs->port_status [wIndex]); |
break; |
case USB_PORT_FEAT_C_ENABLE: |
writel (temp | PORT_PEC, |
&ehci->regs->port_status [wIndex]); |
break; |
case USB_PORT_FEAT_SUSPEND: |
case USB_PORT_FEAT_C_SUSPEND: |
/* ? */ |
break; |
case USB_PORT_FEAT_POWER: |
if (HCS_PPC (ehci->hcs_params)) |
writel (temp & ~PORT_POWER, |
&ehci->regs->port_status [wIndex]); |
break; |
case USB_PORT_FEAT_C_CONNECTION: |
writel (temp | PORT_CSC, |
&ehci->regs->port_status [wIndex]); |
break; |
case USB_PORT_FEAT_C_OVER_CURRENT: |
writel (temp | PORT_OCC, |
&ehci->regs->port_status [wIndex]); |
break; |
case USB_PORT_FEAT_C_RESET: |
/* GetPortStatus clears reset */ |
break; |
default: |
goto error; |
} |
readl (&ehci->regs->command); /* unblock posted write */ |
break; |
case GetHubDescriptor: |
ehci_hub_descriptor (ehci, (struct usb_hub_descriptor *) |
buf); |
break; |
case GetHubStatus: |
/* no hub-wide feature/status flags */ |
memset (buf, 0, 4); |
//cpu_to_le32s ((u32 *) buf); |
break; |
case GetPortStatus: |
if (!wIndex || wIndex > ports) |
goto error; |
wIndex--; |
status = 0; |
temp = readl (&ehci->regs->port_status [wIndex]); |
// wPortChange bits |
if (temp & PORT_CSC) |
status |= 1 << USB_PORT_FEAT_C_CONNECTION; |
if (temp & PORT_PEC) |
status |= 1 << USB_PORT_FEAT_C_ENABLE; |
// USB_PORT_FEAT_C_SUSPEND |
if (temp & PORT_OCC) |
status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT; |
/* whoever resets must GetPortStatus to complete it!! */ |
if ((temp & PORT_RESET) |
&& time_after (jiffies26, |
ehci->reset_done [wIndex])) { |
status |= 1 << USB_PORT_FEAT_C_RESET; |
/* force reset to complete */ |
writel (temp & ~PORT_RESET, |
&ehci->regs->port_status [wIndex]); |
do { |
temp = readl ( |
&ehci->regs->port_status [wIndex]); |
udelay (10); |
} while (temp & PORT_RESET); |
/* see what we found out */ |
temp = check_reset_complete (ehci, wIndex, temp); |
} |
// don't show wPortStatus if it's owned by a companion hc |
if (!(temp & PORT_OWNER)) { |
if (temp & PORT_CONNECT) { |
status |= 1 << USB_PORT_FEAT_CONNECTION; |
status |= 1 << USB_PORT_FEAT_HIGHSPEED; |
} |
if (temp & PORT_PE) |
status |= 1 << USB_PORT_FEAT_ENABLE; |
if (temp & PORT_SUSPEND) |
status |= 1 << USB_PORT_FEAT_SUSPEND; |
if (temp & PORT_OC) |
status |= 1 << USB_PORT_FEAT_OVER_CURRENT; |
if (temp & PORT_RESET) |
status |= 1 << USB_PORT_FEAT_RESET; |
if (temp & PORT_POWER) |
status |= 1 << USB_PORT_FEAT_POWER; |
} |
#ifndef EHCI_VERBOSE_DEBUG |
if (status & ~0xffff) /* only if wPortChange is interesting */ |
#endif |
dbg_port (ehci, "GetStatus", wIndex + 1, temp); |
// we "know" this alignment is good, caller used kmalloc()... |
*((u32 *) buf) = cpu_to_le32 (status); |
break; |
case SetHubFeature: |
switch (wValue) { |
case C_HUB_LOCAL_POWER: |
case C_HUB_OVER_CURRENT: |
/* no hub-wide feature/status flags */ |
break; |
default: |
goto error; |
} |
break; |
case SetPortFeature: |
if (!wIndex || wIndex > ports) |
goto error; |
wIndex--; |
temp = readl (&ehci->regs->port_status [wIndex]); |
if (temp & PORT_OWNER) |
break; |
switch (wValue) { |
case USB_PORT_FEAT_SUSPEND: |
writel (temp | PORT_SUSPEND, |
&ehci->regs->port_status [wIndex]); |
break; |
case USB_PORT_FEAT_POWER: |
if (HCS_PPC (ehci->hcs_params)) |
writel (temp | PORT_POWER, |
&ehci->regs->port_status [wIndex]); |
break; |
case USB_PORT_FEAT_RESET: |
/* line status bits may report this as low speed */ |
if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT |
&& PORT_USB11 (temp)) { |
ehci_dbg (ehci, |
"port %d low speed --> companion\n", |
wIndex + 1); |
temp |= PORT_OWNER; |
} else { |
ehci_vdbg (ehci, "port %d reset\n", wIndex + 1); |
temp |= PORT_RESET; |
temp &= ~PORT_PE; |
/* |
* caller must wait, then call GetPortStatus |
* usb 2.0 spec says 50 ms resets on root |
*/ |
ehci->reset_done [wIndex] = jiffies26 |
+ ((50 /* msec */ * HZ) / 1000); |
} |
writel (temp, &ehci->regs->port_status [wIndex]); |
break; |
default: |
goto error; |
} |
readl (&ehci->regs->command); /* unblock posted writes */ |
break; |
default: |
error: |
/* "stall" on error */ |
retval = -EPIPE; |
} |
spin_unlock_irqrestore (&ehci->lock, flags); |
return retval; |
} |
/shark/trunk/drivers/usb/host/ehci-mem.c |
---|
0,0 → 1,249 |
/* |
* Copyright (c) 2001 by David Brownell |
* |
* 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. |
*/ |
/* this file is part of ehci-hcd.c */ |
/*-------------------------------------------------------------------------*/ |
/* |
* There's basically three types of memory: |
* - data used only by the HCD ... kmalloc is fine |
* - async and periodic schedules, shared by HC and HCD ... these |
* need to use pci_pool or pci_alloc_consistent |
* - driver buffers, read/written by HC ... single shot DMA mapped |
* |
* There's also PCI "register" data, which is memory mapped. |
* No memory seen by this driver is pagable. |
*/ |
/*-------------------------------------------------------------------------*/ |
/* |
* Allocator / cleanup for the per device structure |
* Called by hcd init / removal code |
*/ |
static struct usb_hcd *ehci_hcd_alloc (void) |
{ |
struct ehci_hcd *ehci; |
ehci = (struct ehci_hcd *) |
kmalloc (sizeof (struct ehci_hcd), GFP_KERNEL); |
if (ehci != 0) { |
memset (ehci, 0, sizeof (struct ehci_hcd)); |
ehci->hcd.product_desc = "EHCI Host Controller"; |
return &ehci->hcd; |
} |
return 0; |
} |
static void ehci_hcd_free (struct usb_hcd *hcd) |
{ |
kfree (hcd_to_ehci (hcd)); |
} |
/*-------------------------------------------------------------------------*/ |
/* Allocate the key transfer structures from the previously allocated pool */ |
static inline void ehci_qtd_init (struct ehci_qtd *qtd, dma_addr_t dma) |
{ |
memset (qtd, 0, sizeof *qtd); |
qtd->qtd_dma = dma; |
qtd->hw_token = cpu_to_le32 (QTD_STS_HALT); |
qtd->hw_next = EHCI_LIST_END; |
qtd->hw_alt_next = EHCI_LIST_END; |
INIT_LIST_HEAD (&qtd->qtd_list); |
} |
static struct ehci_qtd *ehci_qtd_alloc (struct ehci_hcd *ehci, int flags) |
{ |
struct ehci_qtd *qtd; |
dma_addr_t dma; |
qtd = pci_pool_alloc (ehci->qtd_pool, flags, &dma); |
if (qtd != 0) { |
ehci_qtd_init (qtd, dma); |
} |
return qtd; |
} |
static inline void ehci_qtd_free (struct ehci_hcd *ehci, struct ehci_qtd *qtd) |
{ |
pci_pool_free (ehci->qtd_pool, qtd, qtd->qtd_dma); |
} |
static struct ehci_qh *ehci_qh_alloc (struct ehci_hcd *ehci, int flags) |
{ |
struct ehci_qh *qh; |
dma_addr_t dma; |
qh = (struct ehci_qh *) |
pci_pool_alloc (ehci->qh_pool, flags, &dma); |
if (!qh) |
return qh; |
memset (qh, 0, sizeof *qh); |
atomic_set (&qh->refcount, 1); |
qh->qh_dma = dma; |
// INIT_LIST_HEAD (&qh->qh_list); |
INIT_LIST_HEAD (&qh->qtd_list); |
/* dummy td enables safe urb queuing */ |
qh->dummy = ehci_qtd_alloc (ehci, flags); |
if (qh->dummy == 0) { |
ehci_dbg (ehci, "no dummy td\n"); |
pci_pool_free (ehci->qh_pool, qh, qh->qh_dma); |
qh = 0; |
} |
return qh; |
} |
/* to share a qh (cpu threads, or hc) */ |
static inline struct ehci_qh *qh_get (/* ehci, */ struct ehci_qh *qh) |
{ |
atomic_inc (&qh->refcount); |
return qh; |
} |
static void qh_put (struct ehci_hcd *ehci, struct ehci_qh *qh) |
{ |
if (!atomic_dec_and_test (&qh->refcount)) |
return; |
/* clean qtds first, and know this is not linked */ |
if (!list_empty (&qh->qtd_list) || qh->qh_next.ptr) { |
ehci_dbg (ehci, "unused qh not empty!\n"); |
BUG (); |
} |
if (qh->dummy) |
ehci_qtd_free (ehci, qh->dummy); |
pci_pool_free (ehci->qh_pool, qh, qh->qh_dma); |
} |
/*-------------------------------------------------------------------------*/ |
/* The queue heads and transfer descriptors are managed from pools tied |
* to each of the "per device" structures. |
* This is the initialisation and cleanup code. |
*/ |
static void ehci_mem_cleanup (struct ehci_hcd *ehci) |
{ |
if (ehci->async) |
qh_put (ehci, ehci->async); |
ehci->async = 0; |
/* PCI consistent memory and pools */ |
if (ehci->qtd_pool) |
pci_pool_destroy (ehci->qtd_pool); |
ehci->qtd_pool = 0; |
if (ehci->qh_pool) { |
pci_pool_destroy (ehci->qh_pool); |
ehci->qh_pool = 0; |
} |
if (ehci->itd_pool) |
pci_pool_destroy (ehci->itd_pool); |
ehci->itd_pool = 0; |
if (ehci->sitd_pool) |
pci_pool_destroy (ehci->sitd_pool); |
ehci->sitd_pool = 0; |
if (ehci->periodic) |
pci_free_consistent (ehci->hcd.pdev, |
ehci->periodic_size * sizeof (u32), |
ehci->periodic, ehci->periodic_dma); |
ehci->periodic = 0; |
/* shadow periodic table */ |
if (ehci->pshadow) |
kfree (ehci->pshadow); |
ehci->pshadow = 0; |
} |
/* remember to add cleanup code (above) if you add anything here */ |
static int ehci_mem_init (struct ehci_hcd *ehci, int flags) |
{ |
int i; |
/* QTDs for control/bulk/intr transfers */ |
ehci->qtd_pool = pci_pool_create ("ehci_qtd", ehci->hcd.pdev, |
sizeof (struct ehci_qtd), |
32 /* byte alignment (for hw parts) */, |
4096 /* can't cross 4K */); |
if (!ehci->qtd_pool) { |
goto fail; |
} |
/* QHs for control/bulk/intr transfers */ |
ehci->qh_pool = pci_pool_create ("ehci_qh", ehci->hcd.pdev, |
sizeof (struct ehci_qh), |
32 /* byte alignment (for hw parts) */, |
4096 /* can't cross 4K */); |
if (!ehci->qh_pool) { |
goto fail; |
} |
ehci->async = ehci_qh_alloc (ehci, flags); |
if (!ehci->async) { |
goto fail; |
} |
/* ITD for high speed ISO transfers */ |
ehci->itd_pool = pci_pool_create ("ehci_itd", ehci->hcd.pdev, |
sizeof (struct ehci_itd), |
32 /* byte alignment (for hw parts) */, |
4096 /* can't cross 4K */); |
if (!ehci->itd_pool) { |
goto fail; |
} |
/* SITD for full/low speed split ISO transfers */ |
ehci->sitd_pool = pci_pool_create ("ehci_sitd", ehci->hcd.pdev, |
sizeof (struct ehci_sitd), |
32 /* byte alignment (for hw parts) */, |
4096 /* can't cross 4K */); |
if (!ehci->sitd_pool) { |
goto fail; |
} |
/* Hardware periodic table */ |
ehci->periodic = (u32 *) |
pci_alloc_consistent (ehci->hcd.pdev, |
ehci->periodic_size * sizeof (u32), |
&ehci->periodic_dma); |
if (ehci->periodic == 0) { |
goto fail; |
} |
for (i = 0; i < ehci->periodic_size; i++) |
ehci->periodic [i] = EHCI_LIST_END; |
/* software shadow of hardware table */ |
ehci->pshadow = kmalloc (ehci->periodic_size * sizeof (void *), flags); |
if (ehci->pshadow == 0) { |
goto fail; |
} |
memset (ehci->pshadow, 0, ehci->periodic_size * sizeof (void *)); |
return 0; |
fail: |
ehci_dbg (ehci, "couldn't init memory\n"); |
ehci_mem_cleanup (ehci); |
return -ENOMEM; |
} |
/shark/trunk/drivers/usb/host/uhci-hcd.c |
---|
0,0 → 1,2670 |
/* |
* Universal Host Controller Interface driver for USB. |
* |
* Maintainer: Johannes Erdfelt <johannes@erdfelt.com> |
* |
* (C) Copyright 1999 Linus Torvalds |
* (C) Copyright 1999-2002 Johannes Erdfelt, johannes@erdfelt.com |
* (C) Copyright 1999 Randy Dunlap |
* (C) Copyright 1999 Georg Acher, acher@in.tum.de |
* (C) Copyright 1999 Deti Fliegl, deti@fliegl.de |
* (C) Copyright 1999 Thomas Sailer, sailer@ife.ee.ethz.ch |
* (C) Copyright 1999 Roman Weissgaerber, weissg@vienna.at |
* (C) Copyright 2000 Yggdrasil Computing, Inc. (port of new PCI interface |
* support from usb-ohci.c by Adam Richter, adam@yggdrasil.com). |
* (C) Copyright 1999 Gregory P. Smith (from usb-ohci.c) |
* |
* Intel documents this fairly well, and as far as I know there |
* are no royalties or anything like that, but even so there are |
* people who decided that they want to do the same thing in a |
* completely different way. |
* |
* WARNING! The USB documentation is downright evil. Most of it |
* is just crap, written by a committee. You're better off ignoring |
* most of it, the important stuff is: |
* - the low-level protocol (fairly simple but lots of small details) |
* - working around the horridness of the rest |
*/ |
#include <linuxcomp.h> |
#include <linux/config.h> |
#include <linux/module.h> |
#include <linux/pci.h> |
#include <linux/kernel.h> |
#include <linux/init.h> |
#include <linux/delay.h> |
#include <linux/ioport.h> |
#include <linux/sched.h> |
#include <linux/slab.h> |
#include <linux/smp_lock.h> |
#include <linux/errno.h> |
#include <linux/unistd.h> |
#include <linux/interrupt.h> |
#include <linux/spinlock.h> |
#include <linux/proc_fs.h> |
#ifdef CONFIG_USB_DEBUG |
#define DEBUG |
#else |
#undef DEBUG |
#endif |
#include <linux/usb.h> |
#include <asm/uaccess.h> |
#include <asm/io.h> |
#include <asm/irq.h> |
#include <asm/system.h> |
#include "../core/hcd.h" |
#include "uhci-hcd.h" |
#include <linux/pm.h> |
/* |
* Version Information |
*/ |
#define DRIVER_VERSION "v2.1" |
#define DRIVER_AUTHOR "Linus 'Frodo Rabbit' Torvalds, Johannes Erdfelt, Randy Dunlap, Georg Acher, Deti Fliegl, Thomas Sailer, Roman Weissgaerber" |
#define DRIVER_DESC "USB Universal Host Controller Interface driver" |
/* |
* debug = 0, no debugging messages |
* debug = 1, dump failed URB's except for stalls |
* debug = 2, dump all failed URB's (including stalls) |
* show all queues in /proc/driver/uhci/[pci_addr] |
* debug = 3, show all TD's in URB's when dumping |
*/ |
#ifdef DEBUG |
static int debug = 3; |
#else |
static int debug = 0; |
#endif |
MODULE_PARM(debug, "i"); |
MODULE_PARM_DESC(debug, "Debug level"); |
static char *errbuf; |
#define ERRBUF_LEN (PAGE_SIZE * 8) |
#include "uhci-hub.c" |
#include "uhci-debug.c" |
static kmem_cache_t *uhci_up_cachep; /* urb_priv */ |
static int uhci_get_current_frame_number(struct uhci_hcd *uhci); |
static int uhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb); |
static void uhci_unlink_generic(struct uhci_hcd *uhci, struct urb *urb); |
static void hc_state_transitions(struct uhci_hcd *uhci); |
/* If a transfer is still active after this much time, turn off FSBR */ |
#define IDLE_TIMEOUT (HZ / 20) /* 50 ms */ |
#define FSBR_DELAY (HZ / 20) /* 50 ms */ |
/* When we timeout an idle transfer for FSBR, we'll switch it over to */ |
/* depth first traversal. We'll do it in groups of this number of TD's */ |
/* to make sure it doesn't hog all of the bandwidth */ |
#define DEPTH_INTERVAL 5 |
/* |
* Technically, updating td->status here is a race, but it's not really a |
* problem. The worst that can happen is that we set the IOC bit again |
* generating a spurious interrupt. We could fix this by creating another |
* QH and leaving the IOC bit always set, but then we would have to play |
* games with the FSBR code to make sure we get the correct order in all |
* the cases. I don't think it's worth the effort |
*/ |
static inline void uhci_set_next_interrupt(struct uhci_hcd *uhci) |
{ |
unsigned long flags; |
spin_lock_irqsave(&uhci->frame_list_lock, flags); |
uhci->term_td->status |= cpu_to_le32(TD_CTRL_IOC); |
spin_unlock_irqrestore(&uhci->frame_list_lock, flags); |
} |
static inline void uhci_clear_next_interrupt(struct uhci_hcd *uhci) |
{ |
unsigned long flags; |
spin_lock_irqsave(&uhci->frame_list_lock, flags); |
uhci->term_td->status &= ~cpu_to_le32(TD_CTRL_IOC); |
spin_unlock_irqrestore(&uhci->frame_list_lock, flags); |
} |
static inline void uhci_add_complete(struct uhci_hcd *uhci, struct urb *urb) |
{ |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
unsigned long flags; |
spin_lock_irqsave(&uhci->complete_list_lock, flags); |
list_add_tail(&urbp->complete_list, &uhci->complete_list); |
spin_unlock_irqrestore(&uhci->complete_list_lock, flags); |
} |
static struct uhci_td *uhci_alloc_td(struct uhci_hcd *uhci, struct usb_device *dev) |
{ |
dma_addr_t dma_handle; |
struct uhci_td *td; |
td = pci_pool_alloc(uhci->td_pool, GFP_ATOMIC, &dma_handle); |
if (!td) |
return NULL; |
td->dma_handle = dma_handle; |
td->link = UHCI_PTR_TERM; |
td->buffer = 0; |
td->frame = -1; |
td->dev = dev; |
INIT_LIST_HEAD(&td->list); |
INIT_LIST_HEAD(&td->remove_list); |
INIT_LIST_HEAD(&td->fl_list); |
usb_get_dev(dev); |
return td; |
} |
static inline void uhci_fill_td(struct uhci_td *td, __u32 status, |
__u32 token, __u32 buffer) |
{ |
td->status = cpu_to_le32(status); |
td->token = cpu_to_le32(token); |
td->buffer = cpu_to_le32(buffer); |
} |
/* |
* We insert Isochronous URB's directly into the frame list at the beginning |
*/ |
static void uhci_insert_td_frame_list(struct uhci_hcd *uhci, struct uhci_td *td, unsigned framenum) |
{ |
unsigned long flags; |
framenum %= UHCI_NUMFRAMES; |
spin_lock_irqsave(&uhci->frame_list_lock, flags); |
td->frame = framenum; |
/* Is there a TD already mapped there? */ |
if (uhci->fl->frame_cpu[framenum]) { |
struct uhci_td *ftd, *ltd; |
ftd = uhci->fl->frame_cpu[framenum]; |
ltd = list_entry(ftd->fl_list.prev, struct uhci_td, fl_list); |
list_add_tail(&td->fl_list, &ftd->fl_list); |
td->link = ltd->link; |
mb(); |
ltd->link = cpu_to_le32(td->dma_handle); |
} else { |
td->link = uhci->fl->frame[framenum]; |
mb(); |
uhci->fl->frame[framenum] = cpu_to_le32(td->dma_handle); |
uhci->fl->frame_cpu[framenum] = td; |
} |
spin_unlock_irqrestore(&uhci->frame_list_lock, flags); |
} |
static void uhci_remove_td(struct uhci_hcd *uhci, struct uhci_td *td) |
{ |
unsigned long flags; |
/* If it's not inserted, don't remove it */ |
spin_lock_irqsave(&uhci->frame_list_lock, flags); |
if (td->frame == -1 && list_empty(&td->fl_list)) |
goto out; |
if (td->frame != -1 && uhci->fl->frame_cpu[td->frame] == td) { |
if (list_empty(&td->fl_list)) { |
uhci->fl->frame[td->frame] = td->link; |
uhci->fl->frame_cpu[td->frame] = NULL; |
} else { |
struct uhci_td *ntd; |
ntd = list_entry(td->fl_list.next, struct uhci_td, fl_list); |
uhci->fl->frame[td->frame] = cpu_to_le32(ntd->dma_handle); |
uhci->fl->frame_cpu[td->frame] = ntd; |
} |
} else { |
struct uhci_td *ptd; |
ptd = list_entry(td->fl_list.prev, struct uhci_td, fl_list); |
ptd->link = td->link; |
} |
mb(); |
td->link = UHCI_PTR_TERM; |
list_del_init(&td->fl_list); |
td->frame = -1; |
out: |
spin_unlock_irqrestore(&uhci->frame_list_lock, flags); |
} |
/* |
* Inserts a td into qh list at the top. |
*/ |
static void uhci_insert_tds_in_qh(struct uhci_qh *qh, struct urb *urb, u32 breadth) |
{ |
struct list_head *tmp, *head; |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
struct uhci_td *td, *ptd; |
if (list_empty(&urbp->td_list)) |
return; |
head = &urbp->td_list; |
tmp = head->next; |
/* Ordering isn't important here yet since the QH hasn't been */ |
/* inserted into the schedule yet */ |
td = list_entry(tmp, struct uhci_td, list); |
/* Add the first TD to the QH element pointer */ |
qh->element = cpu_to_le32(td->dma_handle) | breadth; |
ptd = td; |
/* Then link the rest of the TD's */ |
tmp = tmp->next; |
while (tmp != head) { |
td = list_entry(tmp, struct uhci_td, list); |
tmp = tmp->next; |
ptd->link = cpu_to_le32(td->dma_handle) | breadth; |
ptd = td; |
} |
ptd->link = UHCI_PTR_TERM; |
} |
static void uhci_free_td(struct uhci_hcd *uhci, struct uhci_td *td) |
{ |
if (!list_empty(&td->list)) |
dbg("td %p is still in list!", td); |
if (!list_empty(&td->remove_list)) |
dbg("td %p still in remove_list!", td); |
if (!list_empty(&td->fl_list)) |
dbg("td %p is still in fl_list!", td); |
if (td->dev) |
usb_put_dev(td->dev); |
pci_pool_free(uhci->td_pool, td, td->dma_handle); |
} |
static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci, struct usb_device *dev) |
{ |
dma_addr_t dma_handle; |
struct uhci_qh *qh; |
qh = pci_pool_alloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle); |
if (!qh) |
return NULL; |
qh->dma_handle = dma_handle; |
qh->element = UHCI_PTR_TERM; |
qh->link = UHCI_PTR_TERM; |
qh->dev = dev; |
qh->urbp = NULL; |
INIT_LIST_HEAD(&qh->list); |
INIT_LIST_HEAD(&qh->remove_list); |
usb_get_dev(dev); |
return qh; |
} |
static void uhci_free_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) |
{ |
if (!list_empty(&qh->list)) |
dbg("qh %p list not empty!", qh); |
if (!list_empty(&qh->remove_list)) |
dbg("qh %p still in remove_list!", qh); |
if (qh->dev) |
usb_put_dev(qh->dev); |
pci_pool_free(uhci->qh_pool, qh, qh->dma_handle); |
} |
/* |
* Append this urb's qh after the last qh in skelqh->list |
* MUST be called with uhci->frame_list_lock acquired |
* |
* Note that urb_priv.queue_list doesn't have a separate queue head; |
* it's a ring with every element "live". |
*/ |
static void _uhci_insert_qh(struct uhci_hcd *uhci, struct uhci_qh *skelqh, struct urb *urb) |
{ |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
struct list_head *tmp; |
struct uhci_qh *lqh; |
/* Grab the last QH */ |
lqh = list_entry(skelqh->list.prev, struct uhci_qh, list); |
/* |
* Patch this endpoint's URB's QHs to point to the next skelqh: |
* skelqh --> ... lqh --> newqh --> next skelqh |
* Do this first, so the HC always sees the right QH after this one. |
*/ |
list_for_each (tmp, &urbp->queue_list) { |
struct urb_priv *turbp = |
list_entry(tmp, struct urb_priv, queue_list); |
turbp->qh->link = lqh->link; |
} |
urbp->qh->link = lqh->link; |
wmb(); /* Ordering is important */ |
/* |
* Patch QHs for previous endpoint's queued URBs? HC goes |
* here next, not to the next skelqh it now points to. |
* |
* lqh --> td ... --> qh ... --> td --> qh ... --> td |
* | | | |
* v v v |
* +<----------------+-----------------+ |
* v |
* newqh --> td ... --> td |
* | |
* v |
* ... |
* |
* The HC could see (and use!) any of these as we write them. |
*/ |
if (lqh->urbp) { |
list_for_each (tmp, &lqh->urbp->queue_list) { |
struct urb_priv *turbp = |
list_entry(tmp, struct urb_priv, queue_list); |
turbp->qh->link = cpu_to_le32(urbp->qh->dma_handle) | UHCI_PTR_QH; |
} |
} |
lqh->link = cpu_to_le32(urbp->qh->dma_handle) | UHCI_PTR_QH; |
list_add_tail(&urbp->qh->list, &skelqh->list); |
} |
static void uhci_insert_qh(struct uhci_hcd *uhci, struct uhci_qh *skelqh, struct urb *urb) |
{ |
unsigned long flags; |
spin_lock_irqsave(&uhci->frame_list_lock, flags); |
_uhci_insert_qh(uhci, skelqh, urb); |
spin_unlock_irqrestore(&uhci->frame_list_lock, flags); |
} |
/* |
* Start removal of QH from schedule; it finishes next frame. |
* TDs should be unlinked before this is called. |
*/ |
static void uhci_remove_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) |
{ |
unsigned long flags; |
struct uhci_qh *pqh; |
if (!qh) |
return; |
qh->urbp = NULL; |
/* |
* Only go through the hoops if it's actually linked in |
* Queued QHs are removed in uhci_delete_queued_urb, |
* since (for queued URBs) the pqh is pointed to the next |
* QH in the queue, not the next endpoint's QH. |
*/ |
spin_lock_irqsave(&uhci->frame_list_lock, flags); |
if (!list_empty(&qh->list)) { |
pqh = list_entry(qh->list.prev, struct uhci_qh, list); |
if (pqh->urbp) { |
struct list_head *head, *tmp; |
head = &pqh->urbp->queue_list; |
tmp = head->next; |
while (head != tmp) { |
struct urb_priv *turbp = |
list_entry(tmp, struct urb_priv, queue_list); |
tmp = tmp->next; |
turbp->qh->link = qh->link; |
} |
} |
pqh->link = qh->link; |
mb(); |
/* Leave qh->link in case the HC is on the QH now, it will */ |
/* continue the rest of the schedule */ |
qh->element = UHCI_PTR_TERM; |
list_del_init(&qh->list); |
} |
spin_unlock_irqrestore(&uhci->frame_list_lock, flags); |
spin_lock_irqsave(&uhci->qh_remove_list_lock, flags); |
/* Check to see if the remove list is empty. Set the IOC bit */ |
/* to force an interrupt so we can remove the QH */ |
if (list_empty(&uhci->qh_remove_list)) |
uhci_set_next_interrupt(uhci); |
list_add(&qh->remove_list, &uhci->qh_remove_list); |
spin_unlock_irqrestore(&uhci->qh_remove_list_lock, flags); |
} |
static int uhci_fixup_toggle(struct urb *urb, unsigned int toggle) |
{ |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
struct list_head *head, *tmp; |
head = &urbp->td_list; |
tmp = head->next; |
while (head != tmp) { |
struct uhci_td *td = list_entry(tmp, struct uhci_td, list); |
tmp = tmp->next; |
if (toggle) |
td->token |= cpu_to_le32(TD_TOKEN_TOGGLE); |
else |
td->token &= ~cpu_to_le32(TD_TOKEN_TOGGLE); |
toggle ^= 1; |
} |
return toggle; |
} |
/* This function will append one URB's QH to another URB's QH. This is for */ |
/* queuing interrupt, control or bulk transfers */ |
static void uhci_append_queued_urb(struct uhci_hcd *uhci, struct urb *eurb, struct urb *urb) |
{ |
struct urb_priv *eurbp, *urbp, *furbp, *lurbp; |
struct list_head *tmp; |
struct uhci_td *lltd; |
unsigned long flags; |
eurbp = eurb->hcpriv; |
urbp = urb->hcpriv; |
spin_lock_irqsave(&uhci->frame_list_lock, flags); |
/* Find the first URB in the queue */ |
if (eurbp->queued) { |
struct list_head *head = &eurbp->queue_list; |
tmp = head->next; |
while (tmp != head) { |
struct urb_priv *turbp = |
list_entry(tmp, struct urb_priv, queue_list); |
if (!turbp->queued) |
break; |
tmp = tmp->next; |
} |
} else |
tmp = &eurbp->queue_list; |
furbp = list_entry(tmp, struct urb_priv, queue_list); |
lurbp = list_entry(furbp->queue_list.prev, struct urb_priv, queue_list); |
lltd = list_entry(lurbp->td_list.prev, struct uhci_td, list); |
usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe), |
uhci_fixup_toggle(urb, uhci_toggle(td_token(lltd)) ^ 1)); |
/* All qh's in the queue need to link to the next queue */ |
urbp->qh->link = eurbp->qh->link; |
mb(); /* Make sure we flush everything */ |
lltd->link = cpu_to_le32(urbp->qh->dma_handle) | UHCI_PTR_QH; |
list_add_tail(&urbp->queue_list, &furbp->queue_list); |
urbp->queued = 1; |
spin_unlock_irqrestore(&uhci->frame_list_lock, flags); |
} |
static void uhci_delete_queued_urb(struct uhci_hcd *uhci, struct urb *urb) |
{ |
struct urb_priv *urbp, *nurbp; |
struct list_head *head, *tmp; |
struct urb_priv *purbp; |
struct uhci_td *pltd; |
unsigned int toggle; |
unsigned long flags; |
urbp = urb->hcpriv; |
spin_lock_irqsave(&uhci->frame_list_lock, flags); |
if (list_empty(&urbp->queue_list)) |
goto out; |
nurbp = list_entry(urbp->queue_list.next, struct urb_priv, queue_list); |
/* Fix up the toggle for the next URB's */ |
if (!urbp->queued) |
/* We just set the toggle in uhci_unlink_generic */ |
toggle = usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe)); |
else { |
/* If we're in the middle of the queue, grab the toggle */ |
/* from the TD previous to us */ |
purbp = list_entry(urbp->queue_list.prev, struct urb_priv, |
queue_list); |
pltd = list_entry(purbp->td_list.prev, struct uhci_td, list); |
toggle = uhci_toggle(td_token(pltd)) ^ 1; |
} |
head = &urbp->queue_list; |
tmp = head->next; |
while (head != tmp) { |
struct urb_priv *turbp; |
turbp = list_entry(tmp, struct urb_priv, queue_list); |
tmp = tmp->next; |
if (!turbp->queued) |
break; |
toggle = uhci_fixup_toggle(turbp->urb, toggle); |
} |
usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), |
usb_pipeout(urb->pipe), toggle); |
if (!urbp->queued) { |
struct uhci_qh *pqh; |
nurbp->queued = 0; |
/* |
* Fixup the previous QH's queue to link to the new head |
* of this queue. |
*/ |
pqh = list_entry(urbp->qh->list.prev, struct uhci_qh, list); |
if (pqh->urbp) { |
struct list_head *head, *tmp; |
head = &pqh->urbp->queue_list; |
tmp = head->next; |
while (head != tmp) { |
struct urb_priv *turbp = |
list_entry(tmp, struct urb_priv, queue_list); |
tmp = tmp->next; |
turbp->qh->link = cpu_to_le32(nurbp->qh->dma_handle) | UHCI_PTR_QH; |
} |
} |
pqh->link = cpu_to_le32(nurbp->qh->dma_handle) | UHCI_PTR_QH; |
list_add_tail(&nurbp->qh->list, &urbp->qh->list); |
list_del_init(&urbp->qh->list); |
} else { |
/* We're somewhere in the middle (or end). A bit trickier */ |
/* than the head scenario */ |
purbp = list_entry(urbp->queue_list.prev, struct urb_priv, |
queue_list); |
pltd = list_entry(purbp->td_list.prev, struct uhci_td, list); |
if (nurbp->queued) |
pltd->link = cpu_to_le32(nurbp->qh->dma_handle) | UHCI_PTR_QH; |
else |
/* The next URB happens to be the beginning, so */ |
/* we're the last, end the chain */ |
pltd->link = UHCI_PTR_TERM; |
} |
list_del_init(&urbp->queue_list); |
out: |
spin_unlock_irqrestore(&uhci->frame_list_lock, flags); |
} |
extern void* malloc(int size); |
static struct urb_priv *uhci_alloc_urb_priv(struct uhci_hcd *uhci, struct urb *urb) |
{ |
struct urb_priv *urbp; |
urbp = malloc(sizeof(struct urb_priv)); //**kmem_cache_alloc(uhci_up_cachep, SLAB_ATOMIC); |
if (!urbp) { |
err("uhci_alloc_urb_priv: couldn't allocate memory for urb_priv\n"); |
return NULL; |
} |
memset((void *)urbp, 0, sizeof(*urbp)); |
urbp->inserttime = jiffies26; |
urbp->fsbrtime = jiffies26; |
urbp->urb = urb; |
urbp->dev = urb->dev; |
INIT_LIST_HEAD(&urbp->td_list); |
INIT_LIST_HEAD(&urbp->queue_list); |
INIT_LIST_HEAD(&urbp->complete_list); |
INIT_LIST_HEAD(&urbp->urb_list); |
list_add_tail(&urbp->urb_list, &uhci->urb_list); |
urb->hcpriv = urbp; |
return urbp; |
} |
/* |
* MUST be called with urb->lock acquired |
*/ |
static void uhci_add_td_to_urb(struct urb *urb, struct uhci_td *td) |
{ |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
td->urb = urb; |
list_add_tail(&td->list, &urbp->td_list); |
} |
/* |
* MUST be called with urb->lock acquired |
*/ |
static void uhci_remove_td_from_urb(struct uhci_td *td) |
{ |
if (list_empty(&td->list)) |
return; |
list_del_init(&td->list); |
td->urb = NULL; |
} |
/* |
* MUST be called with urb->lock acquired |
*/ |
static void uhci_destroy_urb_priv(struct uhci_hcd *uhci, struct urb *urb) |
{ |
struct list_head *head, *tmp; |
struct urb_priv *urbp; |
unsigned long flags; |
urbp = (struct urb_priv *)urb->hcpriv; |
if (!urbp) |
return; |
if (!list_empty(&urbp->urb_list)) |
warn("uhci_destroy_urb_priv: urb %p still on uhci->urb_list or uhci->remove_list", urb); |
if (!list_empty(&urbp->complete_list)) |
warn("uhci_destroy_urb_priv: urb %p still on uhci->complete_list", urb); |
spin_lock_irqsave(&uhci->td_remove_list_lock, flags); |
/* Check to see if the remove list is empty. Set the IOC bit */ |
/* to force an interrupt so we can remove the TD's*/ |
if (list_empty(&uhci->td_remove_list)) |
uhci_set_next_interrupt(uhci); |
head = &urbp->td_list; |
tmp = head->next; |
while (tmp != head) { |
struct uhci_td *td = list_entry(tmp, struct uhci_td, list); |
tmp = tmp->next; |
uhci_remove_td_from_urb(td); |
uhci_remove_td(uhci, td); |
list_add(&td->remove_list, &uhci->td_remove_list); |
} |
spin_unlock_irqrestore(&uhci->td_remove_list_lock, flags); |
urb->hcpriv = NULL; |
//**kmem_cache_free(uhci_up_cachep, urbp); |
free(urbp); |
} |
static void uhci_inc_fsbr(struct uhci_hcd *uhci, struct urb *urb) |
{ |
unsigned long flags; |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
spin_lock_irqsave(&uhci->frame_list_lock, flags); |
if ((!(urb->transfer_flags & URB_NO_FSBR)) && !urbp->fsbr) { |
urbp->fsbr = 1; |
if (!uhci->fsbr++ && !uhci->fsbrtimeout) |
uhci->skel_term_qh->link = cpu_to_le32(uhci->skel_hs_control_qh->dma_handle) | UHCI_PTR_QH; |
} |
spin_unlock_irqrestore(&uhci->frame_list_lock, flags); |
} |
static void uhci_dec_fsbr(struct uhci_hcd *uhci, struct urb *urb) |
{ |
unsigned long flags; |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
spin_lock_irqsave(&uhci->frame_list_lock, flags); |
if ((!(urb->transfer_flags & URB_NO_FSBR)) && urbp->fsbr) { |
urbp->fsbr = 0; |
if (!--uhci->fsbr) |
uhci->fsbrtimeout = jiffies26 + FSBR_DELAY; |
} |
spin_unlock_irqrestore(&uhci->frame_list_lock, flags); |
} |
/* |
* Map status to standard result codes |
* |
* <status> is (td->status & 0xFE0000) [a.k.a. uhci_status_bits(td->status)] |
* <dir_out> is True for output TDs and False for input TDs. |
*/ |
static int uhci_map_status(int status, int dir_out) |
{ |
if (!status) |
return 0; |
if (status & TD_CTRL_BITSTUFF) /* Bitstuff error */ |
return -EPROTO; |
if (status & TD_CTRL_CRCTIMEO) { /* CRC/Timeout */ |
if (dir_out) |
return -ETIMEDOUT; |
else |
return -EILSEQ; |
} |
if (status & TD_CTRL_NAK) /* NAK */ |
return -ETIMEDOUT; |
if (status & TD_CTRL_BABBLE) /* Babble */ |
return -EOVERFLOW; |
if (status & TD_CTRL_DBUFERR) /* Buffer error */ |
return -ENOSR; |
if (status & TD_CTRL_STALLED) /* Stalled */ |
return -EPIPE; |
if (status & TD_CTRL_ACTIVE) /* Active */ |
return 0; |
return -EINVAL; |
} |
/* |
* Control transfers |
*/ |
static int uhci_submit_control(struct uhci_hcd *uhci, struct urb *urb, struct urb *eurb) |
{ |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
struct uhci_td *td; |
struct uhci_qh *qh, *skelqh; |
unsigned long destination, status; |
int maxsze = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); |
int len = urb->transfer_buffer_length; |
dma_addr_t data = urb->transfer_dma; |
/* The "pipe" thing contains the destination in bits 8--18 */ |
destination = (urb->pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP; |
/* 3 errors */ |
status = TD_CTRL_ACTIVE | uhci_maxerr(3); |
if (urb->dev->speed == USB_SPEED_LOW) |
status |= TD_CTRL_LS; |
/* |
* Build the TD for the control request |
*/ |
td = uhci_alloc_td(uhci, urb->dev); |
if (!td) |
return -ENOMEM; |
uhci_add_td_to_urb(urb, td); |
uhci_fill_td(td, status, destination | uhci_explen(7), |
urb->setup_dma); |
/* |
* If direction is "send", change the frame from SETUP (0x2D) |
* to OUT (0xE1). Else change it from SETUP to IN (0x69). |
*/ |
destination ^= (USB_PID_SETUP ^ usb_packetid(urb->pipe)); |
if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) |
status |= TD_CTRL_SPD; |
/* |
* Build the DATA TD's |
*/ |
while (len > 0) { |
int pktsze = len; |
if (pktsze > maxsze) |
pktsze = maxsze; |
td = uhci_alloc_td(uhci, urb->dev); |
if (!td) |
return -ENOMEM; |
/* Alternate Data0/1 (start with Data1) */ |
destination ^= TD_TOKEN_TOGGLE; |
uhci_add_td_to_urb(urb, td); |
uhci_fill_td(td, status, destination | uhci_explen(pktsze - 1), |
data); |
data += pktsze; |
len -= pktsze; |
} |
/* |
* Build the final TD for control status |
*/ |
td = uhci_alloc_td(uhci, urb->dev); |
if (!td) |
return -ENOMEM; |
/* |
* It's IN if the pipe is an output pipe or we're not expecting |
* data back. |
*/ |
destination &= ~TD_TOKEN_PID_MASK; |
if (usb_pipeout(urb->pipe) || !urb->transfer_buffer_length) |
destination |= USB_PID_IN; |
else |
destination |= USB_PID_OUT; |
destination |= TD_TOKEN_TOGGLE; /* End in Data1 */ |
status &= ~TD_CTRL_SPD; |
uhci_add_td_to_urb(urb, td); |
uhci_fill_td(td, status | TD_CTRL_IOC, |
destination | uhci_explen(UHCI_NULL_DATA_SIZE), 0); |
qh = uhci_alloc_qh(uhci, urb->dev); |
if (!qh) |
return -ENOMEM; |
urbp->qh = qh; |
qh->urbp = urbp; |
uhci_insert_tds_in_qh(qh, urb, UHCI_PTR_BREADTH); |
/* Low speed transfers get a different queue, and won't hog the bus */ |
if (urb->dev->speed == USB_SPEED_LOW) |
skelqh = uhci->skel_ls_control_qh; |
else { |
skelqh = uhci->skel_hs_control_qh; |
uhci_inc_fsbr(uhci, urb); |
} |
if (eurb) |
uhci_append_queued_urb(uhci, eurb, urb); |
else |
uhci_insert_qh(uhci, skelqh, urb); |
return -EINPROGRESS; |
} |
/* |
* If control was short, then end status packet wasn't sent, so this |
* reorganize s so it's sent to finish the transfer. The original QH is |
* removed from the skel and discarded; all TDs except the last (status) |
* are deleted; the last (status) TD is put on a new QH which is reinserted |
* into the skel. Since the last TD and urb_priv are reused, the TD->link |
* and urb_priv maintain any queued QHs. |
*/ |
static int usb_control_retrigger_status(struct uhci_hcd *uhci, struct urb *urb) |
{ |
struct list_head *tmp, *head; |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
urbp->short_control_packet = 1; |
/* Create a new QH to avoid pointer overwriting problems */ |
uhci_remove_qh(uhci, urbp->qh); |
/* Delete all of the TD's except for the status TD at the end */ |
head = &urbp->td_list; |
tmp = head->next; |
while (tmp != head && tmp->next != head) { |
struct uhci_td *td = list_entry(tmp, struct uhci_td, list); |
tmp = tmp->next; |
uhci_remove_td_from_urb(td); |
uhci_remove_td(uhci, td); |
uhci_free_td(uhci, td); |
} |
urbp->qh = uhci_alloc_qh(uhci, urb->dev); |
if (!urbp->qh) { |
err("unable to allocate new QH for control retrigger"); |
return -ENOMEM; |
} |
urbp->qh->urbp = urbp; |
/* One TD, who cares about Breadth first? */ |
uhci_insert_tds_in_qh(urbp->qh, urb, UHCI_PTR_DEPTH); |
/* Low speed transfers get a different queue */ |
if (urb->dev->speed == USB_SPEED_LOW) |
uhci_insert_qh(uhci, uhci->skel_ls_control_qh, urb); |
else |
uhci_insert_qh(uhci, uhci->skel_hs_control_qh, urb); |
return -EINPROGRESS; |
} |
static int uhci_result_control(struct uhci_hcd *uhci, struct urb *urb) |
{ |
struct list_head *tmp, *head; |
struct urb_priv *urbp = urb->hcpriv; |
struct uhci_td *td; |
unsigned int status; |
int ret = 0; |
if (list_empty(&urbp->td_list)) |
return -EINVAL; |
head = &urbp->td_list; |
if (urbp->short_control_packet) { |
tmp = head->prev; |
goto status_phase; |
} |
tmp = head->next; |
td = list_entry(tmp, struct uhci_td, list); |
/* The first TD is the SETUP phase, check the status, but skip */ |
/* the count */ |
status = uhci_status_bits(td_status(td)); |
if (status & TD_CTRL_ACTIVE) |
return -EINPROGRESS; |
if (status) |
goto td_error; |
urb->actual_length = 0; |
/* The rest of the TD's (but the last) are data */ |
tmp = tmp->next; |
while (tmp != head && tmp->next != head) { |
td = list_entry(tmp, struct uhci_td, list); |
tmp = tmp->next; |
status = uhci_status_bits(td_status(td)); |
if (status & TD_CTRL_ACTIVE) |
return -EINPROGRESS; |
urb->actual_length += uhci_actual_length(td_status(td)); |
if (status) |
goto td_error; |
/* Check to see if we received a short packet */ |
if (uhci_actual_length(td_status(td)) < uhci_expected_length(td_token(td))) { |
if (urb->transfer_flags & URB_SHORT_NOT_OK) { |
ret = -EREMOTEIO; |
goto err; |
} |
if (uhci_packetid(td_token(td)) == USB_PID_IN) |
return usb_control_retrigger_status(uhci, urb); |
else |
return 0; |
} |
} |
status_phase: |
td = list_entry(tmp, struct uhci_td, list); |
/* Control status phase */ |
status = td_status(td); |
#ifdef I_HAVE_BUGGY_APC_BACKUPS |
/* APC BackUPS Pro kludge */ |
/* It tries to send all of the descriptor instead of the amount */ |
/* we requested */ |
if (status & TD_CTRL_IOC && /* IOC is masked out by uhci_status_bits */ |
status & TD_CTRL_ACTIVE && |
status & TD_CTRL_NAK) |
return 0; |
#endif |
if (status & TD_CTRL_ACTIVE) |
return -EINPROGRESS; |
if (uhci_status_bits(status)) |
goto td_error; |
return 0; |
td_error: |
ret = uhci_map_status(status, uhci_packetout(td_token(td))); |
err: |
if ((debug == 1 && ret != -EPIPE) || debug > 1) { |
/* Some debugging code */ |
dbg("uhci_result_control() failed with status %x", status); |
if (errbuf) { |
/* Print the chain for debugging purposes */ |
uhci_show_qh(urbp->qh, errbuf, ERRBUF_LEN, 0); |
lprintk(errbuf); |
} |
} |
return ret; |
} |
/* |
* Common submit for bulk and interrupt |
*/ |
static int uhci_submit_common(struct uhci_hcd *uhci, struct urb *urb, struct urb *eurb, struct uhci_qh *skelqh) |
{ |
struct uhci_td *td; |
struct uhci_qh *qh; |
unsigned long destination, status; |
int maxsze = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); |
int len = urb->transfer_buffer_length; |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
dma_addr_t data = urb->transfer_dma; |
if (len < 0) |
return -EINVAL; |
/* The "pipe" thing contains the destination in bits 8--18 */ |
destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe); |
status = uhci_maxerr(3) | TD_CTRL_ACTIVE; |
if (urb->dev->speed == USB_SPEED_LOW) |
status |= TD_CTRL_LS; |
if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) |
status |= TD_CTRL_SPD; |
/* |
* Build the DATA TD's |
*/ |
do { /* Allow zero length packets */ |
int pktsze = len; |
if (pktsze > maxsze) |
pktsze = maxsze; |
td = uhci_alloc_td(uhci, urb->dev); |
if (!td) |
return -ENOMEM; |
uhci_add_td_to_urb(urb, td); |
uhci_fill_td(td, status, destination | uhci_explen(pktsze - 1) | |
(usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), |
usb_pipeout(urb->pipe)) << TD_TOKEN_TOGGLE_SHIFT), |
data); |
data += pktsze; |
len -= maxsze; |
usb_dotoggle(urb->dev, usb_pipeendpoint(urb->pipe), |
usb_pipeout(urb->pipe)); |
} while (len > 0); |
/* |
* URB_ZERO_PACKET means adding a 0-length packet, if direction |
* is OUT and the transfer_length was an exact multiple of maxsze, |
* hence (len = transfer_length - N * maxsze) == 0 |
* however, if transfer_length == 0, the zero packet was already |
* prepared above. |
*/ |
if (usb_pipeout(urb->pipe) && (urb->transfer_flags & URB_ZERO_PACKET) && |
!len && urb->transfer_buffer_length) { |
td = uhci_alloc_td(uhci, urb->dev); |
if (!td) |
return -ENOMEM; |
uhci_add_td_to_urb(urb, td); |
uhci_fill_td(td, status, destination | uhci_explen(UHCI_NULL_DATA_SIZE) | |
(usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), |
usb_pipeout(urb->pipe)) << TD_TOKEN_TOGGLE_SHIFT), |
data); |
usb_dotoggle(urb->dev, usb_pipeendpoint(urb->pipe), |
usb_pipeout(urb->pipe)); |
} |
/* Set the flag on the last packet */ |
td->status |= cpu_to_le32(TD_CTRL_IOC); |
qh = uhci_alloc_qh(uhci, urb->dev); |
if (!qh) |
return -ENOMEM; |
urbp->qh = qh; |
qh->urbp = urbp; |
/* Always breadth first */ |
uhci_insert_tds_in_qh(qh, urb, UHCI_PTR_BREADTH); |
if (eurb) |
uhci_append_queued_urb(uhci, eurb, urb); |
else |
uhci_insert_qh(uhci, skelqh, urb); |
return -EINPROGRESS; |
} |
/* |
* Common result for bulk and interrupt |
*/ |
static int uhci_result_common(struct uhci_hcd *uhci, struct urb *urb) |
{ |
struct list_head *tmp, *head; |
struct urb_priv *urbp = urb->hcpriv; |
struct uhci_td *td; |
unsigned int status = 0; |
int ret = 0; |
urb->actual_length = 0; |
head = &urbp->td_list; |
tmp = head->next; |
while (tmp != head) { |
td = list_entry(tmp, struct uhci_td, list); |
tmp = tmp->next; |
status = uhci_status_bits(td_status(td)); |
if (status & TD_CTRL_ACTIVE) |
return -EINPROGRESS; |
urb->actual_length += uhci_actual_length(td_status(td)); |
if (status) |
goto td_error; |
if (uhci_actual_length(td_status(td)) < uhci_expected_length(td_token(td))) { |
if (urb->transfer_flags & URB_SHORT_NOT_OK) { |
ret = -EREMOTEIO; |
goto err; |
} else |
return 0; |
} |
} |
return 0; |
td_error: |
ret = uhci_map_status(status, uhci_packetout(td_token(td))); |
if (ret == -EPIPE) |
/* endpoint has stalled - mark it halted */ |
usb_endpoint_halt(urb->dev, uhci_endpoint(td_token(td)), |
uhci_packetout(td_token(td))); |
err: |
/* |
* Enable this chunk of code if you want to see some more debugging. |
* But be careful, it has the tendancy to starve out khubd and prevent |
* disconnects from happening successfully if you have a slow debug |
* log interface (like a serial console. |
*/ |
#if 0 |
if ((debug == 1 && ret != -EPIPE) || debug > 1) { |
/* Some debugging code */ |
dbg("uhci_result_common() failed with status %x", status); |
if (errbuf) { |
/* Print the chain for debugging purposes */ |
uhci_show_qh(urbp->qh, errbuf, ERRBUF_LEN, 0); |
lprintk(errbuf); |
} |
} |
#endif |
return ret; |
} |
static inline int uhci_submit_bulk(struct uhci_hcd *uhci, struct urb *urb, struct urb *eurb) |
{ |
int ret; |
/* Can't have low speed bulk transfers */ |
if (urb->dev->speed == USB_SPEED_LOW) |
return -EINVAL; |
ret = uhci_submit_common(uhci, urb, eurb, uhci->skel_bulk_qh); |
if (ret == -EINPROGRESS) |
uhci_inc_fsbr(uhci, urb); |
return ret; |
} |
static inline int uhci_submit_interrupt(struct uhci_hcd *uhci, struct urb *urb, struct urb *eurb) |
{ |
/* USB 1.1 interrupt transfers only involve one packet per interval; |
* that's the uhci_submit_common() "breadth first" policy. Drivers |
* can submit urbs of any length, but longer ones might need many |
* intervals to complete. |
*/ |
return uhci_submit_common(uhci, urb, eurb, uhci->skelqh[__interval_to_skel(urb->interval)]); |
} |
/* |
* Bulk and interrupt use common result |
*/ |
#define uhci_result_bulk uhci_result_common |
#define uhci_result_interrupt uhci_result_common |
/* |
* Isochronous transfers |
*/ |
static int isochronous_find_limits(struct uhci_hcd *uhci, struct urb *urb, unsigned int *start, unsigned int *end) |
{ |
struct urb *last_urb = NULL; |
struct list_head *tmp, *head; |
int ret = 0; |
head = &uhci->urb_list; |
tmp = head->next; |
while (tmp != head) { |
struct urb_priv *up = list_entry(tmp, struct urb_priv, urb_list); |
struct urb *u = up->urb; |
tmp = tmp->next; |
/* look for pending URB's with identical pipe handle */ |
if ((urb->pipe == u->pipe) && (urb->dev == u->dev) && |
(u->status == -EINPROGRESS) && (u != urb)) { |
if (!last_urb) |
*start = u->start_frame; |
last_urb = u; |
} |
} |
if (last_urb) { |
*end = (last_urb->start_frame + last_urb->number_of_packets * |
last_urb->interval) & (UHCI_NUMFRAMES-1); |
ret = 0; |
} else |
ret = -1; /* no previous urb found */ |
return ret; |
} |
static int isochronous_find_start(struct uhci_hcd *uhci, struct urb *urb) |
{ |
int limits; |
unsigned int start = 0, end = 0; |
if (urb->number_of_packets > 900) /* 900? Why? */ |
return -EFBIG; |
limits = isochronous_find_limits(uhci, urb, &start, &end); |
if (urb->transfer_flags & URB_ISO_ASAP) { |
if (limits) { |
int curframe; |
curframe = uhci_get_current_frame_number(uhci) % UHCI_NUMFRAMES; |
urb->start_frame = (curframe + 10) % UHCI_NUMFRAMES; |
} else |
urb->start_frame = end; |
} else { |
urb->start_frame %= UHCI_NUMFRAMES; |
/* FIXME: Sanity check */ |
} |
return 0; |
} |
/* |
* Isochronous transfers |
*/ |
static int uhci_submit_isochronous(struct uhci_hcd *uhci, struct urb *urb) |
{ |
struct uhci_td *td; |
int i, ret, frame; |
int status, destination; |
status = TD_CTRL_ACTIVE | TD_CTRL_IOS; |
destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe); |
ret = isochronous_find_start(uhci, urb); |
if (ret) |
return ret; |
frame = urb->start_frame; |
for (i = 0; i < urb->number_of_packets; i++, frame += urb->interval) { |
if (!urb->iso_frame_desc[i].length) |
continue; |
td = uhci_alloc_td(uhci, urb->dev); |
if (!td) |
return -ENOMEM; |
uhci_add_td_to_urb(urb, td); |
uhci_fill_td(td, status, destination | uhci_explen(urb->iso_frame_desc[i].length - 1), |
urb->transfer_dma + urb->iso_frame_desc[i].offset); |
if (i + 1 >= urb->number_of_packets) |
td->status |= cpu_to_le32(TD_CTRL_IOC); |
uhci_insert_td_frame_list(uhci, td, frame); |
} |
return -EINPROGRESS; |
} |
static int uhci_result_isochronous(struct uhci_hcd *uhci, struct urb *urb) |
{ |
struct list_head *tmp, *head; |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
int status; |
int i, ret = 0; |
urb->actual_length = 0; |
i = 0; |
head = &urbp->td_list; |
tmp = head->next; |
while (tmp != head) { |
struct uhci_td *td = list_entry(tmp, struct uhci_td, list); |
int actlength; |
tmp = tmp->next; |
if (td_status(td) & TD_CTRL_ACTIVE) |
return -EINPROGRESS; |
actlength = uhci_actual_length(td_status(td)); |
urb->iso_frame_desc[i].actual_length = actlength; |
urb->actual_length += actlength; |
status = uhci_map_status(uhci_status_bits(td_status(td)), usb_pipeout(urb->pipe)); |
urb->iso_frame_desc[i].status = status; |
if (status) { |
urb->error_count++; |
ret = status; |
} |
i++; |
} |
return ret; |
} |
/* |
* MUST be called with uhci->urb_list_lock acquired |
*/ |
static struct urb *uhci_find_urb_ep(struct uhci_hcd *uhci, struct urb *urb) |
{ |
struct list_head *tmp, *head; |
/* We don't match Isoc transfers since they are special */ |
if (usb_pipeisoc(urb->pipe)) |
return NULL; |
head = &uhci->urb_list; |
tmp = head->next; |
while (tmp != head) { |
struct urb_priv *up = list_entry(tmp, struct urb_priv, urb_list); |
struct urb *u = up->urb; |
tmp = tmp->next; |
if (u->dev == urb->dev && u->status == -EINPROGRESS) { |
/* For control, ignore the direction */ |
if (usb_pipecontrol(urb->pipe) && |
(u->pipe & ~USB_DIR_IN) == (urb->pipe & ~USB_DIR_IN)) |
return u; |
else if (u->pipe == urb->pipe) |
return u; |
} |
} |
return NULL; |
} |
static int uhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, int mem_flags) |
{ |
int ret = -EINVAL; |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
unsigned long flags; |
struct urb *eurb; |
int bustime; |
spin_lock_irqsave(&uhci->urb_list_lock, flags); |
eurb = uhci_find_urb_ep(uhci, urb); |
if (!uhci_alloc_urb_priv(uhci, urb)) { |
spin_unlock_irqrestore(&uhci->urb_list_lock, flags); |
return -ENOMEM; |
} |
switch (usb_pipetype(urb->pipe)) { |
case PIPE_CONTROL: |
ret = uhci_submit_control(uhci, urb, eurb); |
break; |
case PIPE_INTERRUPT: |
if (!eurb) { |
bustime = usb_check_bandwidth(urb->dev, urb); |
if (bustime < 0) |
ret = bustime; |
else { |
ret = uhci_submit_interrupt(uhci, urb, eurb); |
if (ret == -EINPROGRESS) |
usb_claim_bandwidth(urb->dev, urb, bustime, 0); |
} |
} else { /* inherit from parent */ |
urb->bandwidth = eurb->bandwidth; |
ret = uhci_submit_interrupt(uhci, urb, eurb); |
} |
break; |
case PIPE_BULK: |
ret = uhci_submit_bulk(uhci, urb, eurb); |
break; |
case PIPE_ISOCHRONOUS: |
bustime = usb_check_bandwidth(urb->dev, urb); |
if (bustime < 0) { |
ret = bustime; |
break; |
} |
ret = uhci_submit_isochronous(uhci, urb); |
if (ret == -EINPROGRESS) |
usb_claim_bandwidth(urb->dev, urb, bustime, 1); |
break; |
} |
if (ret != -EINPROGRESS) { |
/* Submit failed, so delete it from the urb_list */ |
struct urb_priv *urbp = urb->hcpriv; |
list_del_init(&urbp->urb_list); |
spin_unlock_irqrestore(&uhci->urb_list_lock, flags); |
uhci_destroy_urb_priv (uhci, urb); |
return ret; |
} |
spin_unlock_irqrestore(&uhci->urb_list_lock, flags); |
return 0; |
} |
/* |
* Return the result of a transfer |
* |
* MUST be called with urb_list_lock acquired |
*/ |
static void uhci_transfer_result(struct uhci_hcd *uhci, struct urb *urb) |
{ |
int ret = -EINVAL; |
unsigned long flags; |
struct urb_priv *urbp; |
spin_lock_irqsave(&urb->lock, flags); |
urbp = (struct urb_priv *)urb->hcpriv; |
if (urb->status != -EINPROGRESS) { |
info("uhci_transfer_result: called for URB %p not in flight?", urb); |
goto out; |
} |
switch (usb_pipetype(urb->pipe)) { |
case PIPE_CONTROL: |
ret = uhci_result_control(uhci, urb); |
break; |
case PIPE_INTERRUPT: |
ret = uhci_result_interrupt(uhci, urb); |
break; |
case PIPE_BULK: |
ret = uhci_result_bulk(uhci, urb); |
break; |
case PIPE_ISOCHRONOUS: |
ret = uhci_result_isochronous(uhci, urb); |
break; |
} |
urbp->status = ret; |
if (ret == -EINPROGRESS) |
goto out; |
switch (usb_pipetype(urb->pipe)) { |
case PIPE_CONTROL: |
case PIPE_BULK: |
case PIPE_ISOCHRONOUS: |
/* Release bandwidth for Interrupt or Isoc. transfers */ |
/* Spinlock needed ? */ |
if (urb->bandwidth) |
usb_release_bandwidth(urb->dev, urb, 1); |
uhci_unlink_generic(uhci, urb); |
break; |
case PIPE_INTERRUPT: |
/* Release bandwidth for Interrupt or Isoc. transfers */ |
/* Make sure we don't release if we have a queued URB */ |
spin_lock(&uhci->frame_list_lock); |
/* Spinlock needed ? */ |
if (list_empty(&urbp->queue_list) && urb->bandwidth) |
usb_release_bandwidth(urb->dev, urb, 0); |
else |
/* bandwidth was passed on to queued URB, */ |
/* so don't let usb_unlink_urb() release it */ |
urb->bandwidth = 0; |
spin_unlock(&uhci->frame_list_lock); |
uhci_unlink_generic(uhci, urb); |
break; |
default: |
info("uhci_transfer_result: unknown pipe type %d for urb %p\n", |
usb_pipetype(urb->pipe), urb); |
} |
/* Remove it from uhci->urb_list */ |
list_del_init(&urbp->urb_list); |
uhci_add_complete(uhci, urb); |
out: |
spin_unlock_irqrestore(&urb->lock, flags); |
} |
/* |
* MUST be called with urb->lock acquired |
*/ |
static void uhci_unlink_generic(struct uhci_hcd *uhci, struct urb *urb) |
{ |
struct list_head *head, *tmp; |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
int prevactive = 1; |
/* We can get called when urbp allocation fails, so check */ |
if (!urbp) |
return; |
uhci_dec_fsbr(uhci, urb); /* Safe since it checks */ |
/* |
* Now we need to find out what the last successful toggle was |
* so we can update the local data toggle for the next transfer |
* |
* There's 3 way's the last successful completed TD is found: |
* |
* 1) The TD is NOT active and the actual length < expected length |
* 2) The TD is NOT active and it's the last TD in the chain |
* 3) The TD is active and the previous TD is NOT active |
* |
* Control and Isochronous ignore the toggle, so this is safe |
* for all types |
*/ |
head = &urbp->td_list; |
tmp = head->next; |
while (tmp != head) { |
struct uhci_td *td = list_entry(tmp, struct uhci_td, list); |
tmp = tmp->next; |
if (!(td_status(td) & TD_CTRL_ACTIVE) && |
(uhci_actual_length(td_status(td)) < uhci_expected_length(td_token(td)) || |
tmp == head)) |
usb_settoggle(urb->dev, uhci_endpoint(td_token(td)), |
uhci_packetout(td_token(td)), |
uhci_toggle(td_token(td)) ^ 1); |
else if ((td_status(td) & TD_CTRL_ACTIVE) && !prevactive) |
usb_settoggle(urb->dev, uhci_endpoint(td_token(td)), |
uhci_packetout(td_token(td)), |
uhci_toggle(td_token(td))); |
prevactive = td_status(td) & TD_CTRL_ACTIVE; |
} |
uhci_delete_queued_urb(uhci, urb); |
/* The interrupt loop will reclaim the QH's */ |
uhci_remove_qh(uhci, urbp->qh); |
urbp->qh = NULL; |
} |
static int uhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb) |
{ |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
unsigned long flags; |
struct urb_priv *urbp = urb->hcpriv; |
/* If this is an interrupt URB that is being killed in urb->complete, */ |
/* then just set its status and return */ |
if (!urbp) { |
urb->status = -ECONNRESET; |
return 0; |
} |
spin_lock_irqsave(&uhci->urb_list_lock, flags); |
list_del_init(&urbp->urb_list); |
uhci_unlink_generic(uhci, urb); |
spin_lock(&uhci->urb_remove_list_lock); |
/* If we're the first, set the next interrupt bit */ |
if (list_empty(&uhci->urb_remove_list)) |
uhci_set_next_interrupt(uhci); |
list_add(&urbp->urb_list, &uhci->urb_remove_list); |
spin_unlock(&uhci->urb_remove_list_lock); |
spin_unlock_irqrestore(&uhci->urb_list_lock, flags); |
return 0; |
} |
static int uhci_fsbr_timeout(struct uhci_hcd *uhci, struct urb *urb) |
{ |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
struct list_head *head, *tmp; |
int count = 0; |
uhci_dec_fsbr(uhci, urb); |
urbp->fsbr_timeout = 1; |
/* |
* Ideally we would want to fix qh->element as well, but it's |
* read/write by the HC, so that can introduce a race. It's not |
* really worth the hassle |
*/ |
head = &urbp->td_list; |
tmp = head->next; |
while (tmp != head) { |
struct uhci_td *td = list_entry(tmp, struct uhci_td, list); |
tmp = tmp->next; |
/* |
* Make sure we don't do the last one (since it'll have the |
* TERM bit set) as well as we skip every so many TD's to |
* make sure it doesn't hog the bandwidth |
*/ |
if (tmp != head && (count % DEPTH_INTERVAL) == (DEPTH_INTERVAL - 1)) |
td->link |= UHCI_PTR_DEPTH; |
count++; |
} |
return 0; |
} |
/* |
* uhci_get_current_frame_number() |
* |
* returns the current frame number for a USB bus/controller. |
*/ |
static int uhci_get_current_frame_number(struct uhci_hcd *uhci) |
{ |
return inw(uhci->io_addr + USBFRNUM); |
} |
static int init_stall_timer(struct usb_hcd *hcd); |
static void stall_callback(unsigned long ptr) |
{ |
struct usb_hcd *hcd = (struct usb_hcd *)ptr; |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
struct list_head list, *tmp, *head; |
unsigned long flags; |
INIT_LIST_HEAD(&list); |
spin_lock_irqsave(&uhci->urb_list_lock, flags); |
head = &uhci->urb_list; |
tmp = head->next; |
while (tmp != head) { |
struct urb_priv *up = list_entry(tmp, struct urb_priv, urb_list); |
struct urb *u = up->urb; |
tmp = tmp->next; |
spin_lock(&u->lock); |
/* Check if the FSBR timed out */ |
if (up->fsbr && !up->fsbr_timeout && time_after_eq(jiffies26, up->fsbrtime + IDLE_TIMEOUT)) |
uhci_fsbr_timeout(uhci, u); |
/* Check if the URB timed out */ |
if (u->timeout && time_after_eq(jiffies26, up->inserttime + u->timeout)) |
list_move_tail(&up->urb_list, &list); |
spin_unlock(&u->lock); |
} |
spin_unlock_irqrestore(&uhci->urb_list_lock, flags); |
head = &list; |
tmp = head->next; |
while (tmp != head) { |
struct urb_priv *up = list_entry(tmp, struct urb_priv, urb_list); |
struct urb *u = up->urb; |
tmp = tmp->next; |
uhci_urb_dequeue(hcd, u); |
} |
/* Really disable FSBR */ |
if (!uhci->fsbr && uhci->fsbrtimeout && time_after_eq(jiffies26, uhci->fsbrtimeout)) { |
uhci->fsbrtimeout = 0; |
uhci->skel_term_qh->link = UHCI_PTR_TERM; |
} |
/* Poll for and perform state transitions */ |
hc_state_transitions(uhci); |
init_stall_timer(hcd); |
} |
static int init_stall_timer(struct usb_hcd *hcd) |
{ |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
init_timer(&uhci->stall_timer); |
uhci->stall_timer.function = stall_callback; |
uhci->stall_timer.data = (unsigned long)hcd; |
uhci->stall_timer.expires = jiffies26 + (HZ / 10); |
add_timer(&uhci->stall_timer); |
return 0; |
} |
static void uhci_free_pending_qhs(struct uhci_hcd *uhci) |
{ |
struct list_head *tmp, *head; |
unsigned long flags; |
spin_lock_irqsave(&uhci->qh_remove_list_lock, flags); |
head = &uhci->qh_remove_list; |
tmp = head->next; |
while (tmp != head) { |
struct uhci_qh *qh = list_entry(tmp, struct uhci_qh, remove_list); |
tmp = tmp->next; |
list_del_init(&qh->remove_list); |
uhci_free_qh(uhci, qh); |
} |
spin_unlock_irqrestore(&uhci->qh_remove_list_lock, flags); |
} |
static void uhci_free_pending_tds(struct uhci_hcd *uhci) |
{ |
struct list_head *tmp, *head; |
unsigned long flags; |
spin_lock_irqsave(&uhci->td_remove_list_lock, flags); |
head = &uhci->td_remove_list; |
tmp = head->next; |
while (tmp != head) { |
struct uhci_td *td = list_entry(tmp, struct uhci_td, remove_list); |
tmp = tmp->next; |
list_del_init(&td->remove_list); |
uhci_free_td(uhci, td); |
} |
spin_unlock_irqrestore(&uhci->td_remove_list_lock, flags); |
} |
static void uhci_finish_urb(struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs) |
{ |
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
int status; |
unsigned long flags; |
spin_lock_irqsave(&urb->lock, flags); |
status = urbp->status; |
uhci_destroy_urb_priv(uhci, urb); |
if (urb->status != -ENOENT && urb->status != -ECONNRESET) |
urb->status = status; |
spin_unlock_irqrestore(&urb->lock, flags); |
usb_hcd_giveback_urb(hcd, urb, regs); |
} |
static void uhci_finish_completion(struct usb_hcd *hcd, struct pt_regs *regs) |
{ |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
struct list_head *tmp, *head; |
unsigned long flags; |
spin_lock_irqsave(&uhci->complete_list_lock, flags); |
head = &uhci->complete_list; |
tmp = head->next; |
while (tmp != head) { |
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, complete_list); |
struct urb *urb = urbp->urb; |
list_del_init(&urbp->complete_list); |
spin_unlock_irqrestore(&uhci->complete_list_lock, flags); |
uhci_finish_urb(hcd, urb, regs); |
spin_lock_irqsave(&uhci->complete_list_lock, flags); |
head = &uhci->complete_list; |
tmp = head->next; |
} |
spin_unlock_irqrestore(&uhci->complete_list_lock, flags); |
} |
static void uhci_remove_pending_qhs(struct uhci_hcd *uhci) |
{ |
struct list_head *tmp, *head; |
unsigned long flags; |
spin_lock_irqsave(&uhci->urb_remove_list_lock, flags); |
head = &uhci->urb_remove_list; |
tmp = head->next; |
while (tmp != head) { |
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, urb_list); |
struct urb *urb = urbp->urb; |
tmp = tmp->next; |
list_del_init(&urbp->urb_list); |
urbp->status = urb->status = -ECONNRESET; |
uhci_add_complete(uhci, urb); |
} |
spin_unlock_irqrestore(&uhci->urb_remove_list_lock, flags); |
} |
static void uhci_irq(struct usb_hcd *hcd, struct pt_regs *regs) |
{ |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
unsigned int io_addr = uhci->io_addr; |
unsigned short status; |
struct list_head *tmp, *head; |
static int count =0; |
/* |
* Read the interrupt status, and write it back to clear the |
* interrupt cause |
*/ |
status = inw(io_addr + USBSTS); |
if (!status) /* shared interrupt, not mine */ |
return; |
outw(status, io_addr + USBSTS); /* Clear it */ |
// printk("%x uhci_irq\n", io_addr); |
if (status & ~(USBSTS_USBINT | USBSTS_ERROR | USBSTS_RD)) { |
if (status & USBSTS_HSE) |
{ |
err("%x: host system error, PCI problems?", io_addr); |
} |
if (status & USBSTS_HCPE) |
err("%x: host controller process error. something bad happened", io_addr); |
if ((status & USBSTS_HCH) && uhci->state > 0) { |
err("%x: host controller halted. very bad", io_addr); |
/* FIXME: Reset the controller, fix the offending TD */ |
} |
} |
if (status & USBSTS_RD) |
uhci->resume_detect = 1; |
uhci_free_pending_qhs(uhci); |
uhci_free_pending_tds(uhci); |
uhci_remove_pending_qhs(uhci); |
uhci_clear_next_interrupt(uhci); |
/* Walk the list of pending URB's to see which ones completed */ |
spin_lock(&uhci->urb_list_lock); |
head = &uhci->urb_list; |
tmp = head->next; |
while (tmp != head) { |
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, urb_list); |
struct urb *urb = urbp->urb; |
tmp = tmp->next; |
/* Checks the status and does all of the magic necessary */ |
uhci_transfer_result(uhci, urb); |
} |
spin_unlock(&uhci->urb_list_lock); |
uhci_finish_completion(hcd, regs); |
} |
static void reset_hc(struct uhci_hcd *uhci) |
{ |
unsigned int io_addr = uhci->io_addr; |
/* Global reset for 50ms */ |
uhci->state = UHCI_RESET; |
outw(USBCMD_GRESET, io_addr + USBCMD); |
set_current_state(TASK_UNINTERRUPTIBLE); |
schedule_timeout((HZ*50+999) / 1000); |
outw(0, io_addr + USBCMD); |
/* Another 10ms delay */ |
set_current_state(TASK_UNINTERRUPTIBLE); |
schedule_timeout((HZ*10+999) / 1000); |
uhci->resume_detect = 0; |
} |
static void suspend_hc(struct uhci_hcd *uhci) |
{ |
unsigned int io_addr = uhci->io_addr; |
dbg("%x: suspend_hc", io_addr); |
uhci->state = UHCI_SUSPENDED; |
uhci->resume_detect = 0; |
outw(USBCMD_EGSM, io_addr + USBCMD); |
} |
static void wakeup_hc(struct uhci_hcd *uhci) |
{ |
unsigned int io_addr = uhci->io_addr; |
switch (uhci->state) { |
case UHCI_SUSPENDED: /* Start the resume */ |
dbg("%x: wakeup_hc", io_addr); |
/* Global resume for >= 20ms */ |
outw(USBCMD_FGR | USBCMD_EGSM, io_addr + USBCMD); |
uhci->state = UHCI_RESUMING_1; |
uhci->state_end = jiffies26 + (20*HZ+999) / 1000; |
break; |
case UHCI_RESUMING_1: /* End global resume */ |
uhci->state = UHCI_RESUMING_2; |
outw(0, io_addr + USBCMD); |
/* Falls through */ |
case UHCI_RESUMING_2: /* Wait for EOP to be sent */ |
if (inw(io_addr + USBCMD) & USBCMD_FGR) |
break; |
/* Run for at least 1 second, and |
* mark it configured with a 64-byte max packet */ |
uhci->state = UHCI_RUNNING_GRACE; |
uhci->state_end = jiffies26 + HZ; |
outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, |
io_addr + USBCMD); |
break; |
case UHCI_RUNNING_GRACE: /* Now allowed to suspend */ |
uhci->state = UHCI_RUNNING; |
break; |
default: |
break; |
} |
} |
static int ports_active(struct uhci_hcd *uhci) |
{ |
unsigned int io_addr = uhci->io_addr; |
int connection = 0; |
int i; |
for (i = 0; i < uhci->rh_numports; i++) |
connection |= (inw(io_addr + USBPORTSC1 + i * 2) & USBPORTSC_CCS); |
return connection; |
} |
static int suspend_allowed(struct uhci_hcd *uhci) |
{ |
unsigned int io_addr = uhci->io_addr; |
int i; |
if (!uhci->hcd.pdev || uhci->hcd.pdev->vendor != PCI_VENDOR_ID_INTEL) |
return 1; |
/* Some of Intel's USB controllers have a bug that causes false |
* resume indications if any port has an over current condition. |
* To prevent problems, we will not allow a global suspend if |
* any ports are OC. |
* |
* Some motherboards using Intel's chipsets (but not using all |
* the USB ports) appear to hardwire the over current inputs active |
* to disable the USB ports. |
*/ |
/* check for over current condition on any port */ |
for (i = 0; i < uhci->rh_numports; i++) { |
if (inw(io_addr + USBPORTSC1 + i * 2) & USBPORTSC_OC) |
return 0; |
} |
return 1; |
} |
static void hc_state_transitions(struct uhci_hcd *uhci) |
{ |
switch (uhci->state) { |
case UHCI_RUNNING: |
/* global suspend if nothing connected for 1 second */ |
if (!ports_active(uhci) && suspend_allowed(uhci)) { |
uhci->state = UHCI_SUSPENDING_GRACE; |
uhci->state_end = jiffies26 + HZ; |
} |
break; |
case UHCI_SUSPENDING_GRACE: |
if (ports_active(uhci)) |
uhci->state = UHCI_RUNNING; |
else if (time_after_eq(jiffies26, uhci->state_end)) |
suspend_hc(uhci); |
break; |
case UHCI_SUSPENDED: |
/* wakeup if requested by a device */ |
if (uhci->resume_detect) |
wakeup_hc(uhci); |
break; |
case UHCI_RESUMING_1: |
case UHCI_RESUMING_2: |
case UHCI_RUNNING_GRACE: |
if (time_after_eq(jiffies26, uhci->state_end)) |
wakeup_hc(uhci); |
break; |
default: |
break; |
} |
} |
static void start_hc(struct uhci_hcd *uhci) |
{ |
unsigned int io_addr = uhci->io_addr; |
int timeout = 1000; |
/* |
* Reset the HC - this will force us to get a |
* new notification of any already connected |
* ports due to the virtual disconnect that it |
* implies. |
*/ |
outw(USBCMD_HCRESET, io_addr + USBCMD); |
while (inw(io_addr + USBCMD) & USBCMD_HCRESET) { |
if (!--timeout) { |
printk(KERN_ERR "uhci: USBCMD_HCRESET timed out!\n"); |
break; |
} |
} |
/* Turn on all interrupts */ |
outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP, |
io_addr + USBINTR); |
/* Start at frame 0 */ |
outw(0, io_addr + USBFRNUM); |
outl(uhci->fl->dma_handle, io_addr + USBFLBASEADD); |
/* Run and mark it configured with a 64-byte max packet */ |
uhci->state = UHCI_RUNNING_GRACE; |
uhci->state_end = jiffies26 + HZ; |
outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, io_addr + USBCMD); |
uhci->hcd.state = USB_STATE_RUNNING; |
#ifdef DEB |
{ |
__u32 *tdp; |
int i; |
int status = inw(io_addr + USBSTS); |
printk(KERN_INFO "[%x] Frame = %d Status =%x fl=%x\n", io_addr, inw(io_addr + USBFRNUM), status, uhci->fl->dma_handle); |
for (i=0; i<20; i++) |
{ |
int status = inw(io_addr + USBSTS); |
wait_ms26(500); |
tdp=(__u32*)uhci->fl->frame[i]; |
printk(KERN_INFO "[%x] Frame[%d] -> @%x = %x status=%x fl=%x\n", io_addr, i, uhci->fl->frame[i], *tdp, status, uhci->fl->dma_handle ); |
} |
} |
#endif |
} |
/* |
* De-allocate all resources.. |
*/ |
static void release_uhci(struct uhci_hcd *uhci) |
{ |
int i; |
for (i = 0; i < UHCI_NUM_SKELQH; i++) |
if (uhci->skelqh[i]) { |
uhci_free_qh(uhci, uhci->skelqh[i]); |
uhci->skelqh[i] = NULL; |
} |
if (uhci->term_td) { |
uhci_free_td(uhci, uhci->term_td); |
uhci->term_td = NULL; |
} |
if (uhci->qh_pool) { |
pci_pool_destroy(uhci->qh_pool); |
uhci->qh_pool = NULL; |
} |
if (uhci->td_pool) { |
pci_pool_destroy(uhci->td_pool); |
uhci->td_pool = NULL; |
} |
if (uhci->fl) { |
pci_free_consistent(uhci->hcd.pdev, sizeof(*uhci->fl), uhci->fl, uhci->fl->dma_handle); |
uhci->fl = NULL; |
} |
#ifdef CONFIG_PROC_FS |
if (uhci->proc_entry) { |
remove_proc_entry(uhci->hcd.self.bus_name, uhci_proc_root); |
uhci->proc_entry = NULL; |
} |
#endif |
} |
static int uhci_reset(struct usb_hcd *hcd) |
{ |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
uhci->io_addr = (unsigned long) hcd->regs; |
/* Maybe kick BIOS off this hardware. Then reset, so we won't get |
* interrupts from any previous setup. |
*/ |
reset_hc(uhci); |
pci_write_config_word(hcd->pdev, USBLEGSUP, USBLEGSUP_DEFAULT); |
return 0; |
} |
/* |
* Allocate a frame list, and then setup the skeleton |
* |
* The hardware doesn't really know any difference |
* in the queues, but the order does matter for the |
* protocols higher up. The order is: |
* |
* - any isochronous events handled before any |
* of the queues. We don't do that here, because |
* we'll create the actual TD entries on demand. |
* - The first queue is the interrupt queue. |
* - The second queue is the control queue, split into low and high speed |
* - The third queue is bulk queue. |
* - The fourth queue is the bandwidth reclamation queue, which loops back |
* to the high speed control queue. |
*/ |
static int uhci_start(struct usb_hcd *hcd) |
{ |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
int retval = -EBUSY; |
int i, port; |
unsigned io_size; |
dma_addr_t dma_handle; |
struct usb_device *udev; |
#ifdef CONFIG_PROC_FS |
struct proc_dir_entry *ent; |
#endif |
io_size = pci_resource_len(hcd->pdev, hcd->region); |
#ifdef CONFIG_PROC_FS |
ent = create_proc_entry(hcd->self.bus_name, S_IFREG|S_IRUGO|S_IWUSR, uhci_proc_root); |
if (!ent) { |
err("couldn't create uhci proc entry"); |
retval = -ENOMEM; |
goto err_create_proc_entry; |
} |
ent->data = uhci; |
ent->proc_fops = &uhci_proc_operations; |
ent->size = 0; |
uhci->proc_entry = ent; |
#endif |
uhci->fsbr = 0; |
uhci->fsbrtimeout = 0; |
spin_lock_init(&uhci->qh_remove_list_lock); |
INIT_LIST_HEAD(&uhci->qh_remove_list); |
spin_lock_init(&uhci->td_remove_list_lock); |
INIT_LIST_HEAD(&uhci->td_remove_list); |
spin_lock_init(&uhci->urb_remove_list_lock); |
INIT_LIST_HEAD(&uhci->urb_remove_list); |
spin_lock_init(&uhci->urb_list_lock); |
INIT_LIST_HEAD(&uhci->urb_list); |
spin_lock_init(&uhci->complete_list_lock); |
INIT_LIST_HEAD(&uhci->complete_list); |
spin_lock_init(&uhci->frame_list_lock); |
uhci->fl = pci_alloc_consistent(hcd->pdev, sizeof(*uhci->fl), &dma_handle); |
if (!uhci->fl) { |
err("unable to allocate consistent memory for frame list"); |
goto err_alloc_fl; |
} |
memset((void *)uhci->fl, 0, sizeof(*uhci->fl)); |
uhci->fl->dma_handle = dma_handle; |
uhci->td_pool = pci_pool_create("uhci_td", hcd->pdev, |
sizeof(struct uhci_td), 16, 0); |
if (!uhci->td_pool) { |
err("unable to create td pci_pool"); |
goto err_create_td_pool; |
} |
uhci->qh_pool = pci_pool_create("uhci_qh", hcd->pdev, |
sizeof(struct uhci_qh), 16, 0); |
if (!uhci->qh_pool) { |
err("unable to create qh pci_pool"); |
goto err_create_qh_pool; |
} |
/* Initialize the root hub */ |
/* UHCI specs says devices must have 2 ports, but goes on to say */ |
/* they may have more but give no way to determine how many they */ |
/* have. However, according to the UHCI spec, Bit 7 is always set */ |
/* to 1. So we try to use this to our advantage */ |
for (port = 0; port < (io_size - 0x10) / 2; port++) { |
unsigned int portstatus; |
portstatus = inw(uhci->io_addr + 0x10 + (port * 2)); |
if (!(portstatus & 0x0080)) |
break; |
} |
if (debug) |
info("detected %d ports", port); |
/* This is experimental so anything less than 2 or greater than 8 is */ |
/* something weird and we'll ignore it */ |
if (port < 2 || port > 8) { |
info("port count misdetected? forcing to 2 ports"); |
port = 2; |
} |
uhci->rh_numports = port; |
hcd->self.root_hub = udev = usb_alloc_dev(NULL, &hcd->self); |
if (!udev) { |
err("unable to allocate root hub"); |
goto err_alloc_root_hub; |
} |
uhci->term_td = uhci_alloc_td(uhci, udev); |
if (!uhci->term_td) { |
err("unable to allocate terminating TD"); |
goto err_alloc_term_td; |
} |
for (i = 0; i < UHCI_NUM_SKELQH; i++) { |
uhci->skelqh[i] = uhci_alloc_qh(uhci, udev); |
if (!uhci->skelqh[i]) { |
err("unable to allocate QH %d", i); |
goto err_alloc_skelqh; |
} |
} |
/* |
* 8 Interrupt queues; link int2 to int1, int4 to int2, etc |
* then link int1 to control and control to bulk |
*/ |
uhci->skel_int128_qh->link = cpu_to_le32(uhci->skel_int64_qh->dma_handle) | UHCI_PTR_QH; |
uhci->skel_int64_qh->link = cpu_to_le32(uhci->skel_int32_qh->dma_handle) | UHCI_PTR_QH; |
uhci->skel_int32_qh->link = cpu_to_le32(uhci->skel_int16_qh->dma_handle) | UHCI_PTR_QH; |
uhci->skel_int16_qh->link = cpu_to_le32(uhci->skel_int8_qh->dma_handle) | UHCI_PTR_QH; |
uhci->skel_int8_qh->link = cpu_to_le32(uhci->skel_int4_qh->dma_handle) | UHCI_PTR_QH; |
uhci->skel_int4_qh->link = cpu_to_le32(uhci->skel_int2_qh->dma_handle) | UHCI_PTR_QH; |
uhci->skel_int2_qh->link = cpu_to_le32(uhci->skel_int1_qh->dma_handle) | UHCI_PTR_QH; |
uhci->skel_int1_qh->link = cpu_to_le32(uhci->skel_ls_control_qh->dma_handle) | UHCI_PTR_QH; |
uhci->skel_ls_control_qh->link = cpu_to_le32(uhci->skel_hs_control_qh->dma_handle) | UHCI_PTR_QH; |
uhci->skel_hs_control_qh->link = cpu_to_le32(uhci->skel_bulk_qh->dma_handle) | UHCI_PTR_QH; |
uhci->skel_bulk_qh->link = cpu_to_le32(uhci->skel_term_qh->dma_handle) | UHCI_PTR_QH; |
/* This dummy TD is to work around a bug in Intel PIIX controllers */ |
uhci_fill_td(uhci->term_td, 0, (UHCI_NULL_DATA_SIZE << 21) | |
(0x7f << TD_TOKEN_DEVADDR_SHIFT) | USB_PID_IN, 0); |
uhci->term_td->link = cpu_to_le32(uhci->term_td->dma_handle); |
uhci->skel_term_qh->link = UHCI_PTR_TERM; |
uhci->skel_term_qh->element = cpu_to_le32(uhci->term_td->dma_handle); |
/* |
* Fill the frame list: make all entries point to |
* the proper interrupt queue. |
* |
* This is probably silly, but it's a simple way to |
* scatter the interrupt queues in a way that gives |
* us a reasonable dynamic range for irq latencies. |
*/ |
for (i = 0; i < UHCI_NUMFRAMES; i++) { |
int irq = 0; |
if (i & 1) { |
irq++; |
if (i & 2) { |
irq++; |
if (i & 4) { |
irq++; |
if (i & 8) { |
irq++; |
if (i & 16) { |
irq++; |
if (i & 32) { |
irq++; |
if (i & 64) |
irq++; |
} |
} |
} |
} |
} |
} |
/* Only place we don't use the frame list routines */ |
uhci->fl->frame[i] = cpu_to_le32(uhci->skelqh[7 - irq]->dma_handle); |
} |
start_hc(uhci); |
init_stall_timer(hcd); |
udev->speed = USB_SPEED_FULL; |
if (usb_register_root_hub(udev, &hcd->pdev->dev) != 0) { |
err("unable to start root hub"); |
retval = -ENOMEM; |
goto err_start_root_hub; |
} |
return 0; |
/* |
* error exits: |
*/ |
err_start_root_hub: |
reset_hc(uhci); |
del_timer_sync(&uhci->stall_timer); |
err_alloc_skelqh: |
for (i = 0; i < UHCI_NUM_SKELQH; i++) |
if (uhci->skelqh[i]) { |
uhci_free_qh(uhci, uhci->skelqh[i]); |
uhci->skelqh[i] = NULL; |
} |
uhci_free_td(uhci, uhci->term_td); |
uhci->term_td = NULL; |
err_alloc_term_td: |
usb_put_dev(udev); |
hcd->self.root_hub = NULL; |
err_alloc_root_hub: |
pci_pool_destroy(uhci->qh_pool); |
uhci->qh_pool = NULL; |
err_create_qh_pool: |
pci_pool_destroy(uhci->td_pool); |
uhci->td_pool = NULL; |
err_create_td_pool: |
pci_free_consistent(hcd->pdev, sizeof(*uhci->fl), uhci->fl, uhci->fl->dma_handle); |
uhci->fl = NULL; |
err_alloc_fl: |
#ifdef CONFIG_PROC_FS |
remove_proc_entry(hcd->self.bus_name, uhci_proc_root); |
uhci->proc_entry = NULL; |
err_create_proc_entry: |
#endif |
return retval; |
} |
static void uhci_stop(struct usb_hcd *hcd) |
{ |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
del_timer_sync(&uhci->stall_timer); |
/* |
* At this point, we're guaranteed that no new connects can be made |
* to this bus since there are no more parents |
*/ |
uhci_free_pending_qhs(uhci); |
uhci_free_pending_tds(uhci); |
uhci_remove_pending_qhs(uhci); |
reset_hc(uhci); |
uhci_free_pending_qhs(uhci); |
uhci_free_pending_tds(uhci); |
release_uhci(uhci); |
} |
#ifdef CONFIG_PM |
static int uhci_suspend(struct usb_hcd *hcd, u32 state) |
{ |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
/* Don't try to suspend broken motherboards, reset instead */ |
if (suspend_allowed(uhci)) |
suspend_hc(uhci); |
else |
reset_hc(uhci); |
return 0; |
} |
static int uhci_resume(struct usb_hcd *hcd) |
{ |
struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
pci_set_master(uhci->hcd.pdev); |
if (uhci->state == UHCI_SUSPENDED) |
uhci->resume_detect = 1; |
else { |
reset_hc(uhci); |
start_hc(uhci); |
} |
uhci->hcd.state = USB_STATE_RUNNING; |
return 0; |
} |
#endif |
static struct usb_hcd *uhci_hcd_alloc(void) |
{ |
struct uhci_hcd *uhci; |
uhci = (struct uhci_hcd *)kmalloc(sizeof(*uhci), GFP_KERNEL); |
if (!uhci) |
return NULL; |
memset(uhci, 0, sizeof(*uhci)); |
uhci->hcd.product_desc = "UHCI Host Controller"; |
return &uhci->hcd; |
} |
static void uhci_hcd_free(struct usb_hcd *hcd) |
{ |
kfree(hcd_to_uhci(hcd)); |
} |
static int uhci_hcd_get_frame_number(struct usb_hcd *hcd) |
{ |
return uhci_get_current_frame_number(hcd_to_uhci(hcd)); |
} |
static const char hcd_name[] = "uhci_hcd"; |
static const struct hc_driver uhci_driver = { |
.description = hcd_name, |
/* Generic hardware linkage */ |
.irq = uhci_irq, |
.flags = HCD_USB11, |
/* Basic lifecycle operations */ |
.reset = uhci_reset, |
.start = uhci_start, |
#ifdef CONFIG_PM |
.suspend = uhci_suspend, |
.resume = uhci_resume, |
#endif |
.stop = uhci_stop, |
.hcd_alloc = uhci_hcd_alloc, |
.hcd_free = uhci_hcd_free, |
.urb_enqueue = uhci_urb_enqueue, |
.urb_dequeue = uhci_urb_dequeue, |
.get_frame_number = uhci_hcd_get_frame_number, |
.hub_status_data = uhci_hub_status_data, |
.hub_control = uhci_hub_control, |
}; |
static const struct pci_device_id uhci_pci_ids[] = { { |
/* handle any USB UHCI controller */ |
PCI_DEVICE_CLASS(((PCI_CLASS_SERIAL_USB << 8) | 0x00), ~0), |
.driver_data = (unsigned long) &uhci_driver, |
}, { /* end: all zeroes */ } |
}; |
MODULE_DEVICE_TABLE(pci, uhci_pci_ids); |
static struct pci_driver uhci_pci_driver = { |
.name = (char *)hcd_name, |
.id_table = uhci_pci_ids, |
.probe = usb_hcd_pci_probe, |
.remove = usb_hcd_pci_remove, |
#ifdef CONFIG_PM |
.suspend = usb_hcd_pci_suspend, |
.resume = usb_hcd_pci_resume, |
#endif /* PM */ |
}; |
/*static*/ int __init uhci_hcd_init(void) |
{ |
int retval = -ENOMEM; |
info(DRIVER_DESC " " DRIVER_VERSION); |
if (usb_disabled()) |
return -ENODEV; |
if (debug) { |
errbuf = kmalloc(ERRBUF_LEN, GFP_KERNEL); |
if (!errbuf) |
goto errbuf_failed; |
} |
#ifdef CONFIG_PROC_FS |
uhci_proc_root = create_proc_entry("driver/uhci", S_IFDIR, 0); |
if (!uhci_proc_root) |
goto proc_failed; |
#endif |
//** uhci_up_cachep = kmem_cache_create("uhci_urb_priv", |
//** sizeof(struct urb_priv), 0, 0, NULL, NULL); |
//** if (!uhci_up_cachep) |
//** goto up_failed; |
retval = pci_module_init(&uhci_pci_driver); |
if (retval) |
goto init_failed; |
return 0; |
init_failed: |
//** if (kmem_cache_destroy(uhci_up_cachep)) |
//** printk(KERN_INFO "uhci: not all urb_priv's were freed\n"); |
up_failed: |
#ifdef CONFIG_PROC_FS |
remove_proc_entry("driver/uhci", 0); |
proc_failed: |
#endif |
if (errbuf) |
kfree(errbuf); |
errbuf_failed: |
return retval; |
} |
/*static*/ void __exit uhci_hcd_cleanup(void) |
{ |
pci_unregister_driver(&uhci_pci_driver); |
//** if (kmem_cache_destroy(uhci_up_cachep)) |
//** printk(KERN_INFO "uhci: not all urb_priv's were freed\n"); |
#ifdef CONFIG_PROC_FS |
remove_proc_entry("driver/uhci", 0); |
#endif |
if (errbuf) |
kfree(errbuf); |
} |
module_init(uhci_hcd_init); |
module_exit(uhci_hcd_cleanup); |
MODULE_AUTHOR(DRIVER_AUTHOR); |
MODULE_DESCRIPTION(DRIVER_DESC); |
MODULE_LICENSE("GPL"); |
/shark/trunk/drivers/usb/host/ohci.h |
---|
0,0 → 1,401 |
/* |
* OHCI HCD (Host Controller Driver) for USB. |
* |
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> |
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> |
* |
* This file is licenced under the GPL. |
*/ |
/* |
* OHCI Endpoint Descriptor (ED) ... holds TD queue |
* See OHCI spec, section 4.2 |
* |
* This is a "Queue Head" for those transfers, which is why |
* both EHCI and UHCI call similar structures a "QH". |
*/ |
struct ed { |
/* first fields are hardware-specified, le32 */ |
__u32 hwINFO; /* endpoint config bitmap */ |
/* info bits defined by hcd */ |
#define ED_DEQUEUE __constant_cpu_to_le32(1 << 27) |
/* info bits defined by the hardware */ |
#define ED_ISO __constant_cpu_to_le32(1 << 15) |
#define ED_SKIP __constant_cpu_to_le32(1 << 14) |
#define ED_LOWSPEED __constant_cpu_to_le32(1 << 13) |
#define ED_OUT __constant_cpu_to_le32(0x01 << 11) |
#define ED_IN __constant_cpu_to_le32(0x02 << 11) |
__u32 hwTailP; /* tail of TD list */ |
__u32 hwHeadP; /* head of TD list (hc r/w) */ |
#define ED_C __constant_cpu_to_le32(0x02) /* toggle carry */ |
#define ED_H __constant_cpu_to_le32(0x01) /* halted */ |
__u32 hwNextED; /* next ED in list */ |
/* rest are purely for the driver's use */ |
dma_addr_t dma; /* addr of ED */ |
struct td *dummy; /* next TD to activate */ |
/* host's view of schedule */ |
struct ed *ed_next; /* on schedule or rm_list */ |
struct ed *ed_prev; /* for non-interrupt EDs */ |
struct list_head td_list; /* "shadow list" of our TDs */ |
/* create --> IDLE --> OPER --> ... --> IDLE --> destroy |
* usually: OPER --> UNLINK --> (IDLE | OPER) --> ... |
* some special cases : OPER --> IDLE ... |
*/ |
u8 state; /* ED_{IDLE,UNLINK,OPER} */ |
#define ED_IDLE 0x00 /* NOT linked to HC */ |
#define ED_UNLINK 0x01 /* being unlinked from hc */ |
#define ED_OPER 0x02 /* IS linked to hc */ |
u8 type; /* PIPE_{BULK,...} */ |
/* periodic scheduling params (for intr and iso) */ |
u8 branch; |
u16 interval; |
u16 load; |
u16 last_iso; /* iso only */ |
/* HC may see EDs on rm_list until next frame (frame_no == tick) */ |
u16 tick; |
} __attribute__ ((aligned(16))); |
#define ED_MASK ((u32)~0x0f) /* strip hw status in low addr bits */ |
/* |
* OHCI Transfer Descriptor (TD) ... one per transfer segment |
* See OHCI spec, sections 4.3.1 (general = control/bulk/interrupt) |
* and 4.3.2 (iso) |
*/ |
struct td { |
/* first fields are hardware-specified, le32 */ |
__u32 hwINFO; /* transfer info bitmask */ |
/* hwINFO bits for both general and iso tds: */ |
#define TD_CC 0xf0000000 /* condition code */ |
#define TD_CC_GET(td_p) ((td_p >>28) & 0x0f) |
//#define TD_CC_SET(td_p, cc) (td_p) = ((td_p) & 0x0fffffff) | (((cc) & 0x0f) << 28) |
#define TD_DI 0x00E00000 /* frames before interrupt */ |
#define TD_DI_SET(X) (((X) & 0x07)<< 21) |
/* these two bits are available for definition/use by HCDs in both |
* general and iso tds ... others are available for only one type |
*/ |
#define TD_DONE 0x00020000 /* retired to donelist */ |
#define TD_ISO 0x00010000 /* copy of ED_ISO */ |
/* hwINFO bits for general tds: */ |
#define TD_EC 0x0C000000 /* error count */ |
#define TD_T 0x03000000 /* data toggle state */ |
#define TD_T_DATA0 0x02000000 /* DATA0 */ |
#define TD_T_DATA1 0x03000000 /* DATA1 */ |
#define TD_T_TOGGLE 0x00000000 /* uses ED_C */ |
#define TD_DP 0x00180000 /* direction/pid */ |
#define TD_DP_SETUP 0x00000000 /* SETUP pid */ |
#define TD_DP_IN 0x00100000 /* IN pid */ |
#define TD_DP_OUT 0x00080000 /* OUT pid */ |
/* 0x00180000 rsvd */ |
#define TD_R 0x00040000 /* round: short packets OK? */ |
/* (no hwINFO #defines yet for iso tds) */ |
__u32 hwCBP; /* Current Buffer Pointer (or 0) */ |
__u32 hwNextTD; /* Next TD Pointer */ |
__u32 hwBE; /* Memory Buffer End Pointer */ |
/* PSW is only for ISO */ |
#define MAXPSW 1 /* hardware allows 8 */ |
__u16 hwPSW [MAXPSW]; |
/* rest are purely for the driver's use */ |
__u8 index; |
struct ed *ed; |
struct td *td_hash; /* dma-->td hashtable */ |
struct td *next_dl_td; |
struct urb *urb; |
dma_addr_t td_dma; /* addr of this TD */ |
dma_addr_t data_dma; /* addr of data it points to */ |
struct list_head td_list; /* "shadow list", TDs on same ED */ |
} __attribute__ ((aligned(32))); /* c/b/i need 16; only iso needs 32 */ |
#define TD_MASK ((u32)~0x1f) /* strip hw status in low addr bits */ |
/* |
* Hardware transfer status codes -- CC from td->hwINFO or td->hwPSW |
*/ |
#define TD_CC_NOERROR 0x00 |
#define TD_CC_CRC 0x01 |
#define TD_CC_BITSTUFFING 0x02 |
#define TD_CC_DATATOGGLEM 0x03 |
#define TD_CC_STALL 0x04 |
#define TD_DEVNOTRESP 0x05 |
#define TD_PIDCHECKFAIL 0x06 |
#define TD_UNEXPECTEDPID 0x07 |
#define TD_DATAOVERRUN 0x08 |
#define TD_DATAUNDERRUN 0x09 |
/* 0x0A, 0x0B reserved for hardware */ |
#define TD_BUFFEROVERRUN 0x0C |
#define TD_BUFFERUNDERRUN 0x0D |
/* 0x0E, 0x0F reserved for HCD */ |
#define TD_NOTACCESSED 0x0F |
/* map OHCI TD status codes (CC) to errno values */ |
static const int cc_to_error [16] = { |
/* No Error */ 0, |
/* CRC Error */ -EILSEQ, |
/* Bit Stuff */ -EPROTO, |
/* Data Togg */ -EILSEQ, |
/* Stall */ -EPIPE, |
/* DevNotResp */ -ETIMEDOUT, |
/* PIDCheck */ -EPROTO, |
/* UnExpPID */ -EPROTO, |
/* DataOver */ -EOVERFLOW, |
/* DataUnder */ -EREMOTEIO, |
/* (for hw) */ -EIO, |
/* (for hw) */ -EIO, |
/* BufferOver */ -ECOMM, |
/* BuffUnder */ -ENOSR, |
/* (for HCD) */ -EALREADY, |
/* (for HCD) */ -EALREADY |
}; |
/* |
* The HCCA (Host Controller Communications Area) is a 256 byte |
* structure defined section 4.4.1 of the OHCI spec. The HC is |
* told the base address of it. It must be 256-byte aligned. |
*/ |
struct ohci_hcca { |
#define NUM_INTS 32 |
__u32 int_table [NUM_INTS]; /* periodic schedule */ |
__u16 frame_no; /* current frame number */ |
__u16 pad1; /* set to 0 on each frame_no change */ |
__u32 done_head; /* info returned for an interrupt */ |
u8 reserved_for_hc [116]; |
u8 what [4]; /* spec only identifies 252 bytes :) */ |
} __attribute__ ((aligned(256))); |
/* |
* This is the structure of the OHCI controller's memory mapped I/O region. |
* You must use readl() and writel() (in <asm/io.h>) to access these fields!! |
* Layout is in section 7 (and appendix B) of the spec. |
*/ |
struct ohci_regs { |
/* control and status registers (section 7.1) */ |
__u32 revision; |
__u32 control; |
__u32 cmdstatus; |
__u32 intrstatus; |
__u32 intrenable; |
__u32 intrdisable; |
/* memory pointers (section 7.2) */ |
__u32 hcca; |
__u32 ed_periodcurrent; |
__u32 ed_controlhead; |
__u32 ed_controlcurrent; |
__u32 ed_bulkhead; |
__u32 ed_bulkcurrent; |
__u32 donehead; |
/* frame counters (section 7.3) */ |
__u32 fminterval; |
__u32 fmremaining; |
__u32 fmnumber; |
__u32 periodicstart; |
__u32 lsthresh; |
/* Root hub ports (section 7.4) */ |
struct ohci_roothub_regs { |
__u32 a; |
__u32 b; |
__u32 status; |
#define MAX_ROOT_PORTS 15 /* maximum OHCI root hub ports (RH_A_NDP) */ |
__u32 portstatus [MAX_ROOT_PORTS]; |
} roothub; |
/* and optional "legacy support" registers (appendix B) at 0x0100 */ |
} __attribute__ ((aligned(32))); |
/* OHCI CONTROL AND STATUS REGISTER MASKS */ |
/* |
* HcControl (control) register masks |
*/ |
#define OHCI_CTRL_CBSR (3 << 0) /* control/bulk service ratio */ |
#define OHCI_CTRL_PLE (1 << 2) /* periodic list enable */ |
#define OHCI_CTRL_IE (1 << 3) /* isochronous enable */ |
#define OHCI_CTRL_CLE (1 << 4) /* control list enable */ |
#define OHCI_CTRL_BLE (1 << 5) /* bulk list enable */ |
#define OHCI_CTRL_HCFS (3 << 6) /* host controller functional state */ |
#define OHCI_CTRL_IR (1 << 8) /* interrupt routing */ |
#define OHCI_CTRL_RWC (1 << 9) /* remote wakeup connected */ |
#define OHCI_CTRL_RWE (1 << 10) /* remote wakeup enable */ |
/* pre-shifted values for HCFS */ |
# define OHCI_USB_RESET (0 << 6) |
# define OHCI_USB_RESUME (1 << 6) |
# define OHCI_USB_OPER (2 << 6) |
# define OHCI_USB_SUSPEND (3 << 6) |
/* |
* HcCommandStatus (cmdstatus) register masks |
*/ |
#define OHCI_HCR (1 << 0) /* host controller reset */ |
#define OHCI_CLF (1 << 1) /* control list filled */ |
#define OHCI_BLF (1 << 2) /* bulk list filled */ |
#define OHCI_OCR (1 << 3) /* ownership change request */ |
#define OHCI_SOC (3 << 16) /* scheduling overrun count */ |
/* |
* masks used with interrupt registers: |
* HcInterruptStatus (intrstatus) |
* HcInterruptEnable (intrenable) |
* HcInterruptDisable (intrdisable) |
*/ |
#define OHCI_INTR_SO (1 << 0) /* scheduling overrun */ |
#define OHCI_INTR_WDH (1 << 1) /* writeback of done_head */ |
#define OHCI_INTR_SF (1 << 2) /* start frame */ |
#define OHCI_INTR_RD (1 << 3) /* resume detect */ |
#define OHCI_INTR_UE (1 << 4) /* unrecoverable error */ |
#define OHCI_INTR_FNO (1 << 5) /* frame number overflow */ |
#define OHCI_INTR_RHSC (1 << 6) /* root hub status change */ |
#define OHCI_INTR_OC (1 << 30) /* ownership change */ |
#define OHCI_INTR_MIE (1 << 31) /* master interrupt enable */ |
/* OHCI ROOT HUB REGISTER MASKS */ |
/* roothub.portstatus [i] bits */ |
#define RH_PS_CCS 0x00000001 /* current connect status */ |
#define RH_PS_PES 0x00000002 /* port enable status*/ |
#define RH_PS_PSS 0x00000004 /* port suspend status */ |
#define RH_PS_POCI 0x00000008 /* port over current indicator */ |
#define RH_PS_PRS 0x00000010 /* port reset status */ |
#define RH_PS_PPS 0x00000100 /* port power status */ |
#define RH_PS_LSDA 0x00000200 /* low speed device attached */ |
#define RH_PS_CSC 0x00010000 /* connect status change */ |
#define RH_PS_PESC 0x00020000 /* port enable status change */ |
#define RH_PS_PSSC 0x00040000 /* port suspend status change */ |
#define RH_PS_OCIC 0x00080000 /* over current indicator change */ |
#define RH_PS_PRSC 0x00100000 /* port reset status change */ |
/* roothub.status bits */ |
#define RH_HS_LPS 0x00000001 /* local power status */ |
#define RH_HS_OCI 0x00000002 /* over current indicator */ |
#define RH_HS_DRWE 0x00008000 /* device remote wakeup enable */ |
#define RH_HS_LPSC 0x00010000 /* local power status change */ |
#define RH_HS_OCIC 0x00020000 /* over current indicator change */ |
#define RH_HS_CRWE 0x80000000 /* clear remote wakeup enable */ |
/* roothub.b masks */ |
#define RH_B_DR 0x0000ffff /* device removable flags */ |
#define RH_B_PPCM 0xffff0000 /* port power control mask */ |
/* roothub.a masks */ |
#define RH_A_NDP (0xff << 0) /* number of downstream ports */ |
#define RH_A_PSM (1 << 8) /* power switching mode */ |
#define RH_A_NPS (1 << 9) /* no power switching */ |
#define RH_A_DT (1 << 10) /* device type (mbz) */ |
#define RH_A_OCPM (1 << 11) /* over current protection mode */ |
#define RH_A_NOCP (1 << 12) /* no over current protection */ |
#define RH_A_POTPGT (0xff << 24) /* power on to power good time */ |
/* hcd-private per-urb state */ |
typedef struct urb_priv { |
struct ed *ed; |
__u16 length; // # tds in this request |
__u16 td_cnt; // tds already serviced |
struct td *td [0]; // all TDs in this request |
} urb_priv_t; |
#define TD_HASH_SIZE 64 /* power'o'two */ |
// sizeof (struct td) ~= 64 == 2^6 ... |
#define TD_HASH_FUNC(td_dma) ((td_dma ^ (td_dma >> 6)) % TD_HASH_SIZE) |
/* |
* This is the full ohci controller description |
* |
* Note how the "proper" USB information is just |
* a subset of what the full implementation needs. (Linus) |
*/ |
struct ohci_hcd { |
spinlock_t lock; |
/* |
* I/O memory used to communicate with the HC (dma-consistent) |
*/ |
struct ohci_regs *regs; |
/* |
* main memory used to communicate with the HC (dma-consistent). |
* hcd adds to schedule for a live hc any time, but removals finish |
* only at the start of the next frame. |
*/ |
struct ohci_hcca *hcca; |
dma_addr_t hcca_dma; |
struct ed *ed_rm_list; /* to be removed */ |
struct ed *ed_bulktail; /* last in bulk list */ |
struct ed *ed_controltail; /* last in ctrl list */ |
struct ed *periodic [NUM_INTS]; /* shadow int_table */ |
/* |
* memory management for queue data structures |
*/ |
struct pci_pool *td_cache; |
struct pci_pool *ed_cache; |
struct td *td_hash [TD_HASH_SIZE]; |
/* |
* driver state |
*/ |
int load [NUM_INTS]; |
u32 hc_control; /* copy of hc control reg */ |
unsigned long flags; /* for HC bugs */ |
#define OHCI_QUIRK_AMD756 0x01 /* erratum #4 */ |
#define OHCI_QUIRK_SUPERIO 0x02 /* natsemi */ |
// there are also chip quirks/bugs in init logic |
/* |
* framework state |
*/ |
struct usb_hcd hcd; |
}; |
#define hcd_to_ohci(hcd_ptr) container_of(hcd_ptr, struct ohci_hcd, hcd) |
/*-------------------------------------------------------------------------*/ |
#ifndef DEBUG |
#define STUB_DEBUG_FILES |
#endif /* DEBUG */ |
#define ohci_dbg(ohci, fmt, args...) \ |
dev_dbg ((ohci)->hcd.controller , fmt , ## args ) |
#define ohci_err(ohci, fmt, args...) \ |
dev_err ((ohci)->hcd.controller , fmt , ## args ) |
#define ohci_info(ohci, fmt, args...) \ |
dev_info ((ohci)->hcd.controller , fmt , ## args ) |
#define ohci_warn(ohci, fmt, args...) \ |
dev_warn ((ohci)->hcd.controller , fmt , ## args ) |
#ifdef OHCI_VERBOSE_DEBUG |
# define ohci_vdbg ohci_dbg |
#else |
# define ohci_vdbg(ohci, fmt, args...) do { } while (0) |
#endif |
/shark/trunk/drivers/usb/host/uhci-hcd.h |
---|
0,0 → 1,422 |
#ifndef __LINUX_UHCI_HCD_H |
#define __LINUX_UHCI_HCD_H |
#include <linux/list.h> |
#include <linux/usb.h> |
#define usb_packetid(pipe) (usb_pipein(pipe) ? USB_PID_IN : USB_PID_OUT) |
#define PIPE_DEVEP_MASK 0x0007ff00 |
/* |
* Universal Host Controller Interface data structures and defines |
*/ |
/* Command register */ |
#define USBCMD 0 |
#define USBCMD_RS 0x0001 /* Run/Stop */ |
#define USBCMD_HCRESET 0x0002 /* Host reset */ |
#define USBCMD_GRESET 0x0004 /* Global reset */ |
#define USBCMD_EGSM 0x0008 /* Global Suspend Mode */ |
#define USBCMD_FGR 0x0010 /* Force Global Resume */ |
#define USBCMD_SWDBG 0x0020 /* SW Debug mode */ |
#define USBCMD_CF 0x0040 /* Config Flag (sw only) */ |
#define USBCMD_MAXP 0x0080 /* Max Packet (0 = 32, 1 = 64) */ |
/* Status register */ |
#define USBSTS 2 |
#define USBSTS_USBINT 0x0001 /* Interrupt due to IOC */ |
#define USBSTS_ERROR 0x0002 /* Interrupt due to error */ |
#define USBSTS_RD 0x0004 /* Resume Detect */ |
#define USBSTS_HSE 0x0008 /* Host System Error - basically PCI problems */ |
#define USBSTS_HCPE 0x0010 /* Host Controller Process Error - the scripts were buggy */ |
#define USBSTS_HCH 0x0020 /* HC Halted */ |
/* Interrupt enable register */ |
#define USBINTR 4 |
#define USBINTR_TIMEOUT 0x0001 /* Timeout/CRC error enable */ |
#define USBINTR_RESUME 0x0002 /* Resume interrupt enable */ |
#define USBINTR_IOC 0x0004 /* Interrupt On Complete enable */ |
#define USBINTR_SP 0x0008 /* Short packet interrupt enable */ |
#define USBFRNUM 6 |
#define USBFLBASEADD 8 |
#define USBSOF 12 |
/* USB port status and control registers */ |
#define USBPORTSC1 16 |
#define USBPORTSC2 18 |
#define USBPORTSC_CCS 0x0001 /* Current Connect Status ("device present") */ |
#define USBPORTSC_CSC 0x0002 /* Connect Status Change */ |
#define USBPORTSC_PE 0x0004 /* Port Enable */ |
#define USBPORTSC_PEC 0x0008 /* Port Enable Change */ |
#define USBPORTSC_LS 0x0030 /* Line Status */ |
#define USBPORTSC_RD 0x0040 /* Resume Detect */ |
#define USBPORTSC_LSDA 0x0100 /* Low Speed Device Attached */ |
#define USBPORTSC_PR 0x0200 /* Port Reset */ |
#define USBPORTSC_OC 0x0400 /* Over Current condition */ |
#define USBPORTSC_SUSP 0x1000 /* Suspend */ |
/* Legacy support register */ |
#define USBLEGSUP 0xc0 |
#define USBLEGSUP_DEFAULT 0x2000 /* only PIRQ enable set */ |
#define UHCI_NULL_DATA_SIZE 0x7FF /* for UHCI controller TD */ |
#define UHCI_PTR_BITS cpu_to_le32(0x000F) |
#define UHCI_PTR_TERM cpu_to_le32(0x0001) |
#define UHCI_PTR_QH cpu_to_le32(0x0002) |
#define UHCI_PTR_DEPTH cpu_to_le32(0x0004) |
#define UHCI_PTR_BREADTH cpu_to_le32(0x0000) |
#define UHCI_NUMFRAMES 1024 /* in the frame list [array] */ |
#define UHCI_MAX_SOF_NUMBER 2047 /* in an SOF packet */ |
#define CAN_SCHEDULE_FRAMES 1000 /* how far future frames can be scheduled */ |
struct uhci_frame_list { |
__u32 frame[UHCI_NUMFRAMES]; |
void *frame_cpu[UHCI_NUMFRAMES]; |
dma_addr_t dma_handle; |
}; |
struct urb_priv; |
/* |
* One role of a QH is to hold a queue of TDs for some endpoint. Each QH is |
* used with one URB, and qh->element (updated by the HC) is either: |
* - the next unprocessed TD for the URB, or |
* - UHCI_PTR_TERM (when there's no more traffic for this endpoint), or |
* - the QH for the next URB queued to the same endpoint. |
* |
* The other role of a QH is to serve as a "skeleton" framelist entry, so we |
* can easily splice a QH for some endpoint into the schedule at the right |
* place. Then qh->element is UHCI_PTR_TERM. |
* |
* In the frame list, qh->link maintains a list of QHs seen by the HC: |
* skel1 --> ep1-qh --> ep2-qh --> ... --> skel2 --> ... |
*/ |
struct uhci_qh { |
/* Hardware fields */ |
__u32 link; /* Next queue */ |
__u32 element; /* Queue element pointer */ |
/* Software fields */ |
dma_addr_t dma_handle; |
struct usb_device *dev; |
struct urb_priv *urbp; |
struct list_head list; /* P: uhci->frame_list_lock */ |
struct list_head remove_list; /* P: uhci->remove_list_lock */ |
} __attribute__((aligned(16))); |
/* |
* for TD <status>: |
*/ |
#define td_status(td) le32_to_cpu((td)->status) |
#define TD_CTRL_SPD (1 << 29) /* Short Packet Detect */ |
#define TD_CTRL_C_ERR_MASK (3 << 27) /* Error Counter bits */ |
#define TD_CTRL_C_ERR_SHIFT 27 |
#define TD_CTRL_LS (1 << 26) /* Low Speed Device */ |
#define TD_CTRL_IOS (1 << 25) /* Isochronous Select */ |
#define TD_CTRL_IOC (1 << 24) /* Interrupt on Complete */ |
#define TD_CTRL_ACTIVE (1 << 23) /* TD Active */ |
#define TD_CTRL_STALLED (1 << 22) /* TD Stalled */ |
#define TD_CTRL_DBUFERR (1 << 21) /* Data Buffer Error */ |
#define TD_CTRL_BABBLE (1 << 20) /* Babble Detected */ |
#define TD_CTRL_NAK (1 << 19) /* NAK Received */ |
#define TD_CTRL_CRCTIMEO (1 << 18) /* CRC/Time Out Error */ |
#define TD_CTRL_BITSTUFF (1 << 17) /* Bit Stuff Error */ |
#define TD_CTRL_ACTLEN_MASK 0x7FF /* actual length, encoded as n - 1 */ |
#define TD_CTRL_ANY_ERROR (TD_CTRL_STALLED | TD_CTRL_DBUFERR | \ |
TD_CTRL_BABBLE | TD_CTRL_CRCTIME | TD_CTRL_BITSTUFF) |
#define uhci_maxerr(err) ((err) << TD_CTRL_C_ERR_SHIFT) |
#define uhci_status_bits(ctrl_sts) ((ctrl_sts) & 0xFE0000) |
#define uhci_actual_length(ctrl_sts) (((ctrl_sts) + 1) & TD_CTRL_ACTLEN_MASK) /* 1-based */ |
/* |
* for TD <info>: (a.k.a. Token) |
*/ |
#define td_token(td) le32_to_cpu((td)->token) |
#define TD_TOKEN_DEVADDR_SHIFT 8 |
#define TD_TOKEN_TOGGLE_SHIFT 19 |
#define TD_TOKEN_TOGGLE (1 << 19) |
#define TD_TOKEN_EXPLEN_SHIFT 21 |
#define TD_TOKEN_EXPLEN_MASK 0x7FF /* expected length, encoded as n - 1 */ |
#define TD_TOKEN_PID_MASK 0xFF |
#define uhci_explen(len) ((len) << TD_TOKEN_EXPLEN_SHIFT) |
#define uhci_expected_length(token) ((((token) >> 21) + 1) & TD_TOKEN_EXPLEN_MASK) |
#define uhci_toggle(token) (((token) >> TD_TOKEN_TOGGLE_SHIFT) & 1) |
#define uhci_endpoint(token) (((token) >> 15) & 0xf) |
#define uhci_devaddr(token) (((token) >> TD_TOKEN_DEVADDR_SHIFT) & 0x7f) |
#define uhci_devep(token) (((token) >> TD_TOKEN_DEVADDR_SHIFT) & 0x7ff) |
#define uhci_packetid(token) ((token) & TD_TOKEN_PID_MASK) |
#define uhci_packetout(token) (uhci_packetid(token) != USB_PID_IN) |
#define uhci_packetin(token) (uhci_packetid(token) == USB_PID_IN) |
/* |
* The documentation says "4 words for hardware, 4 words for software". |
* |
* That's silly, the hardware doesn't care. The hardware only cares that |
* the hardware words are 16-byte aligned, and we can have any amount of |
* sw space after the TD entry as far as I can tell. |
* |
* But let's just go with the documentation, at least for 32-bit machines. |
* On 64-bit machines we probably want to take advantage of the fact that |
* hw doesn't really care about the size of the sw-only area. |
* |
* Alas, not anymore, we have more than 4 words for software, woops. |
* Everything still works tho, surprise! -jerdfelt |
* |
* td->link points to either another TD (not necessarily for the same urb or |
* even the same endpoint), or nothing (PTR_TERM), or a QH (for queued urbs) |
*/ |
struct uhci_td { |
/* Hardware fields */ |
__u32 link; |
__u32 status; |
__u32 token; |
__u32 buffer; |
/* Software fields */ |
dma_addr_t dma_handle; |
struct usb_device *dev; |
struct urb *urb; |
struct list_head list; /* P: urb->lock */ |
struct list_head remove_list; /* P: uhci->td_remove_list_lock */ |
int frame; /* for iso: what frame? */ |
struct list_head fl_list; /* P: uhci->frame_list_lock */ |
} __attribute__((aligned(16))); |
/* |
* The UHCI driver places Interrupt, Control and Bulk into QH's both |
* to group together TD's for one transfer, and also to faciliate queuing |
* of URB's. To make it easy to insert entries into the schedule, we have |
* a skeleton of QH's for each predefined Interrupt latency, low speed |
* control, high speed control and terminating QH (see explanation for |
* the terminating QH below). |
* |
* When we want to add a new QH, we add it to the end of the list for the |
* skeleton QH. |
* |
* For instance, the queue can look like this: |
* |
* skel int128 QH |
* dev 1 interrupt QH |
* dev 5 interrupt QH |
* skel int64 QH |
* skel int32 QH |
* ... |
* skel int1 QH |
* skel low speed control QH |
* dev 5 control QH |
* skel high speed control QH |
* skel bulk QH |
* dev 1 bulk QH |
* dev 2 bulk QH |
* skel terminating QH |
* |
* The terminating QH is used for 2 reasons: |
* - To place a terminating TD which is used to workaround a PIIX bug |
* (see Intel errata for explanation) |
* - To loop back to the high speed control queue for full speed bandwidth |
* reclamation |
* |
* Isochronous transfers are stored before the start of the skeleton |
* schedule and don't use QH's. While the UHCI spec doesn't forbid the |
* use of QH's for Isochronous, it doesn't use them either. Since we don't |
* need to use them either, we follow the spec diagrams in hope that it'll |
* be more compatible with future UHCI implementations. |
*/ |
#define UHCI_NUM_SKELQH 12 |
#define skel_int128_qh skelqh[0] |
#define skel_int64_qh skelqh[1] |
#define skel_int32_qh skelqh[2] |
#define skel_int16_qh skelqh[3] |
#define skel_int8_qh skelqh[4] |
#define skel_int4_qh skelqh[5] |
#define skel_int2_qh skelqh[6] |
#define skel_int1_qh skelqh[7] |
#define skel_ls_control_qh skelqh[8] |
#define skel_hs_control_qh skelqh[9] |
#define skel_bulk_qh skelqh[10] |
#define skel_term_qh skelqh[11] |
/* |
* Search tree for determining where <interval> fits in the skelqh[] |
* skeleton. |
* |
* An interrupt request should be placed into the slowest skelqh[] |
* which meets the interval/period/frequency requirement. |
* An interrupt request is allowed to be faster than <interval> but not slower. |
* |
* For a given <interval>, this function returns the appropriate/matching |
* skelqh[] index value. |
*/ |
static inline int __interval_to_skel(int interval) |
{ |
if (interval < 16) { |
if (interval < 4) { |
if (interval < 2) |
return 7; /* int1 for 0-1 ms */ |
return 6; /* int2 for 2-3 ms */ |
} |
if (interval < 8) |
return 5; /* int4 for 4-7 ms */ |
return 4; /* int8 for 8-15 ms */ |
} |
if (interval < 64) { |
if (interval < 32) |
return 3; /* int16 for 16-31 ms */ |
return 2; /* int32 for 32-63 ms */ |
} |
if (interval < 128) |
return 1; /* int64 for 64-127 ms */ |
return 0; /* int128 for 128-255 ms (Max.) */ |
} |
/* |
* Device states for the host controller. |
* |
* To prevent "bouncing" in the presence of electrical noise, |
* we insist on a 1-second "grace" period, before switching to |
* the RUNNING or SUSPENDED states, during which the state is |
* not allowed to change. |
* |
* The resume process is divided into substates in order to avoid |
* potentially length delays during the timer handler. |
* |
* States in which the host controller is halted must have values <= 0. |
*/ |
enum uhci_state { |
UHCI_RESET, |
UHCI_RUNNING_GRACE, /* Before RUNNING */ |
UHCI_RUNNING, /* The normal state */ |
UHCI_SUSPENDING_GRACE, /* Before SUSPENDED */ |
UHCI_SUSPENDED = -10, /* When no devices are attached */ |
UHCI_RESUMING_1, |
UHCI_RESUMING_2 |
}; |
#define hcd_to_uhci(hcd_ptr) container_of(hcd_ptr, struct uhci_hcd, hcd) |
/* |
* This describes the full uhci information. |
* |
* Note how the "proper" USB information is just |
* a subset of what the full implementation needs. |
*/ |
struct uhci_hcd { |
struct usb_hcd hcd; |
#ifdef CONFIG_PROC_FS |
/* procfs */ |
struct proc_dir_entry *proc_entry; |
#endif |
/* Grabbed from PCI */ |
unsigned long io_addr; |
struct pci_pool *qh_pool; |
struct pci_pool *td_pool; |
struct usb_bus *bus; |
struct uhci_td *term_td; /* Terminating TD, see UHCI bug */ |
struct uhci_qh *skelqh[UHCI_NUM_SKELQH]; /* Skeleton QH's */ |
spinlock_t frame_list_lock; |
struct uhci_frame_list *fl; /* P: uhci->frame_list_lock */ |
int fsbr; /* Full speed bandwidth reclamation */ |
unsigned long fsbrtimeout; /* FSBR delay */ |
enum uhci_state state; /* FIXME: needs a spinlock */ |
unsigned long state_end; /* Time of next transition */ |
int resume_detect; /* Need a Global Resume */ |
/* Main list of URB's currently controlled by this HC */ |
spinlock_t urb_list_lock; |
struct list_head urb_list; /* P: uhci->urb_list_lock */ |
/* List of QH's that are done, but waiting to be unlinked (race) */ |
spinlock_t qh_remove_list_lock; |
struct list_head qh_remove_list; /* P: uhci->qh_remove_list_lock */ |
/* List of TD's that are done, but waiting to be freed (race) */ |
spinlock_t td_remove_list_lock; |
struct list_head td_remove_list; /* P: uhci->td_remove_list_lock */ |
/* List of asynchronously unlinked URB's */ |
spinlock_t urb_remove_list_lock; |
struct list_head urb_remove_list; /* P: uhci->urb_remove_list_lock */ |
/* List of URB's awaiting completion callback */ |
spinlock_t complete_list_lock; |
struct list_head complete_list; /* P: uhci->complete_list_lock */ |
int rh_numports; |
struct timer_list stall_timer; |
}; |
struct urb_priv { |
struct list_head urb_list; |
struct urb *urb; |
struct usb_device *dev; |
struct uhci_qh *qh; /* QH for this URB */ |
struct list_head td_list; /* P: urb->lock */ |
int fsbr : 1; /* URB turned on FSBR */ |
int fsbr_timeout : 1; /* URB timed out on FSBR */ |
int queued : 1; /* QH was queued (not linked in) */ |
int short_control_packet : 1; /* If we get a short packet during */ |
/* a control transfer, retrigger */ |
/* the status phase */ |
int status; /* Final status */ |
unsigned long inserttime; /* In jiffies */ |
unsigned long fsbrtime; /* In jiffies */ |
struct list_head queue_list; /* P: uhci->frame_list_lock */ |
struct list_head complete_list; /* P: uhci->complete_list_lock */ |
}; |
/* |
* Locking in uhci.c |
* |
* spinlocks are used extensively to protect the many lists and data |
* structures we have. It's not that pretty, but it's necessary. We |
* need to be done with all of the locks (except complete_list_lock) when |
* we call urb->complete. I've tried to make it simple enough so I don't |
* have to spend hours racking my brain trying to figure out if the |
* locking is safe. |
* |
* Here's the safe locking order to prevent deadlocks: |
* |
* #1 uhci->urb_list_lock |
* #2 urb->lock |
* #3 uhci->urb_remove_list_lock, uhci->frame_list_lock, |
* uhci->qh_remove_list_lock |
* #4 uhci->complete_list_lock |
* |
* If you're going to grab 2 or more locks at once, ALWAYS grab the lock |
* at the lowest level FIRST and NEVER grab locks at the same level at the |
* same time. |
* |
* So, if you need uhci->urb_list_lock, grab it before you grab urb->lock |
*/ |
#endif |
/shark/trunk/drivers/usb/host/ohci-pci.c |
---|
0,0 → 1,396 |
/* |
* OHCI HCD (Host Controller Driver) for USB. |
* |
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> |
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> |
* |
* [ Initialisation is based on Linus' ] |
* [ uhci code and gregs ohci fragments ] |
* [ (C) Copyright 1999 Linus Torvalds ] |
* [ (C) Copyright 1999 Gregory P. Smith] |
* |
* PCI Bus Glue |
* |
* This file is licenced under the GPL. |
*/ |
#ifdef CONFIG_PMAC_PBOOK |
#include <asm/machdep.h> |
#include <asm/pmac_feature.h> |
#include <asm/pci-bridge.h> |
#include <asm/prom.h> |
#ifndef CONFIG_PM |
# define CONFIG_PM |
#endif |
#endif |
#ifndef CONFIG_PCI |
#error "This file is PCI bus glue. CONFIG_PCI must be defined." |
#endif |
/*-------------------------------------------------------------------------*/ |
static int |
ohci_pci_reset (struct usb_hcd *hcd) |
{ |
struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
ohci->regs = hcd->regs; |
return hc_reset (ohci); |
} |
static int __devinit |
ohci_pci_start (struct usb_hcd *hcd) |
{ |
struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
int ret; |
if (hcd->pdev) { |
ohci->hcca = pci_alloc_consistent (hcd->pdev, |
sizeof *ohci->hcca, &ohci->hcca_dma); |
if (!ohci->hcca) |
return -ENOMEM; |
/* AMD 756, for most chips (early revs), corrupts register |
* values on read ... so enable the vendor workaround. |
*/ |
if (hcd->pdev->vendor == PCI_VENDOR_ID_AMD |
&& hcd->pdev->device == 0x740c) { |
ohci->flags = OHCI_QUIRK_AMD756; |
ohci_info (ohci, "AMD756 erratum 4 workaround\n"); |
} |
/* FIXME for some of the early AMD 760 southbridges, OHCI |
* won't work at all. blacklist them. |
*/ |
/* Apple's OHCI driver has a lot of bizarre workarounds |
* for this chip. Evidently control and bulk lists |
* can get confused. (B&W G3 models, and ...) |
*/ |
else if (hcd->pdev->vendor == PCI_VENDOR_ID_OPTI |
&& hcd->pdev->device == 0xc861) { |
ohci_info (ohci, |
"WARNING: OPTi workarounds unavailable\n"); |
} |
/* Check for NSC87560. We have to look at the bridge (fn1) to |
* identify the USB (fn2). This quirk might apply to more or |
* even all NSC stuff. |
*/ |
else if (hcd->pdev->vendor == PCI_VENDOR_ID_NS) { |
struct pci_dev *b, *hc; |
hc = hcd->pdev; |
b = pci_find_slot (hc->bus->number, |
PCI_DEVFN (PCI_SLOT (hc->devfn), 1)); |
if (b && b->device == PCI_DEVICE_ID_NS_87560_LIO |
&& b->vendor == PCI_VENDOR_ID_NS) { |
ohci->flags |= OHCI_QUIRK_SUPERIO; |
ohci_info (ohci, "Using NSC SuperIO setup\n"); |
} |
} |
} |
memset (ohci->hcca, 0, sizeof (struct ohci_hcca)); |
if ((ret = ohci_mem_init (ohci)) < 0) { |
ohci_stop (hcd); |
return ret; |
} |
if (hc_start (ohci) < 0) { |
ohci_err (ohci, "can't start\n"); |
ohci_stop (hcd); |
return -EBUSY; |
} |
create_debug_files (ohci); |
#ifdef DEBUG |
ohci_dump (ohci, 1); |
#endif |
return 0; |
} |
#ifdef CONFIG_PM |
static int ohci_pci_suspend (struct usb_hcd *hcd, u32 state) |
{ |
struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
u16 cmd; |
u32 tmp; |
if ((ohci->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_OPER) { |
ohci_dbg (ohci, "can't suspend (state is %s)\n", |
hcfs2string (ohci->hc_control & OHCI_CTRL_HCFS)); |
return -EIO; |
} |
/* act as if usb suspend can always be used */ |
ohci_dbg (ohci, "suspend to %d\n", state); |
/* First stop processing */ |
spin_lock_irq (&ohci->lock); |
ohci->hc_control &= |
~(OHCI_CTRL_PLE|OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_IE); |
writel (ohci->hc_control, &ohci->regs->control); |
writel (OHCI_INTR_SF, &ohci->regs->intrstatus); |
(void) readl (&ohci->regs->intrstatus); |
spin_unlock_irq (&ohci->lock); |
/* Wait a frame or two */ |
mdelay (1); |
if (!readl (&ohci->regs->intrstatus) & OHCI_INTR_SF) |
mdelay (1); |
#ifdef CONFIG_PMAC_PBOOK |
if (_machine == _MACH_Pmac) |
disable_irq (hcd->pdev->irq); |
/* else, 2.4 assumes shared irqs -- don't disable */ |
#endif |
/* Enable remote wakeup */ |
writel (readl (&ohci->regs->intrenable) | OHCI_INTR_RD, |
&ohci->regs->intrenable); |
/* Suspend chip and let things settle down a bit */ |
spin_lock_irq (&ohci->lock); |
ohci->hc_control = OHCI_USB_SUSPEND; |
writel (ohci->hc_control, &ohci->regs->control); |
(void) readl (&ohci->regs->control); |
spin_unlock_irq (&ohci->lock); |
set_current_state (TASK_UNINTERRUPTIBLE); |
schedule_timeout (HZ/2); |
tmp = readl (&ohci->regs->control) | OHCI_CTRL_HCFS; |
switch (tmp) { |
case OHCI_USB_RESET: |
case OHCI_USB_RESUME: |
case OHCI_USB_OPER: |
ohci_err (ohci, "can't suspend; hcfs %d\n", tmp); |
break; |
case OHCI_USB_SUSPEND: |
ohci_dbg (ohci, "suspended\n"); |
break; |
} |
/* In some rare situations, Apple's OHCI have happily trashed |
* memory during sleep. We disable its bus master bit during |
* suspend |
*/ |
pci_read_config_word (hcd->pdev, PCI_COMMAND, &cmd); |
cmd &= ~PCI_COMMAND_MASTER; |
pci_write_config_word (hcd->pdev, PCI_COMMAND, cmd); |
#ifdef CONFIG_PMAC_PBOOK |
{ |
struct device_node *of_node; |
/* Disable USB PAD & cell clock */ |
of_node = pci_device_to_OF_node (hcd->pdev); |
if (of_node) |
pmac_call_feature(PMAC_FTR_USB_ENABLE, of_node, 0, 0); |
} |
#endif |
return 0; |
} |
static int ohci_pci_resume (struct usb_hcd *hcd) |
{ |
struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
int temp; |
int retval = 0; |
#ifdef CONFIG_PMAC_PBOOK |
{ |
struct device_node *of_node; |
/* Re-enable USB PAD & cell clock */ |
of_node = pci_device_to_OF_node (hcd->pdev); |
if (of_node) |
pmac_call_feature (PMAC_FTR_USB_ENABLE, of_node, 0, 1); |
} |
#endif |
/* did we suspend, or were we powered off? */ |
ohci->hc_control = readl (&ohci->regs->control); |
temp = ohci->hc_control & OHCI_CTRL_HCFS; |
#ifdef DEBUG |
/* the registers may look crazy here */ |
ohci_dump_status (ohci, 0, 0); |
#endif |
/* Re-enable bus mastering */ |
pci_set_master (ohci->hcd.pdev); |
switch (temp) { |
case OHCI_USB_RESET: // lost power |
restart: |
ohci_info (ohci, "USB restart\n"); |
retval = hc_restart (ohci); |
break; |
case OHCI_USB_SUSPEND: // host wakeup |
case OHCI_USB_RESUME: // remote wakeup |
ohci_info (ohci, "USB continue from %s wakeup\n", |
(temp == OHCI_USB_SUSPEND) |
? "host" : "remote"); |
/* we "should" only need RESUME if we're SUSPENDed ... */ |
ohci->hc_control = OHCI_USB_RESUME; |
writel (ohci->hc_control, &ohci->regs->control); |
(void) readl (&ohci->regs->control); |
/* Some controllers (lucent) need extra-long delays */ |
mdelay (35); /* no schedule here ! */ |
temp = readl (&ohci->regs->control); |
temp = ohci->hc_control & OHCI_CTRL_HCFS; |
if (temp != OHCI_USB_RESUME) { |
ohci_err (ohci, "controller won't resume\n"); |
/* maybe we can reset */ |
goto restart; |
} |
/* Then re-enable operations */ |
writel (OHCI_USB_OPER, &ohci->regs->control); |
(void) readl (&ohci->regs->control); |
mdelay (3); |
spin_lock_irq (&ohci->lock); |
ohci->hc_control = OHCI_CONTROL_INIT | OHCI_USB_OPER; |
if (!ohci->ed_rm_list) { |
if (ohci->ed_controltail) |
ohci->hc_control |= OHCI_CTRL_CLE; |
if (ohci->ed_bulktail) |
ohci->hc_control |= OHCI_CTRL_BLE; |
} |
hcd->state = USB_STATE_RUNNING; |
writel (ohci->hc_control, &ohci->regs->control); |
/* trigger a start-frame interrupt (why?) */ |
writel (OHCI_INTR_SF, &ohci->regs->intrstatus); |
writel (OHCI_INTR_SF, &ohci->regs->intrenable); |
writel (OHCI_INTR_WDH, &ohci->regs->intrdisable); |
(void) readl (&ohci->regs->intrdisable); |
spin_unlock_irq (&ohci->lock); |
#ifdef CONFIG_PMAC_PBOOK |
if (_machine == _MACH_Pmac) |
enable_irq (hcd->pdev->irq); |
#endif |
/* Check for a pending done list */ |
if (ohci->hcca->done_head) |
dl_done_list (ohci, dl_reverse_done_list (ohci), NULL); |
writel (OHCI_INTR_WDH, &ohci->regs->intrenable); |
/* assume there are TDs on the bulk and control lists */ |
writel (OHCI_BLF | OHCI_CLF, &ohci->regs->cmdstatus); |
break; |
default: |
ohci_warn (ohci, "odd PCI resume\n"); |
} |
return retval; |
} |
#endif /* CONFIG_PM */ |
/*-------------------------------------------------------------------------*/ |
static const struct hc_driver ohci_pci_hc_driver = { |
.description = hcd_name, |
/* |
* generic hardware linkage |
*/ |
.irq = ohci_irq, |
.flags = HCD_MEMORY | HCD_USB11, |
/* |
* basic lifecycle operations |
*/ |
.reset = ohci_pci_reset, |
.start = ohci_pci_start, |
#ifdef CONFIG_PM |
.suspend = ohci_pci_suspend, |
.resume = ohci_pci_resume, |
#endif |
.stop = ohci_stop, |
/* |
* memory lifecycle (except per-request) |
*/ |
.hcd_alloc = ohci_hcd_alloc, |
.hcd_free = ohci_hcd_free, |
/* |
* managing i/o requests and associated device resources |
*/ |
.urb_enqueue = ohci_urb_enqueue, |
.urb_dequeue = ohci_urb_dequeue, |
.endpoint_disable = ohci_endpoint_disable, |
/* |
* scheduling support |
*/ |
.get_frame_number = ohci_get_frame, |
/* |
* root hub support |
*/ |
.hub_status_data = ohci_hub_status_data, |
.hub_control = ohci_hub_control, |
}; |
/*-------------------------------------------------------------------------*/ |
static const struct pci_device_id pci_ids [] = { { |
/* handle any USB OHCI controller */ |
PCI_DEVICE_CLASS((PCI_CLASS_SERIAL_USB << 8) | 0x10, ~0), |
.driver_data = (unsigned long) &ohci_pci_hc_driver, |
}, { /* end: all zeroes */ } |
}; |
MODULE_DEVICE_TABLE (pci, pci_ids); |
/* pci driver glue; this is a "new style" PCI driver module */ |
static struct pci_driver ohci_pci_driver = { |
.name = (char *) hcd_name, |
.id_table = pci_ids, |
.probe = usb_hcd_pci_probe, |
.remove = usb_hcd_pci_remove, |
#ifdef CONFIG_PM |
.suspend = usb_hcd_pci_suspend, |
.resume = usb_hcd_pci_resume, |
#endif |
}; |
/*static*/ int __init ohci_hcd_pci_init (void) |
{ |
printk (KERN_DEBUG "%s: " DRIVER_INFO " (PCI)\n", hcd_name); |
if (usb_disabled()) |
return -ENODEV; |
printk (KERN_DEBUG "%s: block sizes: ed %Zd td %Zd\n", hcd_name, |
sizeof (struct ed), sizeof (struct td)); |
//*** printk (KERN_DEBUG "File: %s @Line:%d\n", __FILE__, __LINE__); |
return pci_module_init (&ohci_pci_driver); |
} |
module_init (ohci_hcd_pci_init); |
/*-------------------------------------------------------------------------*/ |
/*static*/ void /*__exit*/ ohci_hcd_pci_cleanup (void) |
{ |
pci_unregister_driver (&ohci_pci_driver); |
} |
module_exit (ohci_hcd_pci_cleanup); |
/shark/trunk/drivers/usb/host/ehci-sched.c |
---|
0,0 → 1,1123 |
/* |
* Copyright (c) 2001-2002 by David Brownell |
* |
* 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. |
*/ |
/* this file is part of ehci-hcd.c */ |
/*-------------------------------------------------------------------------*/ |
/* |
* EHCI scheduled transaction support: interrupt, iso, split iso |
* These are called "periodic" transactions in the EHCI spec. |
* |
* Note that for interrupt transfers, the QH/QTD manipulation is shared |
* with the "asynchronous" transaction support (control/bulk transfers). |
* The only real difference is in how interrupt transfers are scheduled. |
* We get some funky API restrictions from the current URB model, which |
* works notably better for reading transfers than for writing. (And |
* which accordingly needs to change before it'll work inside devices, |
* or with "USB On The Go" additions to USB 2.0 ...) |
*/ |
static int ehci_get_frame (struct usb_hcd *hcd); |
/*-------------------------------------------------------------------------*/ |
/* |
* periodic_next_shadow - return "next" pointer on shadow list |
* @periodic: host pointer to qh/itd/sitd |
* @tag: hardware tag for type of this record |
*/ |
static union ehci_shadow * |
periodic_next_shadow (union ehci_shadow *periodic, int tag) |
{ |
switch (tag) { |
case Q_TYPE_QH: |
return &periodic->qh->qh_next; |
case Q_TYPE_FSTN: |
return &periodic->fstn->fstn_next; |
case Q_TYPE_ITD: |
return &periodic->itd->itd_next; |
#ifdef have_split_iso |
case Q_TYPE_SITD: |
return &periodic->sitd->sitd_next; |
#endif /* have_split_iso */ |
} |
dbg ("BAD shadow %p tag %d", periodic->ptr, tag); |
// BUG (); |
return 0; |
} |
/* returns true after successful unlink */ |
/* caller must hold ehci->lock */ |
static int periodic_unlink (struct ehci_hcd *ehci, unsigned frame, void *ptr) |
{ |
union ehci_shadow *prev_p = &ehci->pshadow [frame]; |
u32 *hw_p = &ehci->periodic [frame]; |
union ehci_shadow here = *prev_p; |
union ehci_shadow *next_p; |
/* find predecessor of "ptr"; hw and shadow lists are in sync */ |
while (here.ptr && here.ptr != ptr) { |
prev_p = periodic_next_shadow (prev_p, Q_NEXT_TYPE (*hw_p)); |
hw_p = &here.qh->hw_next; |
here = *prev_p; |
} |
/* an interrupt entry (at list end) could have been shared */ |
if (!here.ptr) { |
dbg ("entry %p no longer on frame [%d]", ptr, frame); |
return 0; |
} |
// vdbg ("periodic unlink %p from frame %d", ptr, frame); |
/* update hardware list ... HC may still know the old structure, so |
* don't change hw_next until it'll have purged its cache |
*/ |
next_p = periodic_next_shadow (&here, Q_NEXT_TYPE (*hw_p)); |
*hw_p = here.qh->hw_next; |
/* unlink from shadow list; HCD won't see old structure again */ |
*prev_p = *next_p; |
next_p->ptr = 0; |
return 1; |
} |
/* how many of the uframe's 125 usecs are allocated? */ |
static unsigned short |
periodic_usecs (struct ehci_hcd *ehci, unsigned frame, unsigned uframe) |
{ |
u32 *hw_p = &ehci->periodic [frame]; |
union ehci_shadow *q = &ehci->pshadow [frame]; |
unsigned usecs = 0; |
while (q->ptr) { |
switch (Q_NEXT_TYPE (*hw_p)) { |
case Q_TYPE_QH: |
/* is it in the S-mask? */ |
if (q->qh->hw_info2 & cpu_to_le32 (1 << uframe)) |
usecs += q->qh->usecs; |
/* ... or C-mask? */ |
if (q->qh->hw_info2 & cpu_to_le32 (1 << (8 + uframe))) |
usecs += q->qh->c_usecs; |
q = &q->qh->qh_next; |
break; |
case Q_TYPE_FSTN: |
/* for "save place" FSTNs, count the relevant INTR |
* bandwidth from the previous frame |
*/ |
if (q->fstn->hw_prev != EHCI_LIST_END) { |
dbg ("not counting FSTN bandwidth yet ..."); |
} |
q = &q->fstn->fstn_next; |
break; |
case Q_TYPE_ITD: |
/* NOTE the "one uframe per itd" policy */ |
if (q->itd->hw_transaction [uframe] != 0) |
usecs += q->itd->usecs; |
q = &q->itd->itd_next; |
break; |
#ifdef have_split_iso |
case Q_TYPE_SITD: |
temp = q->sitd->hw_fullspeed_ep & |
__constant_cpu_to_le32 (1 << 31); |
// FIXME: this doesn't count data bytes right... |
/* is it in the S-mask? (count SPLIT, DATA) */ |
if (q->sitd->hw_uframe & cpu_to_le32 (1 << uframe)) { |
if (temp) |
usecs += HS_USECS (188); |
else |
usecs += HS_USECS (1); |
} |
/* ... C-mask? (count CSPLIT, DATA) */ |
if (q->sitd->hw_uframe & |
cpu_to_le32 (1 << (8 + uframe))) { |
if (temp) |
usecs += HS_USECS (0); |
else |
usecs += HS_USECS (188); |
} |
q = &q->sitd->sitd_next; |
break; |
#endif /* have_split_iso */ |
default: |
BUG (); |
} |
} |
#ifdef DEBUG |
if (usecs > 100) |
err ("overallocated uframe %d, periodic is %d usecs", |
frame * 8 + uframe, usecs); |
#endif |
return usecs; |
} |
/*-------------------------------------------------------------------------*/ |
static int enable_periodic (struct ehci_hcd *ehci) |
{ |
u32 cmd; |
int status; |
/* did clearing PSE did take effect yet? |
* takes effect only at frame boundaries... |
*/ |
status = handshake (&ehci->regs->status, STS_PSS, 0, 9 * 125); |
if (status != 0) { |
ehci->hcd.state = USB_STATE_HALT; |
return status; |
} |
cmd = readl (&ehci->regs->command) | CMD_PSE; |
writel (cmd, &ehci->regs->command); |
/* posted write ... PSS happens later */ |
ehci->hcd.state = USB_STATE_RUNNING; |
/* make sure ehci_work scans these */ |
ehci->next_uframe = readl (&ehci->regs->frame_index) |
% (ehci->periodic_size << 3); |
return 0; |
} |
static int disable_periodic (struct ehci_hcd *ehci) |
{ |
u32 cmd; |
int status; |
/* did setting PSE not take effect yet? |
* takes effect only at frame boundaries... |
*/ |
status = handshake (&ehci->regs->status, STS_PSS, STS_PSS, 9 * 125); |
if (status != 0) { |
ehci->hcd.state = USB_STATE_HALT; |
return status; |
} |
cmd = readl (&ehci->regs->command) & ~CMD_PSE; |
writel (cmd, &ehci->regs->command); |
/* posted write ... */ |
ehci->next_uframe = -1; |
return 0; |
} |
/*-------------------------------------------------------------------------*/ |
// FIXME microframe periods not yet handled |
static void intr_deschedule ( |
struct ehci_hcd *ehci, |
struct ehci_qh *qh, |
int wait |
) { |
int status; |
unsigned frame = qh->start; |
do { |
periodic_unlink (ehci, frame, qh); |
qh_put (ehci, qh); |
frame += qh->period; |
} while (frame < ehci->periodic_size); |
qh->qh_state = QH_STATE_UNLINK; |
qh->qh_next.ptr = 0; |
ehci->periodic_sched--; |
/* maybe turn off periodic schedule */ |
if (!ehci->periodic_sched) |
status = disable_periodic (ehci); |
else { |
status = 0; |
vdbg ("periodic schedule still enabled"); |
} |
/* |
* If the hc may be looking at this qh, then delay a uframe |
* (yeech!) to be sure it's done. |
* No other threads may be mucking with this qh. |
*/ |
if (((ehci_get_frame (&ehci->hcd) - frame) % qh->period) == 0) { |
if (wait) { |
udelay (125); |
qh->hw_next = EHCI_LIST_END; |
} else { |
/* we may not be IDLE yet, but if the qh is empty |
* the race is very short. then if qh also isn't |
* rescheduled soon, it won't matter. otherwise... |
*/ |
vdbg ("intr_deschedule..."); |
} |
} else |
qh->hw_next = EHCI_LIST_END; |
qh->qh_state = QH_STATE_IDLE; |
/* update per-qh bandwidth utilization (for usbfs) */ |
hcd_to_bus (&ehci->hcd)->bandwidth_allocated -= |
(qh->usecs + qh->c_usecs) / qh->period; |
dbg ("descheduled qh %p, period = %d frame = %d count = %d, urbs = %d", |
qh, qh->period, frame, |
atomic_read (&qh->refcount), ehci->periodic_sched); |
} |
static int check_period ( |
struct ehci_hcd *ehci, |
unsigned frame, |
unsigned uframe, |
unsigned period, |
unsigned usecs |
) { |
/* complete split running into next frame? |
* given FSTN support, we could sometimes check... |
*/ |
if (uframe >= 8) |
return 0; |
/* |
* 80% periodic == 100 usec/uframe available |
* convert "usecs we need" to "max already claimed" |
*/ |
usecs = 100 - usecs; |
do { |
int claimed; |
// FIXME delete when intr_submit handles non-empty queues |
// this gives us a one intr/frame limit (vs N/uframe) |
// ... and also lets us avoid tracking split transactions |
// that might collide at a given TT/hub. |
if (ehci->pshadow [frame].ptr) |
return 0; |
claimed = periodic_usecs (ehci, frame, uframe); |
if (claimed > usecs) |
return 0; |
// FIXME update to handle sub-frame periods |
} while ((frame += period) < ehci->periodic_size); |
// success! |
return 1; |
} |
static int check_intr_schedule ( |
struct ehci_hcd *ehci, |
unsigned frame, |
unsigned uframe, |
const struct ehci_qh *qh, |
u32 *c_maskp |
) |
{ |
int retval = -ENOSPC; |
if (!check_period (ehci, frame, uframe, qh->period, qh->usecs)) |
goto done; |
if (!qh->c_usecs) { |
retval = 0; |
*c_maskp = cpu_to_le32 (0); |
goto done; |
} |
/* This is a split transaction; check the bandwidth available for |
* the completion too. Check both worst and best case gaps: worst |
* case is SPLIT near uframe end, and CSPLIT near start ... best is |
* vice versa. Difference can be almost two uframe times, but we |
* reserve unnecessary bandwidth (waste it) this way. (Actually |
* even better cases exist, like immediate device NAK.) |
* |
* FIXME don't even bother unless we know this TT is idle in that |
* range of uframes ... for now, check_period() allows only one |
* interrupt transfer per frame, so needn't check "TT busy" status |
* when scheduling a split (QH, SITD, or FSTN). |
* |
* FIXME ehci 0.96 and above can use FSTNs |
*/ |
if (!check_period (ehci, frame, uframe + qh->gap_uf + 1, |
qh->period, qh->c_usecs)) |
goto done; |
if (!check_period (ehci, frame, uframe + qh->gap_uf, |
qh->period, qh->c_usecs)) |
goto done; |
*c_maskp = cpu_to_le32 (0x03 << (8 + uframe + qh->gap_uf)); |
retval = 0; |
done: |
return retval; |
} |
static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh) |
{ |
int status; |
unsigned uframe; |
u32 c_mask; |
unsigned frame; /* 0..(qh->period - 1), or NO_FRAME */ |
qh->hw_next = EHCI_LIST_END; |
frame = qh->start; |
/* reuse the previous schedule slots, if we can */ |
if (frame < qh->period) { |
uframe = ffs (le32_to_cpup (&qh->hw_info2) & 0x00ff); |
status = check_intr_schedule (ehci, frame, --uframe, |
qh, &c_mask); |
} else { |
uframe = 0; |
c_mask = 0; |
status = -ENOSPC; |
} |
/* else scan the schedule to find a group of slots such that all |
* uframes have enough periodic bandwidth available. |
*/ |
if (status) { |
frame = qh->period - 1; |
do { |
for (uframe = 0; uframe < 8; uframe++) { |
status = check_intr_schedule (ehci, |
frame, uframe, qh, |
&c_mask); |
if (status == 0) |
break; |
} |
} while (status && frame--); |
if (status) |
goto done; |
qh->start = frame; |
/* reset S-frame and (maybe) C-frame masks */ |
qh->hw_info2 &= ~0xffff; |
qh->hw_info2 |= cpu_to_le32 (1 << uframe) | c_mask; |
} else |
dbg ("reused previous qh %p schedule", qh); |
/* stuff into the periodic schedule */ |
qh->qh_state = QH_STATE_LINKED; |
dbg ("scheduled qh %p usecs %d/%d period %d.0 starting %d.%d (gap %d)", |
qh, qh->usecs, qh->c_usecs, |
qh->period, frame, uframe, qh->gap_uf); |
do { |
if (unlikely (ehci->pshadow [frame].ptr != 0)) { |
// FIXME -- just link toward the end, before any qh with a shorter period, |
// AND accommodate it already having been linked here (after some other qh) |
// AS WELL AS updating the schedule checking logic |
BUG (); |
} else { |
ehci->pshadow [frame].qh = qh_get (qh); |
ehci->periodic [frame] = |
QH_NEXT (qh->qh_dma); |
} |
wmb (); |
frame += qh->period; |
} while (frame < ehci->periodic_size); |
/* update per-qh bandwidth for usbfs */ |
hcd_to_bus (&ehci->hcd)->bandwidth_allocated += |
(qh->usecs + qh->c_usecs) / qh->period; |
/* maybe enable periodic schedule processing */ |
if (!ehci->periodic_sched++) |
status = enable_periodic (ehci); |
done: |
return status; |
} |
static int intr_submit ( |
struct ehci_hcd *ehci, |
struct urb *urb, |
struct list_head *qtd_list, |
int mem_flags |
) { |
unsigned epnum; |
unsigned long flags; |
struct ehci_qh *qh; |
struct hcd_dev *dev; |
int is_input; |
int status = 0; |
struct list_head empty; |
/* get endpoint and transfer/schedule data */ |
epnum = usb_pipeendpoint (urb->pipe); |
is_input = usb_pipein (urb->pipe); |
if (is_input) |
epnum |= 0x10; |
spin_lock_irqsave (&ehci->lock, flags); |
dev = (struct hcd_dev *)urb->dev->hcpriv; |
/* get qh and force any scheduling errors */ |
INIT_LIST_HEAD (&empty); |
qh = qh_append_tds (ehci, urb, &empty, epnum, &dev->ep [epnum]); |
if (qh == 0) { |
status = -ENOMEM; |
goto done; |
} |
if (qh->qh_state == QH_STATE_IDLE) { |
if ((status = qh_schedule (ehci, qh)) != 0) |
goto done; |
} |
/* then queue the urb's tds to the qh */ |
qh = qh_append_tds (ehci, urb, qtd_list, epnum, &dev->ep [epnum]); |
BUG_ON (qh == 0); |
/* ... update usbfs periodic stats */ |
hcd_to_bus (&ehci->hcd)->bandwidth_int_reqs++; |
done: |
spin_unlock_irqrestore (&ehci->lock, flags); |
if (status) |
qtd_list_free (ehci, urb, qtd_list); |
return status; |
} |
static unsigned |
intr_complete ( |
struct ehci_hcd *ehci, |
unsigned frame, |
struct ehci_qh *qh, |
struct pt_regs *regs |
) { |
unsigned count; |
/* nothing to report? */ |
if (likely ((qh->hw_token & __constant_cpu_to_le32 (QTD_STS_ACTIVE)) |
!= 0)) |
return 0; |
if (unlikely (list_empty (&qh->qtd_list))) { |
dbg ("intr qh %p no TDs?", qh); |
return 0; |
} |
/* handle any completions */ |
count = qh_completions (ehci, qh, regs); |
if (unlikely (list_empty (&qh->qtd_list))) |
intr_deschedule (ehci, qh, 0); |
return count; |
} |
/*-------------------------------------------------------------------------*/ |
static void |
itd_free_list (struct ehci_hcd *ehci, struct urb *urb) |
{ |
struct ehci_itd *first_itd = urb->hcpriv; |
while (!list_empty (&first_itd->itd_list)) { |
struct ehci_itd *itd; |
itd = list_entry ( |
first_itd->itd_list.next, |
struct ehci_itd, itd_list); |
list_del (&itd->itd_list); |
pci_pool_free (ehci->itd_pool, itd, itd->itd_dma); |
} |
pci_pool_free (ehci->itd_pool, first_itd, first_itd->itd_dma); |
urb->hcpriv = 0; |
} |
static int |
itd_fill ( |
struct ehci_hcd *ehci, |
struct ehci_itd *itd, |
struct urb *urb, |
unsigned index, // urb->iso_frame_desc [index] |
dma_addr_t dma // mapped transfer buffer |
) { |
u64 temp; |
u32 buf1; |
unsigned i, epnum, maxp, multi; |
unsigned length; |
int is_input; |
itd->hw_next = EHCI_LIST_END; |
itd->urb = urb; |
itd->index = index; |
/* tell itd about its transfer buffer, max 2 pages */ |
length = urb->iso_frame_desc [index].length; |
dma += urb->iso_frame_desc [index].offset; |
temp = dma & ~0x0fff; |
for (i = 0; i < 2; i++) { |
itd->hw_bufp [i] = cpu_to_le32 ((u32) temp); |
itd->hw_bufp_hi [i] = cpu_to_le32 ((u32)(temp >> 32)); |
temp += 0x1000; |
} |
itd->buf_dma = dma; |
/* |
* this might be a "high bandwidth" highspeed endpoint, |
* as encoded in the ep descriptor's maxpacket field |
*/ |
epnum = usb_pipeendpoint (urb->pipe); |
is_input = usb_pipein (urb->pipe); |
if (is_input) { |
maxp = urb->dev->epmaxpacketin [epnum]; |
buf1 = (1 << 11); |
} else { |
maxp = urb->dev->epmaxpacketout [epnum]; |
buf1 = 0; |
} |
buf1 |= (maxp & 0x03ff); |
multi = 1; |
multi += (maxp >> 11) & 0x03; |
maxp &= 0x03ff; |
maxp *= multi; |
/* transfer can't fit in any uframe? */ |
if (length < 0 || maxp < length) { |
dbg ("BAD iso packet: %d bytes, max %d, urb %p [%d] (of %d)", |
length, maxp, urb, index, |
urb->iso_frame_desc [index].length); |
return -ENOSPC; |
} |
itd->usecs = usb_calc_bus_time (USB_SPEED_HIGH, is_input, 1, length); |
/* "plus" info in low order bits of buffer pointers */ |
itd->hw_bufp [0] |= cpu_to_le32 ((epnum << 8) | urb->dev->devnum); |
itd->hw_bufp [1] |= cpu_to_le32 (buf1); |
itd->hw_bufp [2] |= cpu_to_le32 (multi); |
/* figure hw_transaction[] value (it's scheduled later) */ |
itd->transaction = EHCI_ISOC_ACTIVE; |
itd->transaction |= dma & 0x0fff; /* offset; buffer=0 */ |
if ((index + 1) == urb->number_of_packets) |
itd->transaction |= EHCI_ITD_IOC; /* end-of-urb irq */ |
itd->transaction |= length << 16; |
cpu_to_le32s (&itd->transaction); |
return 0; |
} |
static int |
itd_urb_transaction ( |
struct ehci_hcd *ehci, |
struct urb *urb, |
int mem_flags |
) { |
int frame_index; |
struct ehci_itd *first_itd, *itd; |
int status; |
dma_addr_t itd_dma; |
/* allocate/init ITDs */ |
for (frame_index = 0, first_itd = 0; |
frame_index < urb->number_of_packets; |
frame_index++) { |
itd = pci_pool_alloc (ehci->itd_pool, mem_flags, &itd_dma); |
if (!itd) { |
status = -ENOMEM; |
goto fail; |
} |
memset (itd, 0, sizeof *itd); |
itd->itd_dma = itd_dma; |
status = itd_fill (ehci, itd, urb, frame_index, |
urb->transfer_dma); |
if (status != 0) |
goto fail; |
if (first_itd) |
list_add_tail (&itd->itd_list, |
&first_itd->itd_list); |
else { |
INIT_LIST_HEAD (&itd->itd_list); |
urb->hcpriv = first_itd = itd; |
} |
} |
urb->error_count = 0; |
return 0; |
fail: |
if (urb->hcpriv) |
itd_free_list (ehci, urb); |
return status; |
} |
/*-------------------------------------------------------------------------*/ |
static inline void |
itd_link (struct ehci_hcd *ehci, unsigned frame, struct ehci_itd *itd) |
{ |
/* always prepend ITD/SITD ... only QH tree is order-sensitive */ |
itd->itd_next = ehci->pshadow [frame]; |
itd->hw_next = ehci->periodic [frame]; |
ehci->pshadow [frame].itd = itd; |
ehci->periodic [frame] = cpu_to_le32 (itd->itd_dma) | Q_TYPE_ITD; |
} |
/* |
* return zero on success, else -errno |
* - start holds first uframe to start scheduling into |
* - max is the first uframe it's NOT (!) OK to start scheduling into |
* math to be done modulo "mod" (ehci->periodic_size << 3) |
*/ |
static int get_iso_range ( |
struct ehci_hcd *ehci, |
struct urb *urb, |
unsigned *start, |
unsigned *max, |
unsigned mod |
) { |
struct list_head *lh; |
struct hcd_dev *dev = urb->dev->hcpriv; |
int last = -1; |
unsigned now, span, end; |
span = urb->interval * urb->number_of_packets; |
/* first see if we know when the next transfer SHOULD happen */ |
list_for_each (lh, &dev->urb_list) { |
struct urb *u; |
struct ehci_itd *itd; |
unsigned s; |
u = list_entry (lh, struct urb, urb_list); |
if (u == urb || u->pipe != urb->pipe) |
continue; |
if (u->interval != urb->interval) { /* must not change! */ |
dbg ("urb %p interval %d ... != %p interval %d", |
u, u->interval, urb, urb->interval); |
return -EINVAL; |
} |
/* URB for this endpoint... covers through when? */ |
itd = urb->hcpriv; |
s = itd->uframe + u->interval * u->number_of_packets; |
if (last < 0) |
last = s; |
else { |
/* |
* So far we can only queue two ISO URBs... |
* |
* FIXME do interval math, figure out whether |
* this URB is "before" or not ... also, handle |
* the case where the URB might have completed, |
* but hasn't yet been processed. |
*/ |
dbg ("NYET: queue >2 URBs per ISO endpoint"); |
return -EDOM; |
} |
} |
/* calculate the legal range [start,max) */ |
now = readl (&ehci->regs->frame_index) + 1; /* next uframe */ |
if (!ehci->periodic_sched) |
now += 8; /* startup delay */ |
now %= mod; |
end = now + mod; |
if (last < 0) { |
*start = now + ehci->i_thresh + /* paranoia */ 1; |
*max = end - span; |
if (*max < *start + 1) |
*max = *start + 1; |
} else { |
*start = last % mod; |
*max = (last + 1) % mod; |
} |
/* explicit start frame? */ |
if (!(urb->transfer_flags & URB_ISO_ASAP)) { |
unsigned temp; |
/* sanity check: must be in range */ |
urb->start_frame %= ehci->periodic_size; |
temp = urb->start_frame << 3; |
if (temp < *start) |
temp += mod; |
if (temp > *max) |
return -EDOM; |
/* use that explicit start frame */ |
*start = urb->start_frame << 3; |
temp += 8; |
if (temp < *max) |
*max = temp; |
} |
// FIXME minimize wraparound to "now" ... insist max+span |
// (and start+span) remains a few frames short of "end" |
*max %= ehci->periodic_size; |
if ((*start + span) < end) |
return 0; |
return -EFBIG; |
} |
static int |
itd_schedule (struct ehci_hcd *ehci, struct urb *urb) |
{ |
unsigned start, max, i; |
int status; |
unsigned mod = ehci->periodic_size << 3; |
for (i = 0; i < urb->number_of_packets; i++) { |
urb->iso_frame_desc [i].status = -EINPROGRESS; |
urb->iso_frame_desc [i].actual_length = 0; |
} |
if ((status = get_iso_range (ehci, urb, &start, &max, mod)) != 0) |
return status; |
do { |
unsigned uframe; |
unsigned usecs; |
struct ehci_itd *itd; |
/* check schedule: enough space? */ |
itd = urb->hcpriv; |
uframe = start; |
for (i = 0, uframe = start; |
i < urb->number_of_packets; |
i++, uframe += urb->interval) { |
uframe %= mod; |
/* can't commit more than 80% periodic == 100 usec */ |
if (periodic_usecs (ehci, uframe >> 3, uframe & 0x7) |
> (100 - itd->usecs)) { |
itd = 0; |
break; |
} |
itd = list_entry (itd->itd_list.next, |
struct ehci_itd, itd_list); |
} |
if (!itd) |
continue; |
/* that's where we'll schedule this! */ |
itd = urb->hcpriv; |
urb->start_frame = start >> 3; |
vdbg ("ISO urb %p (%d packets period %d) starting %d.%d", |
urb, urb->number_of_packets, urb->interval, |
urb->start_frame, start & 0x7); |
for (i = 0, uframe = start, usecs = 0; |
i < urb->number_of_packets; |
i++, uframe += urb->interval) { |
uframe %= mod; |
itd->uframe = uframe; |
itd->hw_transaction [uframe & 0x07] = itd->transaction; |
itd_link (ehci, (uframe >> 3) % ehci->periodic_size, |
itd); |
wmb (); |
usecs += itd->usecs; |
itd = list_entry (itd->itd_list.next, |
struct ehci_itd, itd_list); |
} |
/* update bandwidth utilization records (for usbfs) |
* |
* FIXME This claims each URB queued to an endpoint, as if |
* transfers were concurrent, not sequential. So bandwidth |
* typically gets double-billed ... comes from tying it to |
* URBs rather than endpoints in the schedule. Luckily we |
* don't use this usbfs data for serious decision making. |
*/ |
usecs /= urb->number_of_packets; |
usecs /= urb->interval; |
usecs >>= 3; |
if (usecs < 1) |
usecs = 1; |
usb_claim_bandwidth (urb->dev, urb, usecs, 1); |
/* maybe enable periodic schedule processing */ |
if (!ehci->periodic_sched++) { |
if ((status = enable_periodic (ehci)) != 0) { |
// FIXME deschedule right away |
err ("itd_schedule, enable = %d", status); |
} |
} |
return 0; |
} while ((start = ++start % mod) != max); |
/* no room in the schedule */ |
dbg ("urb %p, CAN'T SCHEDULE", urb); |
return -ENOSPC; |
} |
/*-------------------------------------------------------------------------*/ |
#define ISO_ERRS (EHCI_ISOC_BUF_ERR | EHCI_ISOC_BABBLE | EHCI_ISOC_XACTERR) |
static unsigned |
itd_complete ( |
struct ehci_hcd *ehci, |
struct ehci_itd *itd, |
unsigned uframe, |
struct pt_regs *regs |
) { |
struct urb *urb = itd->urb; |
struct usb_iso_packet_descriptor *desc; |
u32 t; |
/* update status for this uframe's transfers */ |
desc = &urb->iso_frame_desc [itd->index]; |
t = itd->hw_transaction [uframe]; |
itd->hw_transaction [uframe] = 0; |
if (t & EHCI_ISOC_ACTIVE) |
desc->status = -EXDEV; |
else if (t & ISO_ERRS) { |
urb->error_count++; |
if (t & EHCI_ISOC_BUF_ERR) |
desc->status = usb_pipein (urb->pipe) |
? -ENOSR /* couldn't read */ |
: -ECOMM; /* couldn't write */ |
else if (t & EHCI_ISOC_BABBLE) |
desc->status = -EOVERFLOW; |
else /* (t & EHCI_ISOC_XACTERR) */ |
desc->status = -EPROTO; |
/* HC need not update length with this error */ |
if (!(t & EHCI_ISOC_BABBLE)) |
desc->actual_length += EHCI_ITD_LENGTH (t); |
} else { |
desc->status = 0; |
desc->actual_length += EHCI_ITD_LENGTH (t); |
} |
vdbg ("itd %p urb %p packet %d/%d trans %x status %d len %d", |
itd, urb, itd->index + 1, urb->number_of_packets, |
t, desc->status, desc->actual_length); |
/* handle completion now? */ |
if ((itd->index + 1) != urb->number_of_packets) |
return 0; |
/* |
* Always give the urb back to the driver ... expect it to submit |
* a new urb (or resubmit this), and to have another already queued |
* when un-interrupted transfers are needed. |
* |
* NOTE that for now we don't accelerate ISO unlinks; they just |
* happen according to the current schedule. Means a delay of |
* up to about a second (max). |
*/ |
itd_free_list (ehci, urb); |
if (urb->status == -EINPROGRESS) |
urb->status = 0; |
/* complete() can reenter this HCD */ |
spin_unlock (&ehci->lock); |
usb_hcd_giveback_urb (&ehci->hcd, urb, regs); |
spin_lock (&ehci->lock); |
/* defer stopping schedule; completion can submit */ |
ehci->periodic_sched--; |
if (!ehci->periodic_sched) |
(void) disable_periodic (ehci); |
return 1; |
} |
/*-------------------------------------------------------------------------*/ |
static int itd_submit (struct ehci_hcd *ehci, struct urb *urb, int mem_flags) |
{ |
int status; |
unsigned long flags; |
dbg ("itd_submit urb %p", urb); |
/* allocate ITDs w/o locking anything */ |
status = itd_urb_transaction (ehci, urb, mem_flags); |
if (status < 0) |
return status; |
/* schedule ... need to lock */ |
spin_lock_irqsave (&ehci->lock, flags); |
status = itd_schedule (ehci, urb); |
spin_unlock_irqrestore (&ehci->lock, flags); |
if (status < 0) |
itd_free_list (ehci, urb); |
return status; |
} |
#ifdef have_split_iso |
/*-------------------------------------------------------------------------*/ |
/* |
* "Split ISO TDs" ... used for USB 1.1 devices going through |
* the TTs in USB 2.0 hubs. |
* |
* FIXME not yet implemented |
*/ |
#endif /* have_split_iso */ |
/*-------------------------------------------------------------------------*/ |
static void |
scan_periodic (struct ehci_hcd *ehci, struct pt_regs *regs) |
{ |
unsigned frame, clock, now_uframe, mod; |
unsigned count = 0; |
mod = ehci->periodic_size << 3; |
/* |
* When running, scan from last scan point up to "now" |
* else clean up by scanning everything that's left. |
* Touches as few pages as possible: cache-friendly. |
* Don't scan ISO entries more than once, though. |
*/ |
frame = ehci->next_uframe >> 3; |
if (HCD_IS_RUNNING (ehci->hcd.state)) |
now_uframe = readl (&ehci->regs->frame_index); |
else |
now_uframe = (frame << 3) - 1; |
now_uframe %= mod; |
clock = now_uframe >> 3; |
for (;;) { |
union ehci_shadow q, *q_p; |
u32 type, *hw_p; |
unsigned uframes; |
restart: |
/* scan schedule to _before_ current frame index */ |
if (frame == clock) |
uframes = now_uframe & 0x07; |
else |
uframes = 8; |
q_p = &ehci->pshadow [frame]; |
hw_p = &ehci->periodic [frame]; |
q.ptr = q_p->ptr; |
type = Q_NEXT_TYPE (*hw_p); |
/* scan each element in frame's queue for completions */ |
while (q.ptr != 0) { |
int last; |
unsigned uf; |
union ehci_shadow temp; |
switch (type) { |
case Q_TYPE_QH: |
last = (q.qh->hw_next == EHCI_LIST_END); |
temp = q.qh->qh_next; |
type = Q_NEXT_TYPE (q.qh->hw_next); |
count += intr_complete (ehci, frame, |
qh_get (q.qh), regs); |
qh_put (ehci, q.qh); |
q = temp; |
break; |
case Q_TYPE_FSTN: |
last = (q.fstn->hw_next == EHCI_LIST_END); |
/* for "save place" FSTNs, look at QH entries |
* in the previous frame for completions. |
*/ |
if (q.fstn->hw_prev != EHCI_LIST_END) { |
dbg ("ignoring completions from FSTNs"); |
} |
type = Q_NEXT_TYPE (q.fstn->hw_next); |
q = q.fstn->fstn_next; |
break; |
case Q_TYPE_ITD: |
last = (q.itd->hw_next == EHCI_LIST_END); |
/* Unlink each (S)ITD we see, since the ISO |
* URB model forces constant rescheduling. |
* That complicates sharing uframes in ITDs, |
* and means we need to skip uframes the HC |
* hasn't yet processed. |
*/ |
for (uf = 0; uf < uframes; uf++) { |
if (q.itd->hw_transaction [uf] != 0) { |
temp = q; |
*q_p = q.itd->itd_next; |
*hw_p = q.itd->hw_next; |
type = Q_NEXT_TYPE (*hw_p); |
/* might free q.itd ... */ |
count += itd_complete (ehci, |
temp.itd, uf, regs); |
break; |
} |
} |
/* we might skip this ITD's uframe ... */ |
if (uf == uframes) { |
q_p = &q.itd->itd_next; |
hw_p = &q.itd->hw_next; |
type = Q_NEXT_TYPE (q.itd->hw_next); |
} |
q = *q_p; |
break; |
#ifdef have_split_iso |
case Q_TYPE_SITD: |
last = (q.sitd->hw_next == EHCI_LIST_END); |
sitd_complete (ehci, q.sitd); |
type = Q_NEXT_TYPE (q.sitd->hw_next); |
// FIXME unlink SITD after split completes |
q = q.sitd->sitd_next; |
break; |
#endif /* have_split_iso */ |
default: |
dbg ("corrupt type %d frame %d shadow %p", |
type, frame, q.ptr); |
// BUG (); |
last = 1; |
q.ptr = 0; |
} |
/* did completion remove an interior q entry? */ |
if (unlikely (q.ptr == 0 && !last)) |
goto restart; |
} |
/* stop when we catch up to the HC */ |
// FIXME: this assumes we won't get lapped when |
// latencies climb; that should be rare, but... |
// detect it, and just go all the way around. |
// FLR might help detect this case, so long as latencies |
// don't exceed periodic_size msec (default 1.024 sec). |
// FIXME: likewise assumes HC doesn't halt mid-scan |
if (frame == clock) { |
unsigned now; |
if (!HCD_IS_RUNNING (ehci->hcd.state)) |
break; |
ehci->next_uframe = now_uframe; |
now = readl (&ehci->regs->frame_index) % mod; |
if (now_uframe == now) |
break; |
/* rescan the rest of this frame, then ... */ |
now_uframe = now; |
clock = now_uframe >> 3; |
} else |
frame = (frame + 1) % ehci->periodic_size; |
} |
} |
/shark/trunk/drivers/usb/makefile |
---|
0,0 → 1,34 |
# PCI support from linux 2.6.0 |
#_I386_SEMAPHORE_H |
ifndef BASE |
BASE=../.. |
endif |
include $(BASE)/config/config.mk |
LIBRARY = usb |
OBJS_PATH = $(BASE)/demos/usb |
OBJS = core/usb.o core/usb-debug.o core/hub.o core/hcd.o core/urb.o core/message.o \ |
core/config.o core/file.o core/buffer.o core/driverfs.o core/hcd-pci.o\ |
host/ohci-hcd.o host/ehci-hcd.o host/uhci-hcd.o \ |
shark_glue/shark_usb.o\ |
input/usbmouse.o input/usbkbd.o \ |
media/pwc-if.o media/pwc-ctrl.o media/pwc-misc.o media/pwc-uncompress.o \ |
input/hid-core.o input/hid-input.o serial/bus.o serial/generic.o \ |
serial/usb-serial.o serial/pl2303.o serial/tty_io.o |
OTHERINCL += -I$(BASE)/drivers/linuxc26/include -I./core -I./host |
# |
# add -DCONFIG_USB_DEBUG to enable USB core debug messages |
# -DCONFIG_USB_SERIAL_DEBUG to enable USB/serial converters debug messages |
# |
C_OPT += -D__KERNEL__ -DCONFIG_USB_HIDINPUT -DCONFIG_USB_SERIAL_PL2303 -DCONFIG_USB_DEBUG |
include $(BASE)/config/lib.mk |
clean:: |
rm -f $(OBJS) |