[PATCH] Input: psmouse - further improve error handling forbasic protocols

From: Jim Hill
Date: Mon Mar 28 2011 - 16:10:36 EST


Keep a failing PS/2 mouse usable until it's convenient to replace it.
Filter incoming packets: drop invalid ones and attempt to correct for
dropped bytes.

New parameter 'filter' makes filtering and logging selectable, leave at 0
to shut off all effects, 3 to work silently.
--
drivers/input/mouse/psmouse-base.c | 197 +++++++++++++++++++++++++++++++++++++
drivers/input/mouse/psmouse.h | 7 ++
2 files changed, 204 insertions(+)

diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mous/psmouse-base.c
index 22fe254..4a3a95f 100644
--- a/drivers/input/mouse/psmouse-base.c
+++ b/drivers/input/mouse/psmouse-base.c
@@ -72,6 +72,25 @@ static unsigned int psmouse_resync_time;
module_param_named(resync_time, psmouse_resync_time, uint, 0644);
MODULE_PARM_DESC(resync_time, "How long can mouse stay idle before forcing resync (in seconds, 0 = never).");

+enum {
+ DROP_BAD_PACKET = 1,
+ ATTEMPT_SYNC = 2,
+ LOG_SUMMARIES = 4,
+ LOG_MALFORMED = 8,
+ LOG_ALL = 16,
+ REPORT_SYNC_FAILURE = 32,
+
+ DEFAULT_FILTER = 0
+};
+static int psmouse_filter = DEFAULT_FILTER;
+module_param_named(filter, psmouse_filter, int, 0644);
+MODULE_PARM_DESC(filter, "1 = drop invalid or hotio packets"
+ ", +2 = attempt-sync"
+ ", +4 = summary logs"
+ ", +8 = log-malformed"
+ ",+16 = log-all"
+ ",+32 = use hard resets");
+
PSMOUSE_DEFINE_ATTR(protocol, S_IWUSR | S_IRUGO,
NULL,
psmouse_attr_show_protocol, psmouse_attr_set_protocol);
@@ -123,6 +142,169 @@ struct psmouse_protocol {
};

/*
+ * psmouse_filter_*: diagnose bad data, recover what we can, drop the rest, log
+ * selected events. See input_report_*()s in psmouse_process_byte below, and
+ * <http://www.computer-engineering.org/ps2mouse/>
+ */
+
+static int psmouse_filter_header_byte(enum psmouse_type type, int byte)
+{
+ int todo = 0;
+ if ((byte & 0xc0) &&
+ type != PSMOUSE_THINKPS &&
+ type != PSMOUSE_GENPS)
+ todo |= DROP_BAD_PACKET+ATTEMPT_SYNC;
+ if ((byte & 0x08) == 0 &&
+ type != PSMOUSE_THINKPS &&
+ type != PSMOUSE_CORTRON)
+ todo |= DROP_BAD_PACKET+ATTEMPT_SYNC;
+ return todo;
+}
+
+static int psmouse_filter_wheel_byte(enum psmouse_type type, int byte)
+{
+ int todo = 0;
+ if (type == PSMOUSE_IMPS || type == PSMOUSE_GENPS)
+ if (abs(byte) > 3)
+ todo = DROP_BAD_PACKET+ATTEMPT_SYNC;
+ if (type == PSMOUSE_IMEX)
+ if (((byte&0xC0) == 0) || ((byte&0XC0) == 0xC0))
+ if (abs((byte&0x08)-(byte&0x07)) > 3)
+ todo = DROP_BAD_PACKET+ATTEMPT_SYNC;
+ return todo;
+}
+
+static int psmouse_filter_motion(struct psmouse *m)
+{ /*
+ * Hunt for implausible accelerations here if it ever seems necessary.
+ * Header/wheel sniffing seems to detect everything recoverable so far.
+ */
+ return 0;
+}
+
+static int psmouse_filter_hotio(struct psmouse *m)
+{
+ int ret = 0;
+ if (time_after(m->last, m->hotio_interval_start + HZ/m->rate)) {
+ m->hotio_interval_pkts = 0;
+ m->hotio_interval_start = m->last;
+ }
+ if (m->hotio_interval_pkts++ > m->rate/HZ + 1) {
+ if (m->hotio_log_counter == 0)
+ m->hotio_log_interval_start = m->last;
+ ++m->hotio_log_counter;
+ ret = DROP_BAD_PACKET;
+ }
+ return ret;
+}
+
+static void psmouse_filter_packet_logger(struct psmouse *m, int todo,
+ const char *tag)
+{
+ if ((todo & psmouse_filter & LOG_MALFORMED) ||
+ (psmouse_filter & LOG_ALL)) {
+ unsigned long long packet = 0;
+ int p;
+ for (p = 0; p < m->pktcnt; ++p)
+ packet = packet<<8 | m->packet[p];
+ printk(KERN_INFO "psmouse.c: packet %0*llx%s\n", p*2, packet,
+ tag ? tag : "");
+ }
+}
+
+static int psmouse_filter_inspect_packet(struct psmouse *m)
+{
+ int todo = 0;
+ todo |= psmouse_filter_header_byte(m->type, m->packet[0]);
+ todo |= psmouse_filter_motion(m);
+ todo |= psmouse_filter_wheel_byte(m->type, (signed char)m->packet[3]);
+ if (todo & DROP_BAD_PACKET) {
+ todo |= LOG_MALFORMED;
+ if (m->err_log_counter == 0)
+ m->err_log_interval_start = m->last;
+ ++m->err_log_counter;
+ }
+ return todo;
+}
+
+/*
+ * find-sync: if the mouse dropped one or more bytes the next packet start
+ * byte's almost certainly in this buffer. Return its offset if we can find it.
+ */
+static unsigned psmouse_filter_find_sync(struct psmouse *m)
+{
+ int p;
+
+ /* same buttons, same general direction as last report? Seems best */
+ for (p = m->pktcnt; --p ; )
+ if (m->packet[p] == m->last_good_packet[0])
+ return p;
+
+ /* same or no buttons, plausible direction from what we can see? */
+ for (p = m->pktcnt; --p ; ) {
+ signed char test0 = (signed char)m->packet[p];
+ signed char last0 = (signed char)m->last_good_packet[0];
+ signed char test1 = (signed char)m->packet[(p+1)%m->pktcnt];
+ signed char test2 = (signed char)m->packet[(p+2)%m->pktcnt];
+ if (((test0&7) == 0 || (test0&7) == (last0&7)) &&
+ (test0>>4&1) == (test1>>7&1) &&
+ (test0>>5&1) == (test2>>7&1) &&
+ !psmouse_filter_header_byte(m->type, test0))
+ return p;
+ }
+ /* nothing looks good */
+ return 0;
+}
+
+static void psmouse_filter_write_summary_logs(struct psmouse *m)
+{
+ if (m->err_log_counter && time_after(m->last, m->err_log_interval_start+HZ)) {
+ printk(KERN_WARNING "psmouse.c: %s at %s %lu bad packets\n",
+ m->name, m->phys, m->err_log_counter);
+ m->err_log_counter = m->err_log_interval_start = 0;
+ }
+ if (m->hotio_log_counter && time_after(m->last, m->hotio_log_interval_start+HZ)) {
+ printk(KERN_WARNING "psmouse.c: %s at %s %lu excess packets\n",
+ m->name, m->phys, m->hotio_log_counter);
+ m->hotio_log_counter = m->hotio_log_interval_start = 0;
+ }
+}
+
+static int psmouse_filter_packet(struct psmouse *m)
+{
+ int todo;
+
+ if (psmouse_filter < 0)
+ psmouse_filter = DEFAULT_FILTER;
+
+ todo = psmouse_filter_inspect_packet(m);
+
+ if (!(todo & DROP_BAD_PACKET))
+ todo |= psmouse_filter_hotio(m);
+
+ psmouse_filter_packet_logger(m, todo, todo&LOG_MALFORMED ? " bad" : 0);
+ if (psmouse_filter & (LOG_SUMMARIES | LOG_ALL))
+ psmouse_filter_write_summary_logs(m);
+
+ if (todo & psmouse_filter & ATTEMPT_SYNC) {
+ unsigned p = psmouse_filter_find_sync(m);
+ if (p) {
+ m->pktcnt -= p;
+ memmove(m->packet, m->packet+p, m->pktcnt);
+ psmouse_filter_packet_logger(m, todo, " sync");
+ } else {
+ todo |= REPORT_SYNC_FAILURE;
+ todo &= ~ATTEMPT_SYNC;
+ }
+ }
+
+ if (!(todo & DROP_BAD_PACKET))
+ memcpy(m->last_good_packet, m->packet, sizeof m->packet);
+
+ return todo & psmouse_filter;
+}
+
+/*
* psmouse_process_byte() analyzes the PS/2 data stream and reports
* relevant events to the input module once full packet has arrived.
*/
@@ -135,6 +317,15 @@ psmouse_ret_t psmouse_process_byte(struct psmouse *psmouse)
if (psmouse->pktcnt < psmouse->pktsize)
return PSMOUSE_GOOD_DATA;

+ {
+ int filter = psmouse_filter_packet(psmouse);
+ if (filter & ATTEMPT_SYNC)
+ return PSMOUSE_GOOD_DATA;
+ if (filter & REPORT_SYNC_FAILURE)
+ return PSMOUSE_BAD_DATA;
+ if (filter & DROP_BAD_PACKET)
+ return PSMOUSE_FULL_PACKET;
+ }
/*
* Full packet accumulated, process it
*/
@@ -226,6 +417,12 @@ static inline void __psmouse_set_state(struct psmouse *psmouse, enum psmouse_sta
psmouse->pktcnt = psmouse->out_of_sync_cnt = 0;
psmouse->ps2dev.flags = 0;
psmouse->last = jiffies;
+ psmouse->err_log_interval_start = 0;
+ psmouse->err_log_counter = 0;
+ psmouse->hotio_interval_start = 0;
+ psmouse->hotio_interval_pkts = 0;
+ psmouse->hotio_log_interval_start = 0;
+ psmouse->hotio_log_counter = 0;
}


diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h
index fe1df23..7d8817e7 100644
--- a/drivers/input/mouse/psmouse.h
+++ b/drivers/input/mouse/psmouse.h
@@ -44,6 +44,7 @@ struct psmouse {
char *vendor;
char *name;
unsigned char packet[8];
+ unsigned char last_good_packet[8];
unsigned char badbyte;
unsigned char pktcnt;
unsigned char pktsize;
@@ -54,6 +55,12 @@ struct psmouse {
unsigned long last;
unsigned long out_of_sync_cnt;
unsigned long num_resyncs;
+ unsigned long hotio_interval_start;
+ unsigned long hotio_interval_pkts;
+ unsigned long hotio_log_interval_start;
+ unsigned long hotio_log_counter;
+ unsigned long err_log_interval_start;
+ unsigned long err_log_counter;
enum psmouse_state state;
char devname[64];
char phys[32];
--
1.7.11.rc2.220.g2b5a256

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/