/* * HID driver for Quickstep, ChromeOS's Latency Measurement Gadget * * The device is connected via USB and transmits a byte each time a * laster is crossed. The job of the driver is to record when those events * happen and then make that information availible to the user via sysfs * entries. */ #include #include #include #include #include #include "hid-ids.h" #define MAX_CROSSINGS 64 enum change_type { OFF, ON }; struct qs_event { struct timespec time; enum change_type direction; }; struct qs_data { unsigned int head; struct qs_event events[MAX_CROSSINGS]; }; static ssize_t append_event(struct qs_event *event, char *buf, ssize_t len) { return snprintf(buf, len, "%010ld.%09ld\t%d\n", event->time.tv_sec, event->time.tv_nsec, event->direction); } static ssize_t show_log(struct device *dev, struct device_attribute *attr, char *buf) { int i, str_len; struct qs_data *data = dev_get_drvdata(dev); str_len = snprintf(buf, PAGE_SIZE, "Laser Crossings:\ntime\t\t\tdirection\n"); if (data->head >= MAX_CROSSINGS) { for (i = data->head % MAX_CROSSINGS; i < MAX_CROSSINGS; i++) { str_len += append_event(&data->events[i], buf + str_len, PAGE_SIZE - str_len); } } for (i = 0; i < data->head % MAX_CROSSINGS; i++) { str_len += append_event(&data->events[i], buf + str_len, PAGE_SIZE - str_len); } return str_len; } static void empty_quickstep_data(struct qs_data *data) { if (data == NULL) return; data->head = 0; } static ssize_t clear_log(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { empty_quickstep_data(dev_get_drvdata(dev)); return len; } static DEVICE_ATTR(laser, 0444, show_log, NULL); static DEVICE_ATTR(clear, 0222, NULL, clear_log); static struct attribute *dev_attrs[] = { &dev_attr_laser.attr, &dev_attr_clear.attr, NULL, }; static struct attribute_group dev_attr_group = {.attrs = dev_attrs}; static int quickstep_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret; struct qs_data *data; ret = hid_parse(hdev); if (ret) { hid_err(hdev, "parse failed\n"); return ret; } ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "hw start failed\n"); return ret; } ret = hid_hw_open(hdev); if (ret) { hid_err(hdev, "hw open failed\n"); hid_hw_stop(hdev); return ret; } data = kmalloc(sizeof(struct qs_data), GFP_KERNEL); empty_quickstep_data(data); hid_set_drvdata(hdev, data); ret = sysfs_create_group(&hdev->dev.kobj, &dev_attr_group); return ret; } static void quickstep_remove(struct hid_device *hdev) { sysfs_remove_group(&hdev->dev.kobj, &dev_attr_group); hid_hw_stop(hdev); kfree(hid_get_drvdata(hdev)); } static int quickstep_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *msg, int size) { struct timespec time; struct qs_data *data = hid_get_drvdata(hdev); getnstimeofday(&time); data->events[data->head % MAX_CROSSINGS].time = time; data->events[data->head % MAX_CROSSINGS].direction = msg[0] ? ON : OFF; data->head++; if (data->head >= MAX_CROSSINGS * 2) data->head = MAX_CROSSINGS + data->head % MAX_CROSSINGS; return 0; } static const struct hid_device_id quickstep_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_QUICKSTEP) }, { } }; MODULE_DEVICE_TABLE(hid, quickstep_devices); static struct hid_driver quickstep_driver = { .name = "quickstep", .id_table = quickstep_devices, .probe = quickstep_probe, .remove = quickstep_remove, .raw_event = quickstep_raw_event, }; static int __init quickstep_init(void) { return hid_register_driver(&quickstep_driver); } static void __exit quickstep_exit(void) { hid_unregister_driver(&quickstep_driver); } module_init(quickstep_init); module_exit(quickstep_exit); MODULE_AUTHOR("Charlie Mooney "); MODULE_LICENSE("GPL");