Input infrastructure, INPUT server, PCKBD driver
This commit separates the low-level keyboard driver from TTY, putting
it in a separate driver (PCKBD). The commit also separates management
of raw input devices from TTY, and puts it in a separate server
(INPUT). All keyboard and mouse input from hardware is sent by drivers
to the INPUT server, which either sends it to a process that has
opened a raw input device, or otherwise forwards it to TTY for
standard processing.
Design by Dirk Vogt. Prototype by Uli Kastlunger.
Additional changes made to the prototype:
- the event communication is now based on USB HID codes; all input
drivers have to use USB codes to describe events;
- all TTY keymaps have been converted to USB format, with the effect
that a single keymap covers all keys; there is no (static) escaped
keymap anymore;
- further keymap tweaks now allow remapping of literally all keys;
- input device renumbering and protocol rewrite;
- INPUT server rewrite, with added support for cancel and select;
- PCKBD reimplementation, including PC/AT-to-USB translation;
- support for manipulating keyboard LEDs has been added;
- keyboard and mouse multiplexer devices have been added to INPUT,
primarily so that an X server need only open two devices;
- a new "libinputdriver" library abstracts away protocol details from
input drivers, and should be used by all future input drivers;
- both INPUT and PCKBD can be restarted;
- TTY is now scheduled by KERNEL, so that it won't be punished for
running a lot; without this, simply running "yes" on the console
kills the system;
- the KIOCBELL IOCTL has been moved to /dev/console;
- support for the SCANCODES termios setting has been removed;
- obsolete keymap compression has been removed;
- the obsolete Olivetti M24 keymap has been removed.
Change-Id: I3a672fb8c4fd566734e4b46d3994b4b7fc96d578
2013-09-28 14:46:21 +02:00
|
|
|
/* Keyboard/mouse input server. */
|
|
|
|
#include <minix/drivers.h>
|
|
|
|
#include <minix/chardriver.h>
|
|
|
|
#include <minix/ds.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <sys/kbdio.h>
|
|
|
|
|
|
|
|
#include "input.h"
|
|
|
|
|
|
|
|
#define INPUT_DEBUG 0
|
|
|
|
|
|
|
|
static int input_open(devminor_t, int, endpoint_t);
|
|
|
|
static int input_close(devminor_t);
|
|
|
|
static ssize_t input_read(devminor_t, u64_t, endpoint_t, cp_grant_id_t, size_t,
|
|
|
|
int, cdev_id_t);
|
|
|
|
static int input_ioctl(devminor_t, unsigned long, endpoint_t, cp_grant_id_t,
|
|
|
|
int, endpoint_t, cdev_id_t);
|
|
|
|
static int input_cancel(devminor_t, endpoint_t, cdev_id_t);
|
|
|
|
static int input_select(devminor_t, unsigned int, endpoint_t);
|
|
|
|
static void input_other(message *, int);
|
|
|
|
|
|
|
|
static struct input_dev devs[INPUT_DEV_MAX];
|
|
|
|
|
|
|
|
#define input_dev_active(dev) ((dev)->owner != NONE || \
|
|
|
|
(dev)->minor == KBDMUX_MINOR || \
|
|
|
|
(dev)->minor == MOUSEMUX_MINOR)
|
|
|
|
#define input_dev_buf_empty(dev) ((dev)->count == 0)
|
|
|
|
#define input_dev_buf_full(dev) ((dev)->count == EVENTBUF_SIZE)
|
|
|
|
|
|
|
|
/* Entry points to the input driver. */
|
|
|
|
static struct chardriver input_tab = {
|
|
|
|
.cdr_open = input_open,
|
|
|
|
.cdr_close = input_close,
|
|
|
|
.cdr_read = input_read,
|
|
|
|
.cdr_ioctl = input_ioctl,
|
|
|
|
.cdr_cancel = input_cancel,
|
|
|
|
.cdr_select = input_select,
|
|
|
|
.cdr_other = input_other
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Map a minor number to an input device structure.
|
|
|
|
*/
|
|
|
|
static struct input_dev *
|
|
|
|
input_map(devminor_t minor)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* The minor device numbers were chosen not to be equal to the array
|
|
|
|
* slots, so that more keyboards can be added without breaking backward
|
|
|
|
* compatibility later.
|
|
|
|
*/
|
|
|
|
if (minor == KBDMUX_MINOR)
|
|
|
|
return &devs[KBDMUX_DEV];
|
|
|
|
else if (minor >= KBD0_MINOR && minor < KBD0_MINOR + KBD_MINORS)
|
|
|
|
return &devs[FIRST_KBD_DEV + (minor - KBD0_MINOR)];
|
|
|
|
else if (minor == MOUSEMUX_MINOR)
|
|
|
|
return &devs[MOUSEMUX_DEV];
|
|
|
|
else if (minor >= MOUSE0_MINOR && minor < MOUSE0_MINOR + MOUSE_MINORS)
|
|
|
|
return &devs[FIRST_MOUSE_DEV + (minor - MOUSE0_MINOR)];
|
|
|
|
else
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Map an input device structure index to a minor number.
|
|
|
|
*/
|
|
|
|
static devminor_t
|
|
|
|
input_revmap(int id)
|
|
|
|
{
|
|
|
|
if (id == KBDMUX_DEV)
|
|
|
|
return KBDMUX_MINOR;
|
|
|
|
else if (id >= FIRST_KBD_DEV && id <= LAST_KBD_DEV)
|
|
|
|
return KBD0_MINOR + (id - FIRST_KBD_DEV);
|
|
|
|
else if (id == MOUSEMUX_DEV)
|
|
|
|
return MOUSEMUX_MINOR;
|
|
|
|
else if (id >= FIRST_MOUSE_DEV && id <= LAST_MOUSE_DEV)
|
|
|
|
return MOUSE0_MINOR + (id - FIRST_MOUSE_DEV);
|
|
|
|
else
|
|
|
|
panic("reverse-mapping invalid ID %d", id);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Open an input device.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
input_open(devminor_t minor, int UNUSED(access), endpoint_t UNUSED(user_endpt))
|
|
|
|
{
|
|
|
|
struct input_dev *input_dev;
|
|
|
|
|
|
|
|
if ((input_dev = input_map(minor)) == NULL)
|
|
|
|
return ENXIO;
|
|
|
|
|
|
|
|
if (!input_dev_active(input_dev))
|
|
|
|
return ENXIO;
|
|
|
|
|
|
|
|
if (input_dev->opened)
|
|
|
|
return EBUSY;
|
|
|
|
|
|
|
|
input_dev->opened = TRUE;
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Close an input device.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
input_close(devminor_t minor)
|
|
|
|
{
|
|
|
|
struct input_dev *input_dev;
|
|
|
|
|
|
|
|
if ((input_dev = input_map(minor)) == NULL)
|
|
|
|
return ENXIO;
|
|
|
|
|
|
|
|
if (!input_dev->opened) {
|
|
|
|
printf("INPUT: closing already-closed device %d\n", minor);
|
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
input_dev->opened = FALSE;
|
|
|
|
input_dev->tail = 0;
|
|
|
|
input_dev->count = 0;
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copy input events to a reader.
|
|
|
|
*/
|
|
|
|
static ssize_t
|
|
|
|
input_copy_events(endpoint_t endpt, cp_grant_id_t grant,
|
|
|
|
unsigned int event_count, struct input_dev *input_dev)
|
|
|
|
{
|
|
|
|
int r, nbytes, wrap_left;
|
|
|
|
size_t event_size = sizeof(*input_dev->eventbuf);
|
|
|
|
|
|
|
|
if (input_dev->count < event_count)
|
|
|
|
panic("input_copy_events: not enough input is ready");
|
|
|
|
|
|
|
|
wrap_left = input_dev->tail + event_count - EVENTBUF_SIZE;
|
|
|
|
nbytes = (wrap_left <= 0 ? event_count :
|
|
|
|
EVENTBUF_SIZE - input_dev->tail) * event_size;
|
|
|
|
|
|
|
|
if ((r = sys_safecopyto(endpt, grant, 0,
|
|
|
|
(vir_bytes)(input_dev->eventbuf + input_dev->tail), nbytes)) != OK)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
/* Copy possible remaining part if we wrap over. */
|
|
|
|
if (wrap_left > 0 && (r = sys_safecopyto(endpt, grant, nbytes,
|
|
|
|
(vir_bytes) input_dev->eventbuf, wrap_left * event_size)) != OK)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
input_dev->tail = (input_dev->tail + event_count) % EVENTBUF_SIZE;
|
|
|
|
input_dev->count -= event_count;
|
|
|
|
|
|
|
|
return event_size * event_count; /* bytes copied */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read from an input device.
|
|
|
|
*/
|
|
|
|
static ssize_t
|
|
|
|
input_read(devminor_t minor, u64_t UNUSED(position), endpoint_t endpt,
|
|
|
|
cp_grant_id_t grant, size_t size, int flags, cdev_id_t id)
|
|
|
|
{
|
|
|
|
unsigned int event_count;
|
|
|
|
struct input_dev *input_dev;
|
|
|
|
|
|
|
|
if ((input_dev = input_map(minor)) == NULL)
|
|
|
|
return ENXIO;
|
|
|
|
|
|
|
|
/* We cannot accept more than one pending read request at once. */
|
|
|
|
if (!input_dev_active(input_dev) || input_dev->suspended)
|
|
|
|
return EIO;
|
|
|
|
|
|
|
|
/* The caller's buffer must have room for at least one whole event. */
|
|
|
|
event_count = size / sizeof(*input_dev->eventbuf);
|
|
|
|
if (event_count == 0)
|
|
|
|
return EIO;
|
|
|
|
|
|
|
|
/* No data available? Suspend the caller, unless we shouldn't block. */
|
|
|
|
if (input_dev_buf_empty(input_dev)) {
|
|
|
|
if (flags & CDEV_NONBLOCK)
|
|
|
|
return EAGAIN;
|
|
|
|
|
|
|
|
input_dev->suspended = TRUE;
|
|
|
|
input_dev->caller = endpt;
|
|
|
|
input_dev->grant = grant;
|
|
|
|
input_dev->req_id = id;
|
|
|
|
|
|
|
|
/* We should now wake up any selector, but that's lame.. */
|
|
|
|
return EDONTREPLY;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event_count > input_dev->count)
|
|
|
|
event_count = input_dev->count;
|
|
|
|
|
|
|
|
return input_copy_events(endpt, grant, event_count, input_dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set keyboard LEDs on one or all keyboards.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
input_set_leds(devminor_t minor, unsigned int mask)
|
|
|
|
{
|
|
|
|
struct input_dev *dev;
|
|
|
|
message m;
|
|
|
|
int i, r;
|
|
|
|
|
|
|
|
/* Prepare the request message */
|
|
|
|
memset(&m, 0, sizeof(m));
|
|
|
|
|
|
|
|
m.m_type = INPUT_SETLEDS;
|
|
|
|
m.INPUT_LED_MASK = mask;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Send the request to all matching keyboard devices. As side effect,
|
|
|
|
* this approach discards the request on mouse devices.
|
|
|
|
*/
|
|
|
|
for (i = FIRST_KBD_DEV; i <= LAST_KBD_DEV; i++) {
|
|
|
|
dev = &devs[i];
|
|
|
|
|
|
|
|
if (minor != KBDMUX_MINOR && minor != dev->minor)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Save the new state; the driver might (re)start later. */
|
|
|
|
dev->leds = mask;
|
|
|
|
|
|
|
|
if (dev->owner != NONE) {
|
|
|
|
if ((r = asynsend3(dev->owner, &m, AMF_NOREPLY)) != OK)
|
|
|
|
printf("INPUT: asynsend to %u failed (%d)\n",
|
|
|
|
dev->owner, r);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Process an IOCTL request.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
input_ioctl(devminor_t minor, unsigned long request, endpoint_t endpt,
|
|
|
|
cp_grant_id_t grant, int flags, endpoint_t user_endpt, cdev_id_t id)
|
|
|
|
{
|
|
|
|
struct input_dev *input_dev;
|
|
|
|
kio_leds_t leds;
|
|
|
|
unsigned int mask;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if ((input_dev = input_map(minor)) == NULL)
|
|
|
|
return ENXIO;
|
|
|
|
|
|
|
|
if (!input_dev_active(input_dev))
|
|
|
|
return EIO;
|
|
|
|
|
|
|
|
switch (request) {
|
|
|
|
case KIOCSLEDS:
|
|
|
|
if ((r = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &leds,
|
|
|
|
sizeof(leds))) != OK)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
mask = 0;
|
|
|
|
if (leds.kl_bits & KBD_LEDS_NUM)
|
|
|
|
mask |= (1 << INPUT_LED_NUMLOCK);
|
|
|
|
if (leds.kl_bits & KBD_LEDS_CAPS)
|
|
|
|
mask |= (1 << INPUT_LED_CAPSLOCK);
|
|
|
|
if (leds.kl_bits & KBD_LEDS_SCROLL)
|
|
|
|
mask |= (1 << INPUT_LED_SCROLLLOCK);
|
|
|
|
|
|
|
|
input_set_leds(minor, mask);
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return ENOTTY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Cancel a suspended read request.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
input_cancel(devminor_t minor, endpoint_t endpt, cdev_id_t id)
|
|
|
|
{
|
|
|
|
struct input_dev *input_dev;
|
|
|
|
|
|
|
|
if ((input_dev = input_map(minor)) == NULL)
|
|
|
|
return ENXIO;
|
|
|
|
|
|
|
|
if (input_dev->suspended && input_dev->caller == endpt &&
|
|
|
|
input_dev->req_id == id) {
|
|
|
|
input_dev->suspended = FALSE;
|
|
|
|
|
|
|
|
return EINTR;
|
|
|
|
}
|
|
|
|
|
|
|
|
return EDONTREPLY;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Perform a select call on an input device.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
input_select(devminor_t minor, unsigned int ops, endpoint_t endpt)
|
|
|
|
{
|
|
|
|
struct input_dev *input_dev;
|
|
|
|
int ready_ops;
|
|
|
|
|
|
|
|
if ((input_dev = input_map(minor)) == NULL)
|
|
|
|
return ENXIO;
|
|
|
|
|
|
|
|
ready_ops = 0;
|
|
|
|
|
|
|
|
if (ops & CDEV_OP_RD) {
|
|
|
|
if (!input_dev_active(input_dev) || input_dev->suspended)
|
|
|
|
ready_ops |= CDEV_OP_RD; /* immediate error */
|
|
|
|
else if (!input_dev_buf_empty(input_dev))
|
|
|
|
ready_ops |= CDEV_OP_RD; /* data available */
|
|
|
|
else if (ops & CDEV_NOTIFY)
|
|
|
|
input_dev->selector = endpt; /* report later */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ops & CDEV_OP_WR) ready_ops |= CDEV_OP_WR; /* immediate error */
|
|
|
|
|
|
|
|
return ready_ops;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* An input device receives an input event. Enqueue it, and possibly unsuspend
|
|
|
|
* a read request or wake up a selector.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
input_process(struct input_dev *input_dev, const message *m)
|
|
|
|
{
|
|
|
|
unsigned int next;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if (input_dev_buf_full(input_dev)) {
|
|
|
|
/* Overflow. Overwrite the oldest event. */
|
|
|
|
input_dev->tail = (input_dev->tail + 1) % EVENTBUF_SIZE;
|
|
|
|
input_dev->count--;
|
|
|
|
|
|
|
|
#if INPUT_DEBUG
|
|
|
|
printf("INPUT: overflow on device %u\n", input_dev - devs);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
next = (input_dev->tail + input_dev->count) % EVENTBUF_SIZE;
|
|
|
|
input_dev->eventbuf[next].page = m->INPUT_PAGE;
|
|
|
|
input_dev->eventbuf[next].code = m->INPUT_CODE;
|
|
|
|
input_dev->eventbuf[next].value = m->INPUT_VALUE;
|
|
|
|
input_dev->eventbuf[next].flags = m->INPUT_FLAGS;
|
|
|
|
input_dev->eventbuf[next].devid = m->INPUT_ID;
|
|
|
|
input_dev->eventbuf[next].rsvd[0] = 0;
|
|
|
|
input_dev->eventbuf[next].rsvd[1] = 0;
|
|
|
|
input_dev->count++;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* There is new input. Revive a suspended reader if there was one.
|
|
|
|
* Otherwise see if we should reply to a select query.
|
|
|
|
*/
|
|
|
|
if (input_dev->suspended) {
|
|
|
|
r = input_copy_events(input_dev->caller, input_dev->grant, 1,
|
|
|
|
input_dev);
|
|
|
|
chardriver_reply_task(input_dev->caller, input_dev->req_id, r);
|
|
|
|
input_dev->suspended = FALSE;
|
|
|
|
} else if (input_dev->selector != NONE) {
|
|
|
|
chardriver_reply_select(input_dev->selector, input_dev->minor,
|
|
|
|
CDEV_OP_RD);
|
|
|
|
input_dev->selector = NONE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* An input event has arrived from a driver.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
input_event(message *m)
|
|
|
|
{
|
|
|
|
struct input_dev *input_dev, *mux_dev;
|
|
|
|
int r, id;
|
|
|
|
|
|
|
|
/* Unlike minor numbers, device IDs are in fact array indices. */
|
|
|
|
id = m->INPUT_ID;
|
|
|
|
if (id < 0 || id >= INPUT_DEV_MAX)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* The sender must owner the device. */
|
|
|
|
input_dev = &devs[id];
|
|
|
|
if (input_dev->owner != m->m_source)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Input events are also delivered to the respective multiplexer. */
|
|
|
|
if (input_dev->minor >= KBD0_MINOR &&
|
|
|
|
input_dev->minor < KBD0_MINOR + KBD_MINORS)
|
|
|
|
mux_dev = &devs[KBDMUX_DEV];
|
|
|
|
else
|
|
|
|
mux_dev = &devs[MOUSEMUX_DEV];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Try to deliver the event to the input device or otherwise the
|
|
|
|
* corresponding multiplexer. If neither are opened, forward the event
|
|
|
|
* to TTY.
|
|
|
|
*/
|
|
|
|
if (input_dev->opened)
|
|
|
|
input_process(input_dev, m);
|
|
|
|
else if (mux_dev->opened)
|
|
|
|
input_process(mux_dev, m);
|
|
|
|
else {
|
|
|
|
m->m_type = TTY_INPUT_EVENT;
|
|
|
|
|
2013-11-01 13:34:14 +01:00
|
|
|
if ((r = ipc_send(TTY_PROC_NR, m)) != OK)
|
Input infrastructure, INPUT server, PCKBD driver
This commit separates the low-level keyboard driver from TTY, putting
it in a separate driver (PCKBD). The commit also separates management
of raw input devices from TTY, and puts it in a separate server
(INPUT). All keyboard and mouse input from hardware is sent by drivers
to the INPUT server, which either sends it to a process that has
opened a raw input device, or otherwise forwards it to TTY for
standard processing.
Design by Dirk Vogt. Prototype by Uli Kastlunger.
Additional changes made to the prototype:
- the event communication is now based on USB HID codes; all input
drivers have to use USB codes to describe events;
- all TTY keymaps have been converted to USB format, with the effect
that a single keymap covers all keys; there is no (static) escaped
keymap anymore;
- further keymap tweaks now allow remapping of literally all keys;
- input device renumbering and protocol rewrite;
- INPUT server rewrite, with added support for cancel and select;
- PCKBD reimplementation, including PC/AT-to-USB translation;
- support for manipulating keyboard LEDs has been added;
- keyboard and mouse multiplexer devices have been added to INPUT,
primarily so that an X server need only open two devices;
- a new "libinputdriver" library abstracts away protocol details from
input drivers, and should be used by all future input drivers;
- both INPUT and PCKBD can be restarted;
- TTY is now scheduled by KERNEL, so that it won't be punished for
running a lot; without this, simply running "yes" on the console
kills the system;
- the KIOCBELL IOCTL has been moved to /dev/console;
- support for the SCANCODES termios setting has been removed;
- obsolete keymap compression has been removed;
- the obsolete Olivetti M24 keymap has been removed.
Change-Id: I3a672fb8c4fd566734e4b46d3994b4b7fc96d578
2013-09-28 14:46:21 +02:00
|
|
|
printf("INPUT: send to TTY failed (%d)\n", r);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate a device structure for an input driver of the given type, and
|
|
|
|
* return its ID. If the given label already owns a device ID of the right
|
|
|
|
* type, update that entry instead. If no device ID could be allocated, return
|
|
|
|
* INVALID_INPUT_ID.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
input_alloc_id(int mouse, endpoint_t owner, const char *label)
|
|
|
|
{
|
|
|
|
int n, id, start, end;
|
|
|
|
|
|
|
|
if (!mouse) {
|
|
|
|
start = FIRST_KBD_DEV;
|
|
|
|
end = LAST_KBD_DEV;
|
|
|
|
} else {
|
|
|
|
start = FIRST_MOUSE_DEV;
|
|
|
|
end = LAST_MOUSE_DEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
id = INVALID_INPUT_ID;
|
|
|
|
for (n = start; n <= end; n++) {
|
|
|
|
if (devs[n].owner != NONE) {
|
|
|
|
if (!strcmp(devs[n].label, label)) {
|
|
|
|
devs[n].owner = owner;
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
/* Do not allocate the ID of a disconnected but open device. */
|
|
|
|
} else if (!devs[n].opened && id == INVALID_INPUT_ID) {
|
|
|
|
id = n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (id != INVALID_INPUT_ID) {
|
|
|
|
devs[id].owner = owner;
|
|
|
|
strlcpy(devs[id].label, label, sizeof(devs[id].label));
|
|
|
|
|
|
|
|
#if INPUT_DEBUG
|
|
|
|
printf("INPUT: connected device %u to %u (%s)\n", id,
|
|
|
|
owner, label);
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
printf("INPUT: out of %s slots for new driver %d\n",
|
|
|
|
mouse ? "mouse" : "keyboard", owner);
|
|
|
|
}
|
|
|
|
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Register keyboard and/or a mouse devices for a driver.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
input_connect(endpoint_t owner, char *labelp, int typemask)
|
|
|
|
{
|
|
|
|
message m;
|
|
|
|
char label[DS_MAX_KEYLEN];
|
|
|
|
int r, kbd_id, mouse_id;
|
|
|
|
|
|
|
|
#if INPUT_DEBUG
|
|
|
|
printf("INPUT: connect request from %u (%s) for mask %x\n", owner,
|
|
|
|
labelp, typemask);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Check the driver's label. */
|
|
|
|
if ((r = ds_retrieve_label_name(label, owner)) != OK) {
|
|
|
|
printf("INPUT: unable to get label for %u: %d\n", owner, r);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (strcmp(label, labelp)) {
|
|
|
|
printf("INPUT: ignoring driver %s label %s\n", label, labelp);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
kbd_id = INVALID_INPUT_ID;
|
|
|
|
mouse_id = INVALID_INPUT_ID;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We ignore allocation failures here, thus possibly sending invalid
|
|
|
|
* IDs to the driver even for either or both the devices types it
|
|
|
|
* requested. As a result, the driver will not send us input for these
|
|
|
|
* device types, possibly effectively disabling the driver altogether.
|
|
|
|
* Theoretically we could still admit events to the multiplexers for
|
|
|
|
* such drivers, but that would lead to unexpected behavior with
|
|
|
|
* respect to keyboard LEDs, for example.
|
|
|
|
*/
|
|
|
|
if (typemask & INPUT_DEV_KBD)
|
|
|
|
kbd_id = input_alloc_id(FALSE /*mouse*/, owner, label);
|
|
|
|
if (typemask & INPUT_DEV_MOUSE)
|
|
|
|
mouse_id = input_alloc_id(TRUE /*mouse*/, owner, label);
|
|
|
|
|
|
|
|
memset(&m, 0, sizeof(m));
|
|
|
|
|
|
|
|
m.m_type = INPUT_CONF;
|
|
|
|
m.INPUT_KBD_ID = kbd_id;
|
|
|
|
m.INPUT_MOUSE_ID = mouse_id;
|
|
|
|
m.INPUT_RSVD1_ID = INVALID_INPUT_ID; /* reserved (joystick?) */
|
|
|
|
m.INPUT_RSVD2_ID = INVALID_INPUT_ID; /* reserved for future use */
|
|
|
|
|
|
|
|
if ((r = asynsend3(owner, &m, AMF_NOREPLY)) != OK)
|
|
|
|
printf("INPUT: asynsend to %u failed (%d)\n", owner, r);
|
|
|
|
|
|
|
|
/* If a keyboard was registered, also set its initial LED state. */
|
|
|
|
if (kbd_id != INVALID_INPUT_ID)
|
|
|
|
input_set_leds(devs[kbd_id].minor, devs[kbd_id].leds);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Disconnect a device.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
input_disconnect(struct input_dev *input_dev)
|
|
|
|
{
|
|
|
|
#if INPUT_DEBUG
|
|
|
|
printf("INPUT: disconnected device %u\n", input_dev - devs);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (input_dev->suspended) {
|
|
|
|
chardriver_reply_task(input_dev->caller, input_dev->req_id,
|
|
|
|
EIO);
|
|
|
|
input_dev->suspended = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (input_dev->selector != NONE) {
|
|
|
|
chardriver_reply_select(input_dev->selector, input_dev->minor,
|
|
|
|
CDEV_OP_RD);
|
|
|
|
input_dev->selector = NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
input_dev->owner = NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check for driver status changes in the data store.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
input_check(void)
|
|
|
|
{
|
|
|
|
char key[DS_MAX_KEYLEN], *label;
|
|
|
|
const char *driver_prefix = "drv.inp.";
|
|
|
|
u32_t value;
|
|
|
|
size_t len;
|
|
|
|
int i, r, type;
|
|
|
|
endpoint_t owner;
|
|
|
|
|
|
|
|
len = strlen(driver_prefix);
|
|
|
|
|
|
|
|
/* Check for new (input driver) entries. */
|
|
|
|
while (ds_check(key, &type, &owner) == OK) {
|
|
|
|
if ((r = ds_retrieve_u32(key, &value)) != OK) {
|
|
|
|
printf("INPUT: ds_retrieve_u32 failed (%d)\n", r);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Only check for input driver registration events. */
|
|
|
|
if (strncmp(key, driver_prefix, len))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* The prefix is followed by the driver's own label. */
|
|
|
|
label = &key[len];
|
|
|
|
|
|
|
|
input_connect(owner, label, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check for removed (label) entries. */
|
|
|
|
for (i = 0; i < INPUT_DEV_MAX; i++) {
|
|
|
|
/* This also skips the multiplexers. */
|
|
|
|
if (devs[i].owner == NONE)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
r = ds_retrieve_label_endpt(devs[i].label, &owner);
|
|
|
|
|
|
|
|
if (r == OK)
|
|
|
|
devs[i].owner = owner; /* not really necessary */
|
|
|
|
else if (r == ESRCH)
|
|
|
|
input_disconnect(&devs[i]);
|
|
|
|
else
|
|
|
|
printf("INPUT: ds_retrieve_label_endpt failed (%d)\n",
|
|
|
|
r);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Process messages not part of the character driver protocol.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
input_other(message *m, int ipc_status)
|
|
|
|
{
|
|
|
|
if (is_ipc_notify(ipc_status)) {
|
|
|
|
switch (m->m_source) {
|
|
|
|
case DS_PROC_NR:
|
|
|
|
input_check();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
printf("INPUT: unexpected notify from %d\n",
|
|
|
|
m->m_source);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* An input event from a registered driver. */
|
|
|
|
switch (m->m_type) {
|
|
|
|
case INPUT_EVENT:
|
|
|
|
input_event(m);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case INPUT_SETLEDS:
|
|
|
|
if (m->m_source == TTY_PROC_NR) {
|
|
|
|
input_set_leds(KBDMUX_MINOR, m->INPUT_LED_MASK);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
default:
|
|
|
|
printf("INPUT: unexpected message %d from %d\n",
|
|
|
|
m->m_type, m->m_source);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize the input server.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
input_init(int UNUSED(type), sef_init_info_t *UNUSED(info))
|
|
|
|
{
|
|
|
|
message m;
|
|
|
|
int i, r;
|
|
|
|
|
|
|
|
/* Initialize input device structures. */
|
|
|
|
for (i = 0; i < INPUT_DEV_MAX; i++) {
|
|
|
|
devs[i].minor = input_revmap(i);
|
|
|
|
devs[i].owner = NONE;
|
|
|
|
devs[i].tail = 0;
|
|
|
|
devs[i].count = 0;
|
|
|
|
devs[i].opened = FALSE;
|
|
|
|
devs[i].suspended = FALSE;
|
|
|
|
devs[i].selector = NONE;
|
|
|
|
devs[i].leds = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Subscribe to driver registration events for input drivers. */
|
|
|
|
if ((r = ds_subscribe("drv\\.inp\\..*", DSF_INITIAL)) != OK)
|
|
|
|
panic("INPUT: can't subscribe to driver events (%d)", r);
|
|
|
|
|
|
|
|
/* Announce our presence to VFS. */
|
|
|
|
chardriver_announce();
|
|
|
|
|
|
|
|
/* Announce our presence to TTY. */
|
|
|
|
memset(&m, 0, sizeof(m));
|
|
|
|
|
|
|
|
m.m_type = TTY_INPUT_UP;
|
|
|
|
|
2013-11-01 13:34:14 +01:00
|
|
|
if ((r = ipc_send(TTY_PROC_NR, &m)) != OK)
|
Input infrastructure, INPUT server, PCKBD driver
This commit separates the low-level keyboard driver from TTY, putting
it in a separate driver (PCKBD). The commit also separates management
of raw input devices from TTY, and puts it in a separate server
(INPUT). All keyboard and mouse input from hardware is sent by drivers
to the INPUT server, which either sends it to a process that has
opened a raw input device, or otherwise forwards it to TTY for
standard processing.
Design by Dirk Vogt. Prototype by Uli Kastlunger.
Additional changes made to the prototype:
- the event communication is now based on USB HID codes; all input
drivers have to use USB codes to describe events;
- all TTY keymaps have been converted to USB format, with the effect
that a single keymap covers all keys; there is no (static) escaped
keymap anymore;
- further keymap tweaks now allow remapping of literally all keys;
- input device renumbering and protocol rewrite;
- INPUT server rewrite, with added support for cancel and select;
- PCKBD reimplementation, including PC/AT-to-USB translation;
- support for manipulating keyboard LEDs has been added;
- keyboard and mouse multiplexer devices have been added to INPUT,
primarily so that an X server need only open two devices;
- a new "libinputdriver" library abstracts away protocol details from
input drivers, and should be used by all future input drivers;
- both INPUT and PCKBD can be restarted;
- TTY is now scheduled by KERNEL, so that it won't be punished for
running a lot; without this, simply running "yes" on the console
kills the system;
- the KIOCBELL IOCTL has been moved to /dev/console;
- support for the SCANCODES termios setting has been removed;
- obsolete keymap compression has been removed;
- the obsolete Olivetti M24 keymap has been removed.
Change-Id: I3a672fb8c4fd566734e4b46d3994b4b7fc96d578
2013-09-28 14:46:21 +02:00
|
|
|
printf("INPUT: send to TTY failed (%d)\n", r);
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set callbacks and invoke SEF startup.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
input_startup(void)
|
|
|
|
{
|
|
|
|
sef_setcb_init_fresh(input_init);
|
|
|
|
|
|
|
|
sef_startup();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Main program of the input server.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
main(void)
|
|
|
|
{
|
|
|
|
input_startup();
|
|
|
|
|
|
|
|
chardriver_task(&input_tab);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|