/*==========================================================================
* lsadrv-main.c : Linux kernel driver for eIT-Xiroku optical touch sensor
*
* Copyright (C) 2009 eIT Co., Ltd. and Xiroku Inc.
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
============================================================================*/
#include
#ifdef MODVERSIONS
#include
#endif
#include /* for linux kernel */
#include /* for kmalloc */
#include "fakemouse.h"
#include /* for linux kernel module */
#include /* for use of /proc */
#include /* for input devide */
#include /* for request_module */
#include /* for single_open */
#include /* for USBDEVFS_HUB_PORTINFO */
#include
#include
#include "lsadrv.h"
#include "lsadrv-ioctl.h"
/******** USB device ********/
/* hotplug device table support */
static struct usb_device_id lsadrv_device_table [] = {
{ USB_DEVICE(0x0611, 0x0009) }, /* eIT-TOTOKU touch sensor */
{ USB_DEVICE(0x1477, 0x0001) }, /* eIT-Xiroku touch sensor */
{ USB_DEVICE(0x1477, 0x0002) }, /* eIT-Xiroku small touch sensor */
{ USB_DEVICE(0x1477, 0x0003) }, /* eIT-Xiroku touch sensor 100K/250K */
{ USB_DEVICE(0x1477, 0x0004) }, /* eIT-Xiroku touch sensor NL */
{ USB_DEVICE(0x1477, 0x0005) }, /* eIT-Xiroku touch sensor NL9638 */
{ USB_DEVICE(0x1477, 0x0006) }, /* eIT-Xiroku touch sensor NLM001 */
{ USB_DEVICE(0x1477, 0x0007) }, /* eIT-Xiroku touch sensor HT5000 */
{ USB_DEVICE(0x1477, 0x0008) }, /* eIT-Xiroku touch sensor HTM131 */
{ }
};
MODULE_DEVICE_TABLE(usb, lsadrv_device_table);
static int usb_lsadrv_probe(struct usb_interface *intf, const struct usb_device_id *id);
static void usb_lsadrv_disconnect(struct usb_interface *intf);
static int usb_lsadrv_ioctl(struct usb_interface *intf, unsigned int cmd, void *arg);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36)
static struct usb_driver lsadrv_driver =
{
//#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)
// owner: THIS_MODULE,
//#endif
.name = "lsadrv",
.id_table = lsadrv_device_table,
.probe = usb_lsadrv_probe, /* probe() */
.disconnect = usb_lsadrv_disconnect, /* disconnect() */
.unlocked_ioctl = usb_lsadrv_ioctl /* through usbdevfs (devio) driver */
};
#else
static struct usb_driver lsadrv_driver =
{
.name = "lsadrv",
.id_table = lsadrv_device_table,
.probe = usb_lsadrv_probe, /* probe() */
.disconnect = usb_lsadrv_disconnect, /* disconnect() */
.unlocked_ioctl = usb_lsadrv_ioctl /* through usbdevfs (devio) driver */
};
#endif
static LIST_HEAD(device_list);
static LIST_HEAD(memleak_list);
static struct semaphore device_list_lock;
/******** input device ********/
static int lsadrv_input_open(struct input_dev *idev);
static void lsadrv_input_close(struct input_dev *idev);
struct lsadrv_input_dev *lsadrv_idev;
/******** global/static variables ********/
int lsadrv_trace = 0;
/***************************************************************************/
/* Private functions */
static inline void
usb_to_input_id(const struct usb_device *dev, struct input_id *id)
{
id->bustype = BUS_USB;
id->vendor = le16_to_cpu(dev->descriptor.idVendor);
id->product = le16_to_cpu(dev->descriptor.idProduct);
id->version = le16_to_cpu(dev->descriptor.bcdDevice);
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 10)
static struct input_dev *
input_allocate_device(void)
{
struct input_dev *idev = kmalloc(sizeof(struct input_dev), GFP_KERNEL);
if (idev) {
memset(idev, 0, sizeof(struct input_dev));
init_input_dev(idev);
}
return idev;
}
static inline void input_free_device(struct input_dev *dev)
{
kfree(dev);
}
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 22)
static inline void *input_get_drvdata(struct input_dev *dev)
{
return dev->private;
}
static inline void input_set_drvdata(struct input_dev *dev, void *data)
{
dev->private = data;
}
#endif
/***************************************************************************/
/* input subsystem functions */
/* called from event handler when input device file (eventNN) is opened */
static int lsadrv_input_open(struct input_dev *idev)
{
int rc = 0;
struct lsadrv_input_dev *xidev = lsadrv_idev;
Trace(LSADRV_TRACE_OPEN, ">> input_open: ptr=0x%p\n", idev);
if (idev == NULL) {
BUG();
}
if (xidev->idev != idev) {
BUG();
}
/* just check open count */
/* (never called after unregistered from input subsystem) */
if (xidev->open++) {
rc = -EBUSY; /* though nobody seems to use this return code... */
}
Trace(LSADRV_TRACE_OPEN, "<< input_open: count=%d\n", xidev->open);
return rc;
}
/* called from event handler when input device file (eventNN) is closed */
static void lsadrv_input_close(struct input_dev *idev)
{
struct lsadrv_input_dev *xidev = lsadrv_idev;
Trace(LSADRV_TRACE_OPEN, ">> input_close: ptr=0x%p\n", idev);
if (idev == NULL) {
BUG();
}
if (xidev->idev != idev) {
BUG();
}
if (--xidev->open < 0) {
Warning("input_close: closed too many times? : count=%d\n", xidev->open);
}
Trace(LSADRV_TRACE_OPEN, "<< input_close: count=%d\n", xidev->open);
}
static void lsadrv_set_key_bits(struct input_dev *idev)
{
const int *list;
int n, i;
n = lsadrv_get_key_list(&list);
for (i = 0; i < n; ++i) {
set_bit(list[i], idev->keybit);
}
}
static int fill_input_dev(struct lsadrv_input_dev *xidev)
{
struct input_dev *idev;
memset(xidev, 0, sizeof(*xidev));
idev = input_allocate_device();
if (idev == NULL) {
return -1;
}
xidev->idev = idev;
input_set_drvdata(idev, xidev);
/* idev->int number; //set in input_register_device() */
idev->name = "lsadrv";
#ifdef PREVENT_MOUSE_DRIVER_MATCH
idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
idev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
idev->keybit[BIT_WORD(BTN_DIGI)] = BIT_MASK(BTN_TOOL_PEN);
#else //!PREVENT_MOUSE_DRIVER_MATCH
idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);// | BIT_MASK(EV_MSC);// | BIT_MASK(EV_REL);
// idev->relbit[BIT_WORD(REL_X)] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
idev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
idev->keybit[BIT_WORD(BTN_DIGI)] = BIT_MASK(BTN_TOOL_PEN) | BIT_MASK(BTN_TOUCH);
//idev->mscbit[0] = BIT_MASK(MSC_SERIAL);
#endif //!PREVENT_MOUSE_DRIVER_MATCH
lsadrv_set_key_bits(idev);
input_set_abs_params(idev, ABS_X, 0, 65535, 0, 0);
input_set_abs_params(idev, ABS_Y, 0, 65535, 0, 0);
input_set_abs_params(idev, ABS_PRESSURE, 0, 1023, 0, 0);
/* operations */
idev->open = lsadrv_input_open;
idev->close = lsadrv_input_close;
return 0;
}
/***************************************************************************
*
* USB functions
*
***************************************************************************/
/* This function gets called when a new device is plugged in or the usb driver is loaded. */
static int usb_lsadrv_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
struct lsadrv_device *xdev = NULL;
Trace(LSADRV_TRACE_PROBE, ">> probe: VID/PID=%04X/%04X\n", udev->descriptor.idVendor, udev->descriptor.idProduct);
/* Check if we can handle this device */
/* -> omit --- filtered by device table */
/* Allocate lsadrv_device structure */
xdev = kmalloc(sizeof(struct lsadrv_device), GFP_KERNEL);
if (xdev == NULL) {
Err("could not allocate memory for lsadrv_device\n");
return -ENOMEM;
}
memset(xdev, 0, sizeof(struct lsadrv_device));
xdev->udev = udev;
lsadrv_spin_lock_init(&xdev->streamLock);
sema_init(&xdev->modlock, 1);
init_waitqueue_head(&xdev->remove_ok);
/* set ids as input device */
if (usb_make_path(xdev->udev, lsadrv_idev->phys_path, sizeof(lsadrv_idev->phys_path)) > 0) {
lsadrv_idev->idev->phys = lsadrv_idev->phys_path;
}
usb_to_input_id(udev, &lsadrv_idev->idev->id);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 10)) && (LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24))
lsadrv_idev->idev->cdev.dev = &intf->dev;
#endif
/* Add it to the device list */
down(&device_list_lock);
list_add(&xdev->device_list, &device_list);
up(&device_list_lock);
Trace(LSADRV_TRACE_PROBE, "<< probe: returning 0x%p\n", xdev);
usb_set_intfdata(intf, xdev);
return 0;
}
/* Usb device is unplugged or driver is shutting down ... */
static void usb_lsadrv_disconnect(struct usb_interface *intf)
{
DECLARE_WAITQUEUE(wait, current);
struct lsadrv_device *xdev;
int usage;
xdev = (struct lsadrv_device *) usb_get_intfdata(intf);
usb_set_intfdata(intf, NULL);
Trace(LSADRV_TRACE_PROBE, ">> disconnect: xdev=0x%p\n", xdev);
if (xdev == NULL) {
Err("lsadrv_disconnect() Called without private pointer.\n");
return;
}
if (xdev->udev != interface_to_usbdev(intf)) {
Err("lsadrv_disconnect(): pointer mismatch.\n");
return;
}
/* remove from the device list */
down(&device_list_lock);
list_del(&xdev->device_list);
up(&device_list_lock);
if (xdev->udev == NULL) {
Err("disconnect: already called for %p\n", xdev);
return;
}
xdev->unplugged = 1;
lsadrv_stop_iso_stream(xdev);
/* wait for i/o in progress done */
add_wait_queue(&xdev->remove_ok, &wait);
set_current_state(TASK_INTERRUPTIBLE);
while ((usage=xdev->usage)) {
Trace(LSADRV_TRACE_PROBE, "waiting io: usage=%d\n", usage);
schedule();
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&xdev->remove_ok, &wait);
lsadrv_spin_lock_term(xdev->streamLock);
/* free memory */
Trace(LSADRV_TRACE_PROBE, "disconnect: cleaning up memories.\n");
if (lsadrv_idev) {
input_set_drvdata(lsadrv_idev->idev, NULL);
lsadrv_idev->idev->phys = NULL;
lsadrv_idev->idev->id.vendor = 0;
lsadrv_idev->idev->id.product = 0;
lsadrv_idev->idev->id.version = 0;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 10)) && (LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24))
lsadrv_idev->idev->cdev.dev = NULL;
#endif
}
kfree(xdev);
Trace(LSADRV_TRACE_PROBE, "<< disconnect\n");
}
/* usb ioctl - called through devio. So, copy from/to user is done outside */
static int usb_lsadrv_ioctl(struct usb_interface *intf, unsigned int cmd, void *arg)
{
struct usb_device *udev = interface_to_usbdev(intf);
struct lsadrv_device *xdev = (struct lsadrv_device *) usb_get_intfdata(intf);
int ret = 0;
Trace(LSADRV_TRACE_IOCTL, ">>lsadrv_ioctl(%d) size=%d\n", _IOC_NR(cmd), _IOC_SIZE(cmd));
if (arg == NULL && _IOC_DIR(cmd) != _IOC_NONE && _IOC_SIZE(cmd) != 0) {
Err("ioctl: arg is NULL: cmd=0x%x\n", cmd);
ret = -EINVAL;
goto l_ret;
}
if (udev == NULL) {
Err("ioctl: udev is NULL\n");
ret = -EFAULT;
goto l_ret;
}
if (xdev == NULL) {
Err("ioctl: xdev is NULL\n");
ret = -EFAULT;
goto l_ret;
}
/* this IOCTL may be sent often... */
if (cmd == USBDEVFS_HUB_PORTINFO) {
/* just returns error because this is not a hub */
ret = -EINVAL;
goto l_ret;
}
lsadrv_modlock(xdev);
xdev->usage++;
lsadrv_modunlock(xdev);
ret = lsadrv_usb_ioctl(xdev, cmd, arg);
lsadrv_modlock(xdev);
xdev->usage--;
lsadrv_modunlock(xdev);
/* wake up the waiting threads */
wake_up_interruptible(&xdev->remove_ok);
l_ret:
Trace(LSADRV_TRACE_IOCTL, "<next;
seq_printf(m, "%03d/%03d\n", xdev->udev->bus->busnum, xdev->udev->devnum);
}
up(&device_list_lock);
return 0;
}
/*** open operation handler for 'devices' file ***/
static int lsadrv_devices_open(struct inode *inode, struct file *file)
{
/* should use seq_open() but this should be sufficient for the need */
return single_open(file, lsadrv_devices_show, NULL);
}
/*** remove procfs files and directory ***/
static void lsadrv_remove_procfs_dir(void)
{
struct lsadrv_proc_files *files = &lsadrv_files;
if (files->devicesFileEntry) {
remove_proc_entry("devices", files->lsadrvDirEntry);
files->devicesFileEntry = NULL;
}
if (files->lsadrvDirEntry) {
/* remove_proc_entry("lsadrv", proc_root_driver);*/
remove_proc_entry("driver/lsadrv", NULL);
files->lsadrvDirEntry = NULL;
}
}
/*** create procfs directory ***/
static void lsadrv_create_procfs_dir(void)
{
struct lsadrv_proc_files *files = &lsadrv_files;
static struct file_operations proc_fops = {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31)
.owner = THIS_MODULE,
#endif
.open = lsadrv_devices_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
/* Make procfs/driver/lsadrv directory */
/* files->lsadrvDirEntry = create_proc_entry("lsadrv", S_IFDIR, proc_root_driver);*/
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
files->lsadrvDirEntry = create_proc_entry("driver/lsadrv", S_IFDIR, NULL);
#else
files->lsadrvDirEntry = proc_mkdir("driver/lsadrv", NULL);
#endif
if (files->lsadrvDirEntry == NULL) {
return;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31)
files->lsadrvDirEntry->owner = THIS_MODULE;
#endif
/* procfs/driver/lsadrv/devices file */
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
files->devicesFileEntry = create_proc_entry("devices",
S_IFREG|S_IRUGO,
files->lsadrvDirEntry);
if (files->devicesFileEntry == NULL) {
lsadrv_remove_procfs_dir();
return;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31)
files->devicesFileEntry->owner = THIS_MODULE;
#endif
files->devicesFileEntry->data = NULL;
files->devicesFileEntry->proc_fops = &proc_fops;
#else
files->devicesFileEntry = proc_create("devices",
S_IFREG|S_IRUGO,
files->lsadrvDirEntry,
&proc_fops
);
if (files->devicesFileEntry == NULL) {
lsadrv_remove_procfs_dir();
return;
}
#endif
}
/***************************************************************************
*
* Initialization code & module stuff
*
***************************************************************************/
static int trace = -1;
module_param(trace, int, 0644);
MODULE_PARM_DESC(trace, "For debugging purposes");
MODULE_DESCRIPTION("lsadrv touch sensor driver");
MODULE_AUTHOR("eIT Co. Ltd. & Xiroku Inc.");
MODULE_LICENSE("GPL");
static int __init usb_lsadrv_init(void)
{
struct lsadrv_input_dev *xidev;
int err;
Info("lsadrv touch sensor driver version " LSADRV_KDRIVER_VERSION " loaded.\n");
/*** driver options ***/
/* trace mode */
if (trace >= 0) {
Info("Trace options: 0x%04x\n", trace);
lsadrv_trace = trace;
}
Debug("init_Mutex\n");
sema_init(&device_list_lock, 1);
/*** create procfs directory and 'devices' file ***/
Debug("creating procfs\n");
lsadrv_create_procfs_dir();
/*** load 'evdev' module if not loaded yet ***/
Debug("request_module\n");
request_module("evdev");
/* Allocate input_dev structure */
Debug("allocating input_dev\n");
xidev = kmalloc(sizeof(struct lsadrv_input_dev), GFP_KERNEL);
if (xidev == NULL) {
Err("could not allocate memory for lsadrv_input_dev.\n");
lsadrv_remove_procfs_dir();
return -ENOMEM;
}
if (fill_input_dev(xidev)) {
Err("could not allocate memory for input device.\n");
kfree(xidev);
lsadrv_remove_procfs_dir();
return -ENOMEM;
}
lsadrv_idev = xidev;
/* Register to input subsystem */
Debug("registering input device\n");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 15)
err = input_register_device(xidev->idev);
#else
input_register_device(xidev->idev);
err = 0;
#endif
if (err) {
Err("could not register input device.\n");
input_free_device(xidev->idev);
lsadrv_idev = NULL;
kfree(xidev);
lsadrv_remove_procfs_dir();
return err;
}
Trace(LSADRV_TRACE_MODULE, "Registered input struct at 0x%p.\n", xidev->idev);
Info("Registered input device\n"/*, xidev->idev.number*/);
Trace(LSADRV_TRACE_MODULE, "Registering driver at address 0x%p.\n", &lsadrv_driver);
err = usb_register(&lsadrv_driver);
if (err) {
/*Err("failed to register usb device.\n");*/
input_unregister_device(xidev->idev);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 10)
input_free_device(xidev->idev);
#endif
lsadrv_idev = NULL;
kfree(xidev);
lsadrv_remove_procfs_dir();
}
if (!err) create_fakemouse();
return err;
}
static void __exit usb_lsadrv_exit(void)
{
struct list_head *tmp;
/* unregister usb device */
Trace(LSADRV_TRACE_MODULE, "Deregistering usb driver.\n");
usb_deregister(&lsadrv_driver);
/* unregister input devide */
if (lsadrv_idev) {
Trace(LSADRV_TRACE_MODULE, "Unregistering input device.\n");
input_unregister_device(lsadrv_idev->idev);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 10)
input_free_device(lsadrv_idev->idev);
#endif
kfree(lsadrv_idev);
lsadrv_idev = NULL;
}
/*** remove procfs files and directory ***/
Debug("removing procfs dir\n");
lsadrv_remove_procfs_dir();
/*** free remained memories ***/
tmp = memleak_list.next;
if (tmp != &memleak_list) {
Debug("freeing remained memories\n");
while (tmp != &memleak_list) {
struct lsadrv_device *xdev = list_entry(tmp, struct lsadrv_device, device_list);
tmp = tmp->next;
Trace(LSADRV_TRACE_MODULE, "cleaning up memories:0x%p.\n", xdev);
list_del(&xdev->device_list);
kfree(xdev);
}
}
// destroy
destroy_fakemouse();
Info("lsadrv driver removed.\n");
}
module_init(usb_lsadrv_init);
module_exit(usb_lsadrv_exit);