Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
This commit is contained in:
commit
d0f7acf9eb
275 changed files with 51417 additions and 0 deletions
305
kernel/drivers/usb/hid/usb_hid.c
Normal file
305
kernel/drivers/usb/hid/usb_hid.c
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
// USB HID, boot protocol: keyboards and mice.
|
||||
//
|
||||
// Boot protocol is the reason a USB keyboard works in a BIOS setup screen
|
||||
// before any operating system exists. A HID device normally describes its
|
||||
// reports with a report descriptor -- a small stack language that has to be
|
||||
// parsed to know which bit means which key -- but a device claiming the boot
|
||||
// subclass also promises to support one fixed layout on request. Eight bytes
|
||||
// for a keyboard, three for a mouse, defined in the HID specification and
|
||||
// identical on every device.
|
||||
//
|
||||
// That is worth taking. A full report-descriptor parser is a real piece of
|
||||
// work and buys nothing for a keyboard; it is what a game controller or a
|
||||
// touchscreen will need, and it can arrive with them.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "dma.h"
|
||||
#include "usb.h"
|
||||
#include "input.h"
|
||||
|
||||
#define NHID 4
|
||||
|
||||
struct hid {
|
||||
int in_use;
|
||||
int proto; // USB_HID_PROTO_KEYBOARD or _MOUSE
|
||||
uchar prev[8]; // the previous report, for edge detection
|
||||
};
|
||||
|
||||
static struct hid hids[NHID];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Keyboard
|
||||
//
|
||||
// The report is: modifiers, a reserved byte, then up to six usage IDs of keys
|
||||
// currently held. It is a *state*, not an event -- a key that stays down
|
||||
// appears in every report until it is released, so a character is emitted
|
||||
// only for usages that were not in the previous report. Sending one per
|
||||
// report instead would repeat every held key a few hundred times a second.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define HID_MOD_CTRL 0x11 // left | right
|
||||
#define HID_MOD_SHIFT 0x22
|
||||
#define HID_MOD_ALT 0x44
|
||||
|
||||
// Usage IDs 0x00-0x38 of the Keyboard/Keypad page. 0 means "nothing to type".
|
||||
static const uchar hid_unshifted[0x39] = {
|
||||
0, 0, 0, 0,
|
||||
'a','b','c','d','e','f','g','h','i','j','k','l','m',
|
||||
'n','o','p','q','r','s','t','u','v','w','x','y','z',
|
||||
'1','2','3','4','5','6','7','8','9','0',
|
||||
'\n', 0x1B, '\b', '\t', ' ',
|
||||
'-','=','[',']','\\','#',';','\'','`',',','.','/',
|
||||
};
|
||||
|
||||
static const uchar hid_shifted[0x39] = {
|
||||
0, 0, 0, 0,
|
||||
'A','B','C','D','E','F','G','H','I','J','K','L','M',
|
||||
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
|
||||
'!','@','#','$','%','^','&','*','(',')',
|
||||
'\n', 0x1B, '\b', '\t', ' ',
|
||||
'_','+','{','}','|','~',':','"','~','<','>','?',
|
||||
};
|
||||
|
||||
// Characters decoded from a report, waiting to be handed to the console.
|
||||
//
|
||||
// The console pulls input through a getc function rather than being pushed
|
||||
// to (consoleintr takes one), which is exactly the shape the PS/2 driver
|
||||
// needs and exactly the wrong shape here -- a USB report arrives complete,
|
||||
// with up to six keys in it. So the report is decoded into this queue and
|
||||
// the console then drains it through the same path as any other keyboard,
|
||||
// which means the entire line discipline is reused rather than reimplemented.
|
||||
#define KQSIZE 64
|
||||
static struct {
|
||||
uchar buf[KQSIZE];
|
||||
uint r, w;
|
||||
} kq;
|
||||
|
||||
static void
|
||||
kq_put(int c)
|
||||
{
|
||||
if(kq.w - kq.r < KQSIZE)
|
||||
kq.buf[kq.w++ % KQSIZE] = c;
|
||||
}
|
||||
|
||||
static int
|
||||
usb_kbd_getc(void)
|
||||
{
|
||||
if(kq.r == kq.w)
|
||||
return -1;
|
||||
return kq.buf[kq.r++ % KQSIZE];
|
||||
}
|
||||
|
||||
// Was this usage in that report?
|
||||
static int
|
||||
was_down(const uchar *prev, uchar usage)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 2; i < 8; i++)
|
||||
if(prev[i] == usage)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Turn the difference between two reports into press and release events for
|
||||
// /dev/input/event0.
|
||||
//
|
||||
// The console path below cannot do this job: it emits a character per new key
|
||||
// and nothing at all for a release, because a character stream has no way to
|
||||
// say "released". A program that wants to know a key is being *held* -- which
|
||||
// is every game and every compositor -- needs exactly the events this makes.
|
||||
static void
|
||||
keyboard_events(const uchar *prev, const uchar *rep)
|
||||
{
|
||||
int i, bit, n = 0;
|
||||
|
||||
for(bit = 0; bit < 8; bit++){
|
||||
int now = (rep[0] >> bit) & 1;
|
||||
int then = (prev[0] >> bit) & 1;
|
||||
if(now != then){
|
||||
input_report(INPUT_KBD, EV_KEY, input_hid_keycode(0xE0 + bit), now);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 2; i < 8; i++){
|
||||
uchar u = rep[i];
|
||||
if(u && !was_down(prev, u)){
|
||||
int k = input_hid_keycode(u);
|
||||
if(k){ input_report(INPUT_KBD, EV_KEY, k, 1); n++; }
|
||||
}
|
||||
}
|
||||
for(i = 2; i < 8; i++){
|
||||
uchar u = prev[i];
|
||||
if(u && !was_down(rep, u)){
|
||||
int k = input_hid_keycode(u);
|
||||
if(k){ input_report(INPUT_KBD, EV_KEY, k, 0); n++; }
|
||||
}
|
||||
}
|
||||
|
||||
if(n)
|
||||
input_sync(INPUT_KBD);
|
||||
}
|
||||
|
||||
static void
|
||||
keyboard_report(struct hid *h, const uchar *rep, int len)
|
||||
{
|
||||
uchar mods = rep[0];
|
||||
int i, c;
|
||||
|
||||
if(len < 8)
|
||||
return;
|
||||
|
||||
keyboard_events(h->prev, rep);
|
||||
|
||||
// Grabbed: the events above still went to /dev/input/event0, but nothing
|
||||
// reaches the console. Note prev[] is updated before returning, or the next
|
||||
// report would look like every held key had just been pressed again.
|
||||
if(input_is_grabbed(INPUT_KBD)){
|
||||
memmove(h->prev, rep, 8);
|
||||
return;
|
||||
}
|
||||
|
||||
for(i = 2; i < 8; i++){
|
||||
uchar usage = rep[i];
|
||||
|
||||
if(usage == 0 || usage >= 0x39)
|
||||
continue;
|
||||
if(was_down(h->prev, usage))
|
||||
continue; // still held from last time, not a new press
|
||||
|
||||
c = (mods & HID_MOD_SHIFT) ? hid_shifted[usage] : hid_unshifted[usage];
|
||||
if(c == 0)
|
||||
continue;
|
||||
|
||||
// Ctrl folds a letter down into its control character, which is how ^C
|
||||
// and ^D reach the line discipline at all.
|
||||
if(mods & HID_MOD_CTRL){
|
||||
if(c >= 'a' && c <= 'z')
|
||||
c = c - 'a' + 1;
|
||||
else if(c >= 'A' && c <= 'Z')
|
||||
c = c - 'A' + 1;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
kq_put(c);
|
||||
}
|
||||
|
||||
memmove(h->prev, rep, 8);
|
||||
consoleintr(usb_kbd_getc);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mouse
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
mouse_report(struct hid *h, const uchar *rep, int len)
|
||||
{
|
||||
int dx, dy;
|
||||
|
||||
(void)h;
|
||||
if(len < 3)
|
||||
return;
|
||||
|
||||
// The deltas are signed bytes. Reading them as unsigned is the classic way
|
||||
// to get a pointer that only travels right and down.
|
||||
dx = (signed char)rep[1];
|
||||
dy = (signed char)rep[2];
|
||||
|
||||
// HID's Y axis points down, and so does the screen's. The PS/2 protocol's
|
||||
// points up, which is why mouseintr subtracts where this adds.
|
||||
mouse_inject(dx, dy, rep[0] & 0x07);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
hid_complete(struct usb_dev *dev, void *buf, int len)
|
||||
{
|
||||
struct hid *h = dev->drvdata;
|
||||
|
||||
if(h == 0)
|
||||
return;
|
||||
if(h->proto == USB_HID_PROTO_KEYBOARD)
|
||||
keyboard_report(h, buf, len);
|
||||
else
|
||||
mouse_report(h, buf, len);
|
||||
}
|
||||
|
||||
static int
|
||||
hid_request(struct usb_dev *dev, int request, int value)
|
||||
{
|
||||
struct usb_setup s;
|
||||
|
||||
s.bmRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
|
||||
s.bRequest = request;
|
||||
s.wValue = value;
|
||||
s.wIndex = dev->iface.bInterfaceNumber;
|
||||
s.wLength = 0;
|
||||
return dev->ops->control(dev, &s, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
hid_probe(struct usb_dev *dev)
|
||||
{
|
||||
struct hid *h = 0;
|
||||
int i, proto = dev->iface.bInterfaceProtocol;
|
||||
|
||||
if(!dev->has_intr_in){
|
||||
cprintf("usb-hid: no interrupt IN endpoint; nothing to read\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for(i = 0; i < NHID; i++)
|
||||
if(!hids[i].in_use){ h = &hids[i]; break; }
|
||||
if(h == 0)
|
||||
return -1;
|
||||
|
||||
// Ask for the fixed layout explicitly. A device that supports the boot
|
||||
// subclass still starts in report protocol, so without this the reports
|
||||
// arrive in whatever shape its report descriptor declares -- which we have
|
||||
// not read and cannot interpret.
|
||||
if(hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){
|
||||
cprintf("usb-hid: SET_PROTOCOL(boot) refused\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Idle rate 0 = "report only when something changes". The default is to
|
||||
// resend the current state every few milliseconds forever, which for a
|
||||
// keyboard means a constant stream of interrupts saying nothing.
|
||||
hid_request(dev, USB_HID_SET_IDLE, 0);
|
||||
|
||||
if(dev->ops->open_ep(dev, &dev->intr_in) < 0)
|
||||
return -1;
|
||||
|
||||
h->in_use = 1;
|
||||
h->proto = proto;
|
||||
dev->drvdata = h;
|
||||
dev->complete = hid_complete;
|
||||
|
||||
// Arm the first transfer. Until a TRB is waiting on the endpoint the device
|
||||
// has nowhere to put a report, so nothing would ever arrive.
|
||||
if(dev->ops->queue_in(dev, dev->intr_in.bEndpointAddress, 0, 0) < 0)
|
||||
return -1;
|
||||
|
||||
cprintf("usb-hid: %s on slot %d, endpoint 0x%x every %d ms\n",
|
||||
proto == USB_HID_PROTO_KEYBOARD ? "keyboard" :
|
||||
proto == USB_HID_PROTO_MOUSE ? "mouse" : "device",
|
||||
dev->slot, dev->intr_in.bEndpointAddress, dev->intr_in.bInterval);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Boot subclass only. A HID device that does not claim it needs its report
|
||||
// descriptor parsed, and we would have nothing useful to do with it.
|
||||
const struct usb_driver usb_hid_driver = {
|
||||
.name = "usb-hid",
|
||||
.class = USB_CLASS_HID,
|
||||
.subclass = USB_HID_SUBCLASS_BOOT,
|
||||
.protocol = 0xFF,
|
||||
.devclass = DEVCLASS_INPUT,
|
||||
.probe = hid_probe,
|
||||
};
|
||||
Loading…
Reference in a new issue