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));
}


