Userspace driver for Microsoft serial mouse
9/1/2022
To learn the basics of serial programming in UNIX I created a simple userspace driver for a Micrsoft serial mouse.
I use uinput to emulate an input device.
/* * Microsoft mouse */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <termios.h> #include <ctype.h> #include <stdint.h> #include <linux/uinput.h> #define DOWN 1 #define UP 0 #define PORT "/dev/ttyUSB0" #define BAUD B1200 #define LBIT 1 << 5 #define RBIT 1 << 4 #define SYNCBIT 1 << 7 #define XBYTE0MASK 0x03 #define XBYTE1MASK 0x7F #define YBYTE0MASK 0x0C #define YBYTE2MASK 0x7F void process(void); void emit(int fd, int type, int code, int val); uint8_t packet[3]; struct { uint8_t left; uint8_t right; } mouse; struct uinput_setup usetup; int ud; int main(void) { int fd, flags, onbyte; ssize_t byte; struct termios options; fd = open(PORT, O_RDWR | O_NOCTTY); if (fd == -1) { perror("cannot open serial device"); exit(EXIT_FAILURE); } flags = fcntl(fd, F_GETFL); // get current flags fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); // mask out O_NONBLOCK = blocking // get port options and set flags tcgetattr(fd, &options); cfsetispeed(&options, BAUD); cfsetospeed(&options, BAUD); options.c_cflag |= (CLOCAL | CREAD); // local line, enable read // set character size options.c_cflag &= ~CSIZE; // mask out the character size bits options.c_cflag |= CS7; // 7 bits // no parity options.c_cflag &= ~PARENB; // mask out parity bit options.c_cflag &= ~CSTOPB; // mask out stop bit (unset = 1 bit) // raw input options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); options.c_iflag &= ~INPCK; // mask out parity checking options.c_oflag &= ~OPOST; // mask out postprocessing - raw output options.c_cc[VMIN] = 1; // minimum of 1 byte read options.c_cc[VTIME] = 0; // wait indefinitely // apply options tcsetattr(fd, TCSANOW, &options); // setup uevent device ud = open("/dev/uinput", O_WRONLY | O_NONBLOCK); if (ud == -1) { perror("cannot open /dev/uinput"); exit(EXIT_FAILURE); } // enable mouse left/right and relative events ioctl(ud, UI_SET_EVBIT, EV_KEY); ioctl(ud, UI_SET_KEYBIT, BTN_LEFT); ioctl(ud, UI_SET_KEYBIT, BTN_RIGHT); ioctl(ud, UI_SET_EVBIT, EV_REL); ioctl(ud, UI_SET_RELBIT, REL_X); ioctl(ud, UI_SET_RELBIT, REL_Y); memset(&usetup, 0, sizeof(usetup)); usetup.id.bustype = BUS_USB; usetup.id.vendor = 0x1234; usetup.id.product = 0x5678; strcpy(usetup.name, "Userspace Microsoft serial mouse"); ioctl(ud, UI_DEV_SETUP, &usetup); ioctl(ud, UI_DEV_CREATE); mouse.left = 0x00; mouse.right = 0x00; onbyte = 0; byte = 0x00; while(1) { read(fd, &byte, 1); if (byte == 0xcd) // some kind of init? continue; // is byte first of packet? if (byte & SYNCBIT && onbyte == 0) { packet[0] = byte; onbyte = 1; } else if (onbyte == 1) { packet[1] = byte; onbyte = 2; } else if (onbyte == 2) { packet[2] = byte; onbyte = 0; process(); } fflush(stdout); } close(fd); ioctl(ud, UI_DEV_DESTROY); close(ud); return 1; } void process(void) { int8_t relx, rely; printf("0x%02x\t", packet[0]); printf("0x%02x\t", packet[1]); printf("0x%02x\t\n\n", packet[2]); if (packet[0] & LBIT && !mouse.left) { mouse.left = DOWN; puts("left down"); emit(ud, EV_KEY, BTN_LEFT, 1); emit(ud, EV_SYN, SYN_REPORT, 0); } else if (mouse.left && !(packet[0] & LBIT)) { mouse.left = UP; puts("left up"); emit(ud, EV_KEY, BTN_LEFT, 0); emit(ud, EV_SYN, SYN_REPORT, 0); } if (packet[0] & RBIT && !mouse.right) { mouse.right = DOWN; puts("right down"); emit(ud, EV_KEY, BTN_RIGHT, 1); emit(ud, EV_SYN, SYN_REPORT, 0); } else if (mouse.right && !(packet[0] & RBIT)) { mouse.right = UP; emit(ud, EV_KEY, BTN_RIGHT, 0); emit(ud, EV_SYN, SYN_REPORT, 0); } relx |= ((packet[0] & XBYTE0MASK)<<6) | (packet[1] & XBYTE1MASK); rely |= ((packet[0] & YBYTE0MASK)<<4) | (packet[2] & YBYTE2MASK); emit(ud, EV_REL, REL_X, relx); emit(ud, EV_REL, REL_Y, rely); emit(ud, EV_SYN, SYN_REPORT, 0); } void emit(int fd, int type, int code, int val) { struct input_event ie; ie.type = type; ie.code = code; ie.value = val; /* timestamp values below are ignored */ ie.time.tv_sec = 0; ie.time.tv_usec = 0; write(fd, &ie, sizeof(ie)); }