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