162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Roccat driver for Linux 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci/* 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci/* 1262306a36Sopenharmony_ci * Module roccat is a char device used to report special events of roccat 1362306a36Sopenharmony_ci * hardware to userland. These events include requests for on-screen-display of 1462306a36Sopenharmony_ci * profile or dpi settings or requests for execution of macro sequences that are 1562306a36Sopenharmony_ci * not stored in device. The information in these events depends on hid device 1662306a36Sopenharmony_ci * implementation and contains data that is not available in a single hid event 1762306a36Sopenharmony_ci * or else hidraw could have been used. 1862306a36Sopenharmony_ci * It is inspired by hidraw, but uses only one circular buffer for all readers. 1962306a36Sopenharmony_ci */ 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include <linux/cdev.h> 2462306a36Sopenharmony_ci#include <linux/poll.h> 2562306a36Sopenharmony_ci#include <linux/sched/signal.h> 2662306a36Sopenharmony_ci#include <linux/hid-roccat.h> 2762306a36Sopenharmony_ci#include <linux/module.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#define ROCCAT_FIRST_MINOR 0 3062306a36Sopenharmony_ci#define ROCCAT_MAX_DEVICES 8 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci/* should be a power of 2 for performance reason */ 3362306a36Sopenharmony_ci#define ROCCAT_CBUF_SIZE 16 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistruct roccat_report { 3662306a36Sopenharmony_ci uint8_t *value; 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistruct roccat_device { 4062306a36Sopenharmony_ci unsigned int minor; 4162306a36Sopenharmony_ci int report_size; 4262306a36Sopenharmony_ci int open; 4362306a36Sopenharmony_ci int exist; 4462306a36Sopenharmony_ci wait_queue_head_t wait; 4562306a36Sopenharmony_ci struct device *dev; 4662306a36Sopenharmony_ci struct hid_device *hid; 4762306a36Sopenharmony_ci struct list_head readers; 4862306a36Sopenharmony_ci /* protects modifications of readers list */ 4962306a36Sopenharmony_ci struct mutex readers_lock; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci /* 5262306a36Sopenharmony_ci * circular_buffer has one writer and multiple readers with their own 5362306a36Sopenharmony_ci * read pointers 5462306a36Sopenharmony_ci */ 5562306a36Sopenharmony_ci struct roccat_report cbuf[ROCCAT_CBUF_SIZE]; 5662306a36Sopenharmony_ci int cbuf_end; 5762306a36Sopenharmony_ci struct mutex cbuf_lock; 5862306a36Sopenharmony_ci}; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistruct roccat_reader { 6162306a36Sopenharmony_ci struct list_head node; 6262306a36Sopenharmony_ci struct roccat_device *device; 6362306a36Sopenharmony_ci int cbuf_start; 6462306a36Sopenharmony_ci}; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic int roccat_major; 6762306a36Sopenharmony_cistatic struct cdev roccat_cdev; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic struct roccat_device *devices[ROCCAT_MAX_DEVICES]; 7062306a36Sopenharmony_ci/* protects modifications of devices array */ 7162306a36Sopenharmony_cistatic DEFINE_MUTEX(devices_lock); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic ssize_t roccat_read(struct file *file, char __user *buffer, 7462306a36Sopenharmony_ci size_t count, loff_t *ppos) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci struct roccat_reader *reader = file->private_data; 7762306a36Sopenharmony_ci struct roccat_device *device = reader->device; 7862306a36Sopenharmony_ci struct roccat_report *report; 7962306a36Sopenharmony_ci ssize_t retval = 0, len; 8062306a36Sopenharmony_ci DECLARE_WAITQUEUE(wait, current); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci mutex_lock(&device->cbuf_lock); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci /* no data? */ 8562306a36Sopenharmony_ci if (reader->cbuf_start == device->cbuf_end) { 8662306a36Sopenharmony_ci add_wait_queue(&device->wait, &wait); 8762306a36Sopenharmony_ci set_current_state(TASK_INTERRUPTIBLE); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci /* wait for data */ 9062306a36Sopenharmony_ci while (reader->cbuf_start == device->cbuf_end) { 9162306a36Sopenharmony_ci if (file->f_flags & O_NONBLOCK) { 9262306a36Sopenharmony_ci retval = -EAGAIN; 9362306a36Sopenharmony_ci break; 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci if (signal_pending(current)) { 9662306a36Sopenharmony_ci retval = -ERESTARTSYS; 9762306a36Sopenharmony_ci break; 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci if (!device->exist) { 10062306a36Sopenharmony_ci retval = -EIO; 10162306a36Sopenharmony_ci break; 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci mutex_unlock(&device->cbuf_lock); 10562306a36Sopenharmony_ci schedule(); 10662306a36Sopenharmony_ci mutex_lock(&device->cbuf_lock); 10762306a36Sopenharmony_ci set_current_state(TASK_INTERRUPTIBLE); 10862306a36Sopenharmony_ci } 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci set_current_state(TASK_RUNNING); 11162306a36Sopenharmony_ci remove_wait_queue(&device->wait, &wait); 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci /* here we either have data or a reason to return if retval is set */ 11562306a36Sopenharmony_ci if (retval) 11662306a36Sopenharmony_ci goto exit_unlock; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci report = &device->cbuf[reader->cbuf_start]; 11962306a36Sopenharmony_ci /* 12062306a36Sopenharmony_ci * If report is larger than requested amount of data, rest of report 12162306a36Sopenharmony_ci * is lost! 12262306a36Sopenharmony_ci */ 12362306a36Sopenharmony_ci len = device->report_size > count ? count : device->report_size; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (copy_to_user(buffer, report->value, len)) { 12662306a36Sopenharmony_ci retval = -EFAULT; 12762306a36Sopenharmony_ci goto exit_unlock; 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci retval += len; 13062306a36Sopenharmony_ci reader->cbuf_start = (reader->cbuf_start + 1) % ROCCAT_CBUF_SIZE; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ciexit_unlock: 13362306a36Sopenharmony_ci mutex_unlock(&device->cbuf_lock); 13462306a36Sopenharmony_ci return retval; 13562306a36Sopenharmony_ci} 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_cistatic __poll_t roccat_poll(struct file *file, poll_table *wait) 13862306a36Sopenharmony_ci{ 13962306a36Sopenharmony_ci struct roccat_reader *reader = file->private_data; 14062306a36Sopenharmony_ci poll_wait(file, &reader->device->wait, wait); 14162306a36Sopenharmony_ci if (reader->cbuf_start != reader->device->cbuf_end) 14262306a36Sopenharmony_ci return EPOLLIN | EPOLLRDNORM; 14362306a36Sopenharmony_ci if (!reader->device->exist) 14462306a36Sopenharmony_ci return EPOLLERR | EPOLLHUP; 14562306a36Sopenharmony_ci return 0; 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cistatic int roccat_open(struct inode *inode, struct file *file) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci unsigned int minor = iminor(inode); 15162306a36Sopenharmony_ci struct roccat_reader *reader; 15262306a36Sopenharmony_ci struct roccat_device *device; 15362306a36Sopenharmony_ci int error = 0; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci reader = kzalloc(sizeof(struct roccat_reader), GFP_KERNEL); 15662306a36Sopenharmony_ci if (!reader) 15762306a36Sopenharmony_ci return -ENOMEM; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci mutex_lock(&devices_lock); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci device = devices[minor]; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci if (!device) { 16462306a36Sopenharmony_ci pr_emerg("roccat device with minor %d doesn't exist\n", minor); 16562306a36Sopenharmony_ci error = -ENODEV; 16662306a36Sopenharmony_ci goto exit_err_devices; 16762306a36Sopenharmony_ci } 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci mutex_lock(&device->readers_lock); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci if (!device->open++) { 17262306a36Sopenharmony_ci /* power on device on adding first reader */ 17362306a36Sopenharmony_ci error = hid_hw_power(device->hid, PM_HINT_FULLON); 17462306a36Sopenharmony_ci if (error < 0) { 17562306a36Sopenharmony_ci --device->open; 17662306a36Sopenharmony_ci goto exit_err_readers; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci error = hid_hw_open(device->hid); 18062306a36Sopenharmony_ci if (error < 0) { 18162306a36Sopenharmony_ci hid_hw_power(device->hid, PM_HINT_NORMAL); 18262306a36Sopenharmony_ci --device->open; 18362306a36Sopenharmony_ci goto exit_err_readers; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci reader->device = device; 18862306a36Sopenharmony_ci /* new reader doesn't get old events */ 18962306a36Sopenharmony_ci reader->cbuf_start = device->cbuf_end; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci list_add_tail(&reader->node, &device->readers); 19262306a36Sopenharmony_ci file->private_data = reader; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ciexit_err_readers: 19562306a36Sopenharmony_ci mutex_unlock(&device->readers_lock); 19662306a36Sopenharmony_ciexit_err_devices: 19762306a36Sopenharmony_ci mutex_unlock(&devices_lock); 19862306a36Sopenharmony_ci if (error) 19962306a36Sopenharmony_ci kfree(reader); 20062306a36Sopenharmony_ci return error; 20162306a36Sopenharmony_ci} 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_cistatic int roccat_release(struct inode *inode, struct file *file) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci unsigned int minor = iminor(inode); 20662306a36Sopenharmony_ci struct roccat_reader *reader = file->private_data; 20762306a36Sopenharmony_ci struct roccat_device *device; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci mutex_lock(&devices_lock); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci device = devices[minor]; 21262306a36Sopenharmony_ci if (!device) { 21362306a36Sopenharmony_ci mutex_unlock(&devices_lock); 21462306a36Sopenharmony_ci pr_emerg("roccat device with minor %d doesn't exist\n", minor); 21562306a36Sopenharmony_ci return -ENODEV; 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci mutex_lock(&device->readers_lock); 21962306a36Sopenharmony_ci list_del(&reader->node); 22062306a36Sopenharmony_ci mutex_unlock(&device->readers_lock); 22162306a36Sopenharmony_ci kfree(reader); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci if (!--device->open) { 22462306a36Sopenharmony_ci /* removing last reader */ 22562306a36Sopenharmony_ci if (device->exist) { 22662306a36Sopenharmony_ci hid_hw_power(device->hid, PM_HINT_NORMAL); 22762306a36Sopenharmony_ci hid_hw_close(device->hid); 22862306a36Sopenharmony_ci } else { 22962306a36Sopenharmony_ci kfree(device); 23062306a36Sopenharmony_ci } 23162306a36Sopenharmony_ci } 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci mutex_unlock(&devices_lock); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci return 0; 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci/* 23962306a36Sopenharmony_ci * roccat_report_event() - output data to readers 24062306a36Sopenharmony_ci * @minor: minor device number returned by roccat_connect() 24162306a36Sopenharmony_ci * @data: pointer to data 24262306a36Sopenharmony_ci * 24362306a36Sopenharmony_ci * Return value is zero on success, a negative error code on failure. 24462306a36Sopenharmony_ci * 24562306a36Sopenharmony_ci * This is called from interrupt handler. 24662306a36Sopenharmony_ci */ 24762306a36Sopenharmony_ciint roccat_report_event(int minor, u8 const *data) 24862306a36Sopenharmony_ci{ 24962306a36Sopenharmony_ci struct roccat_device *device; 25062306a36Sopenharmony_ci struct roccat_reader *reader; 25162306a36Sopenharmony_ci struct roccat_report *report; 25262306a36Sopenharmony_ci uint8_t *new_value; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci device = devices[minor]; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci new_value = kmemdup(data, device->report_size, GFP_ATOMIC); 25762306a36Sopenharmony_ci if (!new_value) 25862306a36Sopenharmony_ci return -ENOMEM; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci mutex_lock(&device->cbuf_lock); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci report = &device->cbuf[device->cbuf_end]; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci /* passing NULL is safe */ 26562306a36Sopenharmony_ci kfree(report->value); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci report->value = new_value; 26862306a36Sopenharmony_ci device->cbuf_end = (device->cbuf_end + 1) % ROCCAT_CBUF_SIZE; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci list_for_each_entry(reader, &device->readers, node) { 27162306a36Sopenharmony_ci /* 27262306a36Sopenharmony_ci * As we already inserted one element, the buffer can't be 27362306a36Sopenharmony_ci * empty. If start and end are equal, buffer is full and we 27462306a36Sopenharmony_ci * increase start, so that slow reader misses one event, but 27562306a36Sopenharmony_ci * gets the newer ones in the right order. 27662306a36Sopenharmony_ci */ 27762306a36Sopenharmony_ci if (reader->cbuf_start == device->cbuf_end) 27862306a36Sopenharmony_ci reader->cbuf_start = (reader->cbuf_start + 1) % ROCCAT_CBUF_SIZE; 27962306a36Sopenharmony_ci } 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci mutex_unlock(&device->cbuf_lock); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci wake_up_interruptible(&device->wait); 28462306a36Sopenharmony_ci return 0; 28562306a36Sopenharmony_ci} 28662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(roccat_report_event); 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci/* 28962306a36Sopenharmony_ci * roccat_connect() - create a char device for special event output 29062306a36Sopenharmony_ci * @class: the class thats used to create the device. Meant to hold device 29162306a36Sopenharmony_ci * specific sysfs attributes. 29262306a36Sopenharmony_ci * @hid: the hid device the char device should be connected to. 29362306a36Sopenharmony_ci * @report_size: size of reports 29462306a36Sopenharmony_ci * 29562306a36Sopenharmony_ci * Return value is minor device number in Range [0, ROCCAT_MAX_DEVICES] on 29662306a36Sopenharmony_ci * success, a negative error code on failure. 29762306a36Sopenharmony_ci */ 29862306a36Sopenharmony_ciint roccat_connect(const struct class *klass, struct hid_device *hid, int report_size) 29962306a36Sopenharmony_ci{ 30062306a36Sopenharmony_ci unsigned int minor; 30162306a36Sopenharmony_ci struct roccat_device *device; 30262306a36Sopenharmony_ci int temp; 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci device = kzalloc(sizeof(struct roccat_device), GFP_KERNEL); 30562306a36Sopenharmony_ci if (!device) 30662306a36Sopenharmony_ci return -ENOMEM; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci mutex_lock(&devices_lock); 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci for (minor = 0; minor < ROCCAT_MAX_DEVICES; ++minor) { 31162306a36Sopenharmony_ci if (devices[minor]) 31262306a36Sopenharmony_ci continue; 31362306a36Sopenharmony_ci break; 31462306a36Sopenharmony_ci } 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci if (minor < ROCCAT_MAX_DEVICES) { 31762306a36Sopenharmony_ci devices[minor] = device; 31862306a36Sopenharmony_ci } else { 31962306a36Sopenharmony_ci mutex_unlock(&devices_lock); 32062306a36Sopenharmony_ci kfree(device); 32162306a36Sopenharmony_ci return -EINVAL; 32262306a36Sopenharmony_ci } 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci device->dev = device_create(klass, &hid->dev, 32562306a36Sopenharmony_ci MKDEV(roccat_major, minor), NULL, 32662306a36Sopenharmony_ci "%s%s%d", "roccat", hid->driver->name, minor); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci if (IS_ERR(device->dev)) { 32962306a36Sopenharmony_ci devices[minor] = NULL; 33062306a36Sopenharmony_ci mutex_unlock(&devices_lock); 33162306a36Sopenharmony_ci temp = PTR_ERR(device->dev); 33262306a36Sopenharmony_ci kfree(device); 33362306a36Sopenharmony_ci return temp; 33462306a36Sopenharmony_ci } 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci mutex_unlock(&devices_lock); 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci init_waitqueue_head(&device->wait); 33962306a36Sopenharmony_ci INIT_LIST_HEAD(&device->readers); 34062306a36Sopenharmony_ci mutex_init(&device->readers_lock); 34162306a36Sopenharmony_ci mutex_init(&device->cbuf_lock); 34262306a36Sopenharmony_ci device->minor = minor; 34362306a36Sopenharmony_ci device->hid = hid; 34462306a36Sopenharmony_ci device->exist = 1; 34562306a36Sopenharmony_ci device->cbuf_end = 0; 34662306a36Sopenharmony_ci device->report_size = report_size; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci return minor; 34962306a36Sopenharmony_ci} 35062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(roccat_connect); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci/* roccat_disconnect() - remove char device from hid device 35362306a36Sopenharmony_ci * @minor: the minor device number returned by roccat_connect() 35462306a36Sopenharmony_ci */ 35562306a36Sopenharmony_civoid roccat_disconnect(int minor) 35662306a36Sopenharmony_ci{ 35762306a36Sopenharmony_ci struct roccat_device *device; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci mutex_lock(&devices_lock); 36062306a36Sopenharmony_ci device = devices[minor]; 36162306a36Sopenharmony_ci mutex_unlock(&devices_lock); 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci device->exist = 0; /* TODO exist maybe not needed */ 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci device_destroy(device->dev->class, MKDEV(roccat_major, minor)); 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci mutex_lock(&devices_lock); 36862306a36Sopenharmony_ci devices[minor] = NULL; 36962306a36Sopenharmony_ci mutex_unlock(&devices_lock); 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci if (device->open) { 37262306a36Sopenharmony_ci hid_hw_close(device->hid); 37362306a36Sopenharmony_ci wake_up_interruptible(&device->wait); 37462306a36Sopenharmony_ci } else { 37562306a36Sopenharmony_ci kfree(device); 37662306a36Sopenharmony_ci } 37762306a36Sopenharmony_ci} 37862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(roccat_disconnect); 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_cistatic long roccat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 38162306a36Sopenharmony_ci{ 38262306a36Sopenharmony_ci struct inode *inode = file_inode(file); 38362306a36Sopenharmony_ci struct roccat_device *device; 38462306a36Sopenharmony_ci unsigned int minor = iminor(inode); 38562306a36Sopenharmony_ci long retval = 0; 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci mutex_lock(&devices_lock); 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci device = devices[minor]; 39062306a36Sopenharmony_ci if (!device) { 39162306a36Sopenharmony_ci retval = -ENODEV; 39262306a36Sopenharmony_ci goto out; 39362306a36Sopenharmony_ci } 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci switch (cmd) { 39662306a36Sopenharmony_ci case ROCCATIOCGREPSIZE: 39762306a36Sopenharmony_ci if (put_user(device->report_size, (int __user *)arg)) 39862306a36Sopenharmony_ci retval = -EFAULT; 39962306a36Sopenharmony_ci break; 40062306a36Sopenharmony_ci default: 40162306a36Sopenharmony_ci retval = -ENOTTY; 40262306a36Sopenharmony_ci } 40362306a36Sopenharmony_ciout: 40462306a36Sopenharmony_ci mutex_unlock(&devices_lock); 40562306a36Sopenharmony_ci return retval; 40662306a36Sopenharmony_ci} 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_cistatic const struct file_operations roccat_ops = { 40962306a36Sopenharmony_ci .owner = THIS_MODULE, 41062306a36Sopenharmony_ci .read = roccat_read, 41162306a36Sopenharmony_ci .poll = roccat_poll, 41262306a36Sopenharmony_ci .open = roccat_open, 41362306a36Sopenharmony_ci .release = roccat_release, 41462306a36Sopenharmony_ci .llseek = noop_llseek, 41562306a36Sopenharmony_ci .unlocked_ioctl = roccat_ioctl, 41662306a36Sopenharmony_ci}; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_cistatic int __init roccat_init(void) 41962306a36Sopenharmony_ci{ 42062306a36Sopenharmony_ci int retval; 42162306a36Sopenharmony_ci dev_t dev_id; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci retval = alloc_chrdev_region(&dev_id, ROCCAT_FIRST_MINOR, 42462306a36Sopenharmony_ci ROCCAT_MAX_DEVICES, "roccat"); 42562306a36Sopenharmony_ci if (retval < 0) { 42662306a36Sopenharmony_ci pr_warn("can't get major number\n"); 42762306a36Sopenharmony_ci goto error; 42862306a36Sopenharmony_ci } 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci roccat_major = MAJOR(dev_id); 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci cdev_init(&roccat_cdev, &roccat_ops); 43362306a36Sopenharmony_ci retval = cdev_add(&roccat_cdev, dev_id, ROCCAT_MAX_DEVICES); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci if (retval < 0) { 43662306a36Sopenharmony_ci pr_warn("cannot add cdev\n"); 43762306a36Sopenharmony_ci goto cleanup_alloc_chrdev_region; 43862306a36Sopenharmony_ci } 43962306a36Sopenharmony_ci return 0; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci cleanup_alloc_chrdev_region: 44362306a36Sopenharmony_ci unregister_chrdev_region(dev_id, ROCCAT_MAX_DEVICES); 44462306a36Sopenharmony_ci error: 44562306a36Sopenharmony_ci return retval; 44662306a36Sopenharmony_ci} 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_cistatic void __exit roccat_exit(void) 44962306a36Sopenharmony_ci{ 45062306a36Sopenharmony_ci dev_t dev_id = MKDEV(roccat_major, 0); 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci cdev_del(&roccat_cdev); 45362306a36Sopenharmony_ci unregister_chrdev_region(dev_id, ROCCAT_MAX_DEVICES); 45462306a36Sopenharmony_ci} 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_cimodule_init(roccat_init); 45762306a36Sopenharmony_cimodule_exit(roccat_exit); 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ciMODULE_AUTHOR("Stefan Achatz"); 46062306a36Sopenharmony_ciMODULE_DESCRIPTION("USB Roccat char device"); 46162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 462