blob: 13319665d40f5f8ea3d61e72509c8647e7ddf9c0 [file] [log] [blame]
/*
* 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 <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/time.h>
#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, 0220, 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 <charliemooney@google.com>");
MODULE_LICENSE("GPL");