From: Florian Echtler <floe@butterbrot.org>
To: linux-input <linux-input@vger.kernel.org>
Subject: [RFC] surface-input
Date: Thu, 05 Sep 2013 16:15:37 +0200 [thread overview]
Message-ID: <52289209.2070707@butterbrot.org> (raw)
[-- Attachment #1.1: Type: text/plain, Size: 831 bytes --]
Hello everyone,
as mentioned earlier, I'm currently writing a multitouch input driver
for the Pixelsense (formerly Surface 2.0). It's now at a point where I'd
consider it mostly done, but a) I have very limited experience with
kernel drivers and b) there are some additional questions I have, so I'm
attaching the current state of my source code and would like to ask for
your comments.
Open question: it looks like just calling
input_mt_init_slots(poll_dev->input, MAX_CONTACTS, INPUT_MT_DIRECT);
in the initialization routine isn't enough. hid-multitouch doesn't seem
to use any other init commands, though, or did I overlook something?
Are there any other caveats of the input-mt library which I should be
aware of?
Thanks for your input, and best regards, Florian
--
SENT FROM MY DEC VT50 TERMINAL
[-- Attachment #1.2: Makefile --]
[-- Type: text/plain, Size: 1058 bytes --]
# Assumptions:
# This makefile assumes that kernel modules are installed in
# /lib/modules/'uname -r'/.
# Should the assumption be incorrect, please use:
# make MODULE_ROOT="/path/to/modules/for/your/kernel"
# to override assumed path
MODULE=surface-input
srcs := $(MODULE).c
obj-m := $(srcs:%.c=%.o)
MODULE_ROOT:= /lib/modules/$(shell uname -r)
BUILDDIR := $(MODULE_ROOT)/build
MODDIR := $(MODULE_ROOT)/updates
PWD := $(shell pwd)
override install_targets := $(srcs:%.c=install_%)
default:
$(MAKE) -C $(BUILDDIR) SUBDIRS=$(PWD) "obj-m=$(obj-m)" modules
.PHONY: default install all clean help $(install_targets)
install:
install -m 644 $(MODULE).ko $(MODDIR)
depmod -a
modprobe -r $(MODULE)
modprobe $(MODULE)
debug:
@echo
@echo "srcs="$(srcs)
@echo "obj-m="$(obj-m)
@echo "MODULE_ROOT="$(MODULE_ROOT)
@echo "PWD="$(PWD)
@echo "install targets: " $(install_targets)
@echo
clean:
-rm -rf *.o *.ko $(MODULE)*.mod.c Module.symvers .$(MODULE)* .tmp_versions modules.order
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1.3: surface-data.h --]
[-- Type: text/x-chdr; name="surface-data.h", Size: 2533 bytes --]
/*
Surface2.0/SUR40/PixelSense input driver v0.0.1
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
Copyright (C) 2013 by Florian 'floe' Echtler <floe@butterbrot.org>
Derived from the USB Skeleton driver 1.1,
Copyright (C) 2003 Greg Kroah-Hartman (greg@kroah.com)
Derived from the Apple USB BCM5974 multitouch driver,
Copyright (C) 2008 Henrik Rydberg (rydberg@euromail.se)
*/
#ifndef _SURFACE_DATA_H_
#define _SURFACE_DATA_H_
#define ID_MICROSOFT 0x045e
#define ID_SURFACE 0x0775
#define VIDEO_RES_X 960
#define VIDEO_RES_Y 540
#define VIDEO_BUFFER_SIZE VIDEO_RES_X * VIDEO_RES_Y
// read 512 bytes from endpoint 0x86 -> get header + blobs
struct surface_header {
uint16_t type; // always 0x0001
uint16_t count; // count of blobs (if == 0: continue prev. packet ID)
uint32_t packet_id;
uint32_t timestamp; // milliseconds (increases by 16 or 17 each frame)
uint32_t unknown; // "epoch?" always 02/03 00 00 00
};
struct surface_blob {
uint16_t blob_id;
uint8_t action; // 0x02 = enter/exit, 0x03 = update (?)
uint8_t unknown; // always 0x01 or 0x02 (no idea what this is?)
uint16_t bb_pos_x; // upper left corner of bounding box
uint16_t bb_pos_y;
uint16_t bb_size_x; // size of bounding box
uint16_t bb_size_y;
uint16_t pos_x; // finger tip position
uint16_t pos_y;
uint16_t ctr_x; // centroid position
uint16_t ctr_y;
uint16_t axis_x; // somehow related to major/minor axis, mostly:
uint16_t axis_y; // axis_x == bb_size_y && axis_y == bb_size_x
float angle; // orientation in radians relative to x axis
uint32_t area; // size in pixels/pressure (?)
uint8_t padding[32];
};
// read 512 bytes from endpoint 0x82 -> get header below
// continue reading 16k blocks until header.size bytes read
struct surface_image {
uint32_t magic; // "SUBF"
uint32_t packet_id;
uint32_t size; // always 0x0007e900 = 960x540
uint32_t timestamp; // milliseconds (increases by 16 or 17 each frame)
uint32_t unknown; // "epoch?" always 02/03 00 00 00
};
// read 8 bytes using control message 0xc0,0xb1,0x00,0x00
struct surface_sensors {
uint16_t temp;
uint16_t acc_x;
uint16_t acc_y;
uint16_t acc_z;
};
#endif // _SURFACE_DATA_H_
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1.4: surface-input.c --]
[-- Type: text/x-csrc; name="surface-input.c", Size: 11285 bytes --]
/*
Surface2.0/SUR40/PixelSense input driver v0.0.1
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
Copyright (C) 2013 by Florian 'floe' Echtler <floe@butterbrot.org>
Derived from the USB Skeleton driver 1.1,
Copyright (C) 2003 Greg Kroah-Hartman (greg@kroah.com)
Derived from the Apple USB BCM5974 multitouch driver,
Copyright (C) 2008 Henrik Rydberg (rydberg@euromail.se)
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/completion.h>
#include <asm/uaccess.h>
#include <linux/usb.h>
#include <linux/printk.h>
#include <linux/input-polldev.h>
#include <linux/input/mt.h>
#include <linux/usb/input.h>
#include "surface-data.h"
/* version information */
#define DRIVER_VERSION "0.0.1"
#define DRIVER_SHORT "surface-input"
#define DRIVER_AUTHOR "Florian 'floe' Echtler <floe@butterbrot.org>"
#define DRIVER_DESC "Surface2.0/SUR40/PixelSense input driver"
/* vendor and device IDs */
#define ID_MICROSOFT 0x045e
#define ID_SURFACE 0x0775
/* touch data endpoint */
#define TOUCH_ENDPOINT 0x86
/* polling interval (ms) */
#define POLL_INTERVAL 10
/* maximum number of contacts */
#define MAX_CONTACTS 64
/* device ID table */
static const struct usb_device_id surface_table[] = {
{USB_DEVICE(ID_MICROSOFT, ID_SURFACE)}, /* Microsoft Surface 2.0 */
{} /* terminating null entry */
};
/* control commands */
#define SURFACE_GET_VERSION 0xb0 /* 12 bytes string */
#define SURFACE_UNKNOWN1 0xb3 /* 5 bytes */
#define SURFACE_UNKNOWN2 0xc1 /* 24 bytes */
#define SURFACE_GET_STATE 0xc5 /* 4 bytes state (?) */
#define SURFACE_GET_SENSORS 0xb1 /* 8 bytes sensors */
/* FIXME: previous version used USB_RECIP_ENDPOINT, may have corrupted eeprom? */
#define surface_command(dev, command, index, buffer, size) \
usb_control_msg (dev->usbdev, usb_rcvctrlpipe (dev->usbdev, 0), command, \
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, 0x00, index, buffer, size, 1000)
MODULE_DEVICE_TABLE(usb, surface_table);
/* structure to hold all of our device specific stuff */
struct usb_surface {
struct usb_device *usbdev; /* save the usb device pointer */
struct usb_interface *interface; /* the interface for this device */
struct input_polled_dev *input; /* struct for polled input device */
unsigned char *bulk_in_buffer; /* the buffer to receive data */
size_t bulk_in_size; /* the maximum bulk packet size */
__u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */
char phys[64];
};
/* initialization routine, called from surface_open */
static int surface_init(struct usb_surface *dev)
{
int result;
__u8 buffer[24];
/* stupidly replay the original MS driver init sequence */
result = surface_command(dev, SURFACE_GET_VERSION, 0x00, buffer, 12 );
if (result < 0)
return result;
result = surface_command(dev, SURFACE_GET_VERSION, 0x01, buffer, 12 );
if (result < 0)
return result;
result = surface_command(dev, SURFACE_GET_VERSION, 0x02, buffer, 12 );
if (result < 0)
return result;
result = surface_command(dev, SURFACE_UNKNOWN2, 0x00, buffer, 24 );
if (result < 0)
return result;
result = surface_command(dev, SURFACE_UNKNOWN1, 0x00, buffer, 5 );
if (result < 0)
return result;
result = surface_command(dev, SURFACE_GET_VERSION, 0x03, buffer, 12 );
/* discard the result buffer - no known data inside except
some version strings, maybe extract these sometime.. */
return result;
}
/*
* callback routines from input_polled_dev
*/
/* enable the device, polling will now start */
void surface_open(struct input_polled_dev *polldev) {
struct usb_surface *surface = polldev->private;
pr_info("surface_open");
surface_init(surface);
}
/* disable device, polling has stopped */
void surface_close(struct input_polled_dev *polldev) {
/* no known way to stop the device, except to stop polling */
pr_info("surface_close");
}
/*
* this function is called when a whole contact has been processed,
* so that it can assign it to a slot and store the data there
*/
static void report_blob(struct surface_blob *blob, struct input_dev *input)
{
int wide, major, minor;
int slotnum = input_mt_get_slot_by_key(input, blob->blob_id);
if (slotnum < 0 || slotnum >= MAX_CONTACTS)
return;
/* FIXME: is this needed for the Pixelsense? */
/*if ((td->mtclass.quirks & MT_QUIRK_IGNORE_DUPLICATES) && mt) {
struct input_mt *mt = input->mt;
struct input_mt_slot *slot = &mt->slots[slotnum];
if (input_mt_is_active(slot) &&
input_mt_is_used(mt, slot))
return;
}*/
input_mt_slot(input, slotnum);
input_mt_report_slot_state(input, MT_TOOL_FINGER, 1);
wide = (blob->bb_size_x > blob->bb_size_y);
major = max(blob->bb_size_x, blob->bb_size_y);
minor = min(blob->bb_size_x, blob->bb_size_y);
input_event(input, EV_ABS, ABS_MT_POSITION_X, blob->pos_x);
input_event(input, EV_ABS, ABS_MT_POSITION_Y, blob->pos_y);
input_event(input, EV_ABS, ABS_MT_TOOL_X, blob->ctr_x);
input_event(input, EV_ABS, ABS_MT_TOOL_Y, blob->ctr_y);
input_event(input, EV_ABS, ABS_MT_ORIENTATION, wide);
input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
}
/* core function: poll for new input data */
void surface_poll(struct input_polled_dev *polldev) {
struct usb_surface *surface = polldev->private;
struct input_dev *input = polldev->input;
int result, bulk_read, need_blobs, packet_blobs, i;
uint32_t packet_id;
struct surface_header* header = (struct surface_header*)(surface->bulk_in_buffer);
struct surface_blob* inblob = (struct surface_blob*)(surface->bulk_in_buffer+sizeof(struct surface_header));
need_blobs = -1;
pr_info("surface_poll\n");
do {
/* perform a blocking bulk read to get data from the device */
result = usb_bulk_msg (surface->usbdev,
usb_rcvbulkpipe (surface->usbdev, surface->bulk_in_endpointAddr),
surface->bulk_in_buffer, 512, &bulk_read, 1000);
pr_info("got %d bytes\n",bulk_read);
if (result < 0) {
pr_err("error in usb_bulk_read");
return;
}
result = bulk_read - sizeof(struct surface_header);
if (result % sizeof(struct surface_blob) != 0) {
pr_err("transfer size mismatch");
return;
}
/* first packet? */
if (need_blobs == -1) {
need_blobs = header->count;
pr_info("expecting %d blobs\n",need_blobs);
packet_id = header->packet_id;
}
/* sanity check. when video data is also being retrieved, the packet ID
will usually increase in the middle of a series instead of at the end. */
if (packet_id != header->packet_id)
pr_warn("packet ID mismatch\n");
packet_blobs = result / sizeof(struct surface_blob);
pr_info("got %d blobs\n",packet_blobs);
/* packets always contain at least 4 blobs, even if they are empty */
if (packet_blobs > need_blobs)
packet_blobs = need_blobs;
for (i = 0; i < packet_blobs; i++) {
need_blobs--;
pr_info("got a blob\n");
report_blob( &(inblob[i]), input );
}
} while (need_blobs > 0);
input_mt_sync_frame(input);
input_sync(input);
}
/*
* power management routines
*/
/* PM operations are nops as this driver does IO only during open() */
/*static int surface_suspend(struct usb_interface *intf, pm_message_t message)
{
return 0;
}
static int surface_resume(struct usb_interface *intf)
{
return 0;
}*/
/*
* housekeeping routines
*/
/* clean up all allocated buffers/structs */
static inline void surface_delete(struct usb_surface *surface)
{
input_free_polled_device(surface->input);
kfree(surface->bulk_in_buffer);
kfree(surface);
}
/* check candidate USB interface */
static int surface_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct usb_device *usbdev = interface_to_usbdev(interface);
struct usb_surface *surface;
struct usb_host_interface *iface_desc;
struct usb_endpoint_descriptor *endpoint;
struct input_polled_dev* poll_dev;
/* check if we really have the right interface */
iface_desc = &interface->altsetting[0];
if (iface_desc->desc.bInterfaceClass != 0xFF)
return -ENODEV;
/* allocate memory for our device state and initialize it */
surface = kzalloc(sizeof(struct usb_surface), GFP_KERNEL);
poll_dev = input_allocate_polled_device();
surface->input = poll_dev;
if (!surface || !poll_dev)
return -ENOMEM;
poll_dev->private = surface;
poll_dev->poll_interval = POLL_INTERVAL;
poll_dev->open = surface_open;
poll_dev->poll = surface_poll;
poll_dev->close = surface_close;
poll_dev->input->name = "Samsung SUR40";
usb_to_input_id(usbdev,&(poll_dev->input->id));
usb_make_path(usbdev, surface->phys, sizeof(surface->phys));
strlcat(surface->phys, "/input0", sizeof(surface->phys));
poll_dev->input->phys = surface->phys;
input_mt_init_slots(poll_dev->input, MAX_CONTACTS, INPUT_MT_DIRECT);
surface->usbdev = usbdev;
surface->interface = interface;
/* use endpoint #4 (0x86) */
endpoint = &iface_desc->endpoint[4].desc;
if (endpoint->bEndpointAddress == TOUCH_ENDPOINT) {
/* we found a bulk in endpoint */
surface->bulk_in_size = le16_to_cpu(endpoint->wMaxPacketSize);
surface->bulk_in_endpointAddr = endpoint->bEndpointAddress;
surface->bulk_in_buffer = kmalloc(2*surface->bulk_in_size, GFP_KERNEL);
if (!surface->bulk_in_buffer) {
pr_err("Unable to allocate input buffer.");
surface_delete(surface);
return -ENOMEM;
}
}
if (!(surface->bulk_in_endpointAddr)) {
pr_err("Unable to find bulk-in endpoint.");
surface_delete(surface);
return -ENODEV;
}
/* we can register the device now, as it is ready */
usb_set_intfdata(interface, surface);
if (input_register_polled_device(poll_dev)) {
pr_err("Unable to register polled input device.");
surface_delete(surface);
return -ENODEV;
}
dev_info(&interface->dev,"%s now attached\n",DRIVER_DESC);
return 0;
}
/* unregister device & clean up */
static void surface_disconnect(struct usb_interface *interface)
{
struct usb_surface *surface = usb_get_intfdata(interface);
input_unregister_polled_device(surface->input);
usb_set_intfdata(interface, NULL);
surface_delete(surface);
dev_info(&interface->dev, "%s now disconnected\n",DRIVER_DESC);
}
/* usb specific object needed to register this driver with the usb subsystem */
static struct usb_driver surface_driver = {
.name = DRIVER_SHORT,
.probe = surface_probe,
.disconnect = surface_disconnect,
/*.suspend = surface_suspend,
.resume = surface_resume,*/
.id_table = surface_table,
/*.supports_autosuspend = 1,*/
};
module_usb_driver(surface_driver);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]
next reply other threads:[~2013-09-05 14:15 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-09-05 14:15 Florian Echtler [this message]
2013-09-06 13:25 ` [RFC] surface-input Benjamin Tissoires
2013-09-09 8:25 ` Florian Echtler
2013-09-09 9:35 ` David Herrmann
2013-09-06 13:36 ` David Herrmann
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=52289209.2070707@butterbrot.org \
--to=floe@butterbrot.org \
--cc=linux-input@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).