Commit 41b701f9 authored by Dmitry Torokhov's avatar Dmitry Torokhov

Input: switch atkbd driver from busy-polling for command completion

       to waiting for event
Signed-off-by: default avatarDmitry Torokhov <dtor@mail.ru>
parent f69c1f0f
...@@ -209,10 +209,25 @@ struct atkbd { ...@@ -209,10 +209,25 @@ struct atkbd {
unsigned int last; unsigned int last;
unsigned long time; unsigned long time;
/* Ensures that only one command is executing at a time */
struct semaphore cmd_sem;
/* Used to signal completion from interrupt handler */
wait_queue_head_t wait;
/* Flags */ /* Flags */
unsigned long flags; unsigned long flags;
}; };
/* Work structure to schedule execution of a command */
struct atkbd_work {
struct work_struct work;
struct atkbd *atkbd;
int command;
unsigned char param[0];
};
static void atkbd_report_key(struct input_dev *dev, struct pt_regs *regs, int code, int value) static void atkbd_report_key(struct input_dev *dev, struct pt_regs *regs, int code, int value)
{ {
input_regs(dev, regs); input_regs(dev, regs);
...@@ -262,10 +277,12 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data, ...@@ -262,10 +277,12 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
set_bit(ATKBD_FLAG_CMD1, &atkbd->flags); set_bit(ATKBD_FLAG_CMD1, &atkbd->flags);
} }
clear_bit(ATKBD_FLAG_ACK, &atkbd->flags); clear_bit(ATKBD_FLAG_ACK, &atkbd->flags);
wake_up_interruptible(&atkbd->wait);
break; break;
case ATKBD_RET_NAK: case ATKBD_RET_NAK:
atkbd->nak = 1; atkbd->nak = 1;
clear_bit(ATKBD_FLAG_ACK, &atkbd->flags); clear_bit(ATKBD_FLAG_ACK, &atkbd->flags);
wake_up_interruptible(&atkbd->wait);
break; break;
} }
goto out; goto out;
...@@ -276,10 +293,13 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data, ...@@ -276,10 +293,13 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
if (atkbd->cmdcnt) if (atkbd->cmdcnt)
atkbd->cmdbuf[--atkbd->cmdcnt] = code; atkbd->cmdbuf[--atkbd->cmdcnt] = code;
clear_bit(ATKBD_FLAG_CMD1, &atkbd->flags); if (test_and_clear_bit(ATKBD_FLAG_CMD1, &atkbd->flags) && atkbd->cmdcnt)
if (!atkbd->cmdcnt) wake_up_interruptible(&atkbd->wait);
clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
if (!atkbd->cmdcnt) {
clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
wake_up_interruptible(&atkbd->wait);
}
goto out; goto out;
} }
...@@ -412,12 +432,12 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data, ...@@ -412,12 +432,12 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
* acknowledge. It doesn't handle resends according to the keyboard * acknowledge. It doesn't handle resends according to the keyboard
* protocol specs, because if these are needed, the keyboard needs * protocol specs, because if these are needed, the keyboard needs
* replacement anyway, and they only make a mess in the protocol. * replacement anyway, and they only make a mess in the protocol.
*
* atkbd_sendbyte() can only be called from a process context
*/ */
static int atkbd_sendbyte(struct atkbd *atkbd, unsigned char byte) static int atkbd_sendbyte(struct atkbd *atkbd, unsigned char byte)
{ {
int timeout = 200000; /* 200 msec */
#ifdef ATKBD_DEBUG #ifdef ATKBD_DEBUG
printk(KERN_DEBUG "atkbd.c: Sent: %02x\n", byte); printk(KERN_DEBUG "atkbd.c: Sent: %02x\n", byte);
#endif #endif
...@@ -425,8 +445,9 @@ static int atkbd_sendbyte(struct atkbd *atkbd, unsigned char byte) ...@@ -425,8 +445,9 @@ static int atkbd_sendbyte(struct atkbd *atkbd, unsigned char byte)
set_bit(ATKBD_FLAG_ACK, &atkbd->flags); set_bit(ATKBD_FLAG_ACK, &atkbd->flags);
if (serio_write(atkbd->serio, byte) == 0) if (serio_write(atkbd->serio, byte) == 0)
while (test_bit(ATKBD_FLAG_ACK, &atkbd->flags) && timeout--) wait_event_interruptible_timeout(atkbd->wait,
udelay(1); !test_bit(ATKBD_FLAG_ACK, &atkbd->flags),
msecs_to_jiffies(200));
clear_bit(ATKBD_FLAG_ACK, &atkbd->flags); clear_bit(ATKBD_FLAG_ACK, &atkbd->flags);
return -atkbd->nak; return -atkbd->nak;
...@@ -435,19 +456,21 @@ static int atkbd_sendbyte(struct atkbd *atkbd, unsigned char byte) ...@@ -435,19 +456,21 @@ static int atkbd_sendbyte(struct atkbd *atkbd, unsigned char byte)
/* /*
* atkbd_command() sends a command, and its parameters to the keyboard, * atkbd_command() sends a command, and its parameters to the keyboard,
* then waits for the response and puts it in the param array. * then waits for the response and puts it in the param array.
*
* atkbd_command() can only be called from a process context
*/ */
static int atkbd_command(struct atkbd *atkbd, unsigned char *param, int command) static int atkbd_command(struct atkbd *atkbd, unsigned char *param, int command)
{ {
int timeout = 500000; /* 500 msec */ int timeout;
int send = (command >> 12) & 0xf; int send = (command >> 12) & 0xf;
int receive = (command >> 8) & 0xf; int receive = (command >> 8) & 0xf;
int rc = -1; int rc = -1;
int i; int i;
if (command == ATKBD_CMD_RESET_BAT) timeout = msecs_to_jiffies(command == ATKBD_CMD_RESET_BAT ? 4000 : 500);
timeout = 4000000; /* 4 sec */
down(&atkbd->cmd_sem);
clear_bit(ATKBD_FLAG_CMD, &atkbd->flags); clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
if (receive && param) if (receive && param)
...@@ -464,27 +487,26 @@ static int atkbd_command(struct atkbd *atkbd, unsigned char *param, int command) ...@@ -464,27 +487,26 @@ static int atkbd_command(struct atkbd *atkbd, unsigned char *param, int command)
if (atkbd_sendbyte(atkbd, param[i])) if (atkbd_sendbyte(atkbd, param[i]))
goto out; goto out;
while (test_bit(ATKBD_FLAG_CMD, &atkbd->flags) && timeout--) { timeout = wait_event_interruptible_timeout(atkbd->wait,
!test_bit(ATKBD_FLAG_CMD1, &atkbd->flags), timeout);
if (!test_bit(ATKBD_FLAG_CMD1, &atkbd->flags)) { if (atkbd->cmdcnt && timeout > 0) {
if (command == ATKBD_CMD_RESET_BAT && jiffies_to_msecs(timeout) > 100)
timeout = msecs_to_jiffies(100);
if (command == ATKBD_CMD_RESET_BAT && timeout > 100000) if (command == ATKBD_CMD_GETID &&
timeout = 100000; atkbd->cmdbuf[receive - 1] != 0xab && atkbd->cmdbuf[receive - 1] != 0xac) {
/*
if (command == ATKBD_CMD_GETID && * Device behind the port is not a keyboard
atkbd->cmdbuf[receive - 1] != 0xab && atkbd->cmdbuf[receive - 1] != 0xac) { * so we don't need to wait for the 2nd byte
/* * of ID response.
* Device behind the port is not a keyboard */
* so we don't need to wait for the 2nd byte clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
* of ID response. atkbd->cmdcnt = 0;
*/
clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
atkbd->cmdcnt = 0;
break;
}
} }
udelay(1); wait_event_interruptible_timeout(atkbd->wait,
!test_bit(ATKBD_FLAG_CMD, &atkbd->flags), timeout);
} }
if (param) if (param)
...@@ -499,10 +521,58 @@ static int atkbd_command(struct atkbd *atkbd, unsigned char *param, int command) ...@@ -499,10 +521,58 @@ static int atkbd_command(struct atkbd *atkbd, unsigned char *param, int command)
out: out:
clear_bit(ATKBD_FLAG_CMD, &atkbd->flags); clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
clear_bit(ATKBD_FLAG_CMD1, &atkbd->flags); clear_bit(ATKBD_FLAG_CMD1, &atkbd->flags);
up(&atkbd->cmd_sem);
return rc; return rc;
} }
/*
* atkbd_execute_scheduled_command() sends a command, previously scheduled by
* atkbd_schedule_command(), to the keyboard.
*/
static void atkbd_execute_scheduled_command(void *data)
{
struct atkbd_work *atkbd_work = data;
atkbd_command(atkbd_work->atkbd, atkbd_work->param, atkbd_work->command);
kfree(atkbd_work);
}
/*
* atkbd_schedule_command() allows to schedule delayed execution of a keyboard
* command and can be used to issue a command from an interrupt or softirq
* context.
*/
static int atkbd_schedule_command(struct atkbd *atkbd, unsigned char *param, int command)
{
struct atkbd_work *atkbd_work;
int send = (command >> 12) & 0xf;
int receive = (command >> 8) & 0xf;
if (!test_bit(ATKBD_FLAG_ENABLED, &atkbd->flags))
return -1;
if (!(atkbd_work = kmalloc(sizeof(struct atkbd_work) + max(send, receive), GFP_ATOMIC)))
return -1;
memset(atkbd_work, 0, sizeof(struct atkbd_work));
atkbd_work->atkbd = atkbd;
atkbd_work->command = command;
memcpy(atkbd_work->param, param, send);
INIT_WORK(&atkbd_work->work, atkbd_execute_scheduled_command, atkbd_work);
if (!schedule_work(&atkbd_work->work)) {
kfree(atkbd_work);
return -1;
}
return 0;
}
/* /*
* Event callback from the input module. Events that change the state of * Event callback from the input module. Events that change the state of
* the hardware are processed here. * the hardware are processed here.
...@@ -529,7 +599,7 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co ...@@ -529,7 +599,7 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co
param[0] = (test_bit(LED_SCROLLL, dev->led) ? 1 : 0) param[0] = (test_bit(LED_SCROLLL, dev->led) ? 1 : 0)
| (test_bit(LED_NUML, dev->led) ? 2 : 0) | (test_bit(LED_NUML, dev->led) ? 2 : 0)
| (test_bit(LED_CAPSL, dev->led) ? 4 : 0); | (test_bit(LED_CAPSL, dev->led) ? 4 : 0);
atkbd_command(atkbd, param, ATKBD_CMD_SETLEDS); atkbd_schedule_command(atkbd, param, ATKBD_CMD_SETLEDS);
if (atkbd->extra) { if (atkbd->extra) {
param[0] = 0; param[0] = 0;
...@@ -538,7 +608,7 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co ...@@ -538,7 +608,7 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co
| (test_bit(LED_SUSPEND, dev->led) ? 0x04 : 0) | (test_bit(LED_SUSPEND, dev->led) ? 0x04 : 0)
| (test_bit(LED_MISC, dev->led) ? 0x10 : 0) | (test_bit(LED_MISC, dev->led) ? 0x10 : 0)
| (test_bit(LED_MUTE, dev->led) ? 0x20 : 0); | (test_bit(LED_MUTE, dev->led) ? 0x20 : 0);
atkbd_command(atkbd, param, ATKBD_CMD_EX_SETLEDS); atkbd_schedule_command(atkbd, param, ATKBD_CMD_EX_SETLEDS);
} }
return 0; return 0;
...@@ -554,7 +624,7 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co ...@@ -554,7 +624,7 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co
dev->rep[REP_PERIOD] = period[i]; dev->rep[REP_PERIOD] = period[i];
dev->rep[REP_DELAY] = delay[j]; dev->rep[REP_DELAY] = delay[j];
param[0] = i | (j << 5); param[0] = i | (j << 5);
atkbd_command(atkbd, param, ATKBD_CMD_SETREP); atkbd_schedule_command(atkbd, param, ATKBD_CMD_SETREP);
return 0; return 0;
} }
...@@ -726,7 +796,11 @@ static void atkbd_cleanup(struct serio *serio) ...@@ -726,7 +796,11 @@ static void atkbd_cleanup(struct serio *serio)
static void atkbd_disconnect(struct serio *serio) static void atkbd_disconnect(struct serio *serio)
{ {
struct atkbd *atkbd = serio->private; struct atkbd *atkbd = serio->private;
clear_bit(ATKBD_FLAG_ENABLED, &atkbd->flags); clear_bit(ATKBD_FLAG_ENABLED, &atkbd->flags);
synchronize_kernel();
flush_scheduled_work();
input_unregister_device(&atkbd->dev); input_unregister_device(&atkbd->dev);
serio_close(serio); serio_close(serio);
kfree(atkbd); kfree(atkbd);
...@@ -748,6 +822,9 @@ static void atkbd_connect(struct serio *serio, struct serio_driver *drv) ...@@ -748,6 +822,9 @@ static void atkbd_connect(struct serio *serio, struct serio_driver *drv)
return; return;
memset(atkbd, 0, sizeof(struct atkbd)); memset(atkbd, 0, sizeof(struct atkbd));
init_MUTEX(&atkbd->cmd_sem);
init_waitqueue_head(&atkbd->wait);
switch (serio->type & SERIO_TYPE) { switch (serio->type & SERIO_TYPE) {
case SERIO_8042_XL: case SERIO_8042_XL:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment