// SPDX-License-Identifier: GPL-2.0-only /* * HID raw devices, giving access to raw HID events. * * In comparison to hiddev, this device does not process the * hid events at all (no parsing, no lookups). This lets applications * to work on raw hid events as they want to, and avoids a need to * use a transport-specific userspace libhid/libusb libraries. * * Copyright (c) 2007-2014 Jiri Kosina
*/
while (ret == 0) { if (list->head == list->tail) {
add_wait_queue(&list->hidraw->wait, &wait);
set_current_state(TASK_INTERRUPTIBLE);
while (list->head == list->tail) { if (signal_pending(current)) {
ret = -ERESTARTSYS; break;
} if (!list->hidraw->exist) {
ret = -EIO; break;
} if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN; break;
}
/* allow O_NONBLOCK to work well from other threads */
mutex_unlock(&list->read_mutex);
schedule();
mutex_lock(&list->read_mutex);
set_current_state(TASK_INTERRUPTIBLE);
}
/* * The first byte of the report buffer is expected to be a report number.
*/ static ssize_t hidraw_send_report(struct file *file, constchar __user *buffer, size_t count, unsignedchar report_type)
{ unsignedint minor = iminor(file_inode(file)); struct hid_device *dev;
__u8 *buf; int ret = 0;
lockdep_assert_held(&minors_rwsem);
if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
ret = -ENODEV; goto out;
}
dev = hidraw_table[minor]->hid;
if (count > HID_MAX_BUFFER_SIZE) {
hid_warn(dev, "pid %d passed too large report\n",
task_pid_nr(current));
ret = -EINVAL; goto out;
}
if (count < 2) {
hid_warn(dev, "pid %d passed too short report\n",
task_pid_nr(current));
ret = -EINVAL; goto out;
}
buf = memdup_user(buffer, count); if (IS_ERR(buf)) {
ret = PTR_ERR(buf); goto out;
}
if ((report_type == HID_OUTPUT_REPORT) &&
!(dev->quirks & HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP)) {
ret = __hid_hw_output_report(dev, buf, count, (u64)(long)file, false); /* * compatibility with old implementation of USB-HID and I2C-HID: * if the device does not support receiving output reports, * on an interrupt endpoint, fallback to SET_REPORT HID command.
*/ if (ret != -ENOSYS) goto out_free;
}
ret = __hid_hw_raw_request(dev, buf[0], buf, count, report_type,
HID_REQ_SET_REPORT, (u64)(long)file, false);
/* * This function performs a Get_Report transfer over the control endpoint * per section 7.2.1 of the HID specification, version 1.1. The first byte * of buffer is the report number to request, or 0x0 if the device does not * use numbered reports. The report_type parameter can be HID_FEATURE_REPORT * or HID_INPUT_REPORT.
*/ static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t count, unsignedchar report_type)
{ unsignedint minor = iminor(file_inode(file)); struct hid_device *dev;
__u8 *buf; int ret = 0, len; unsignedchar report_number;
lockdep_assert_held(&minors_rwsem);
if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
ret = -ENODEV; goto out;
}
dev = hidraw_table[minor]->hid;
if (!dev->ll_driver->raw_request) {
ret = -ENODEV; goto out;
}
if (count > HID_MAX_BUFFER_SIZE) {
hid_warn(dev, "pid %d passed too large report\n",
task_pid_nr(current));
ret = -EINVAL; goto out;
}
if (count < 2) {
hid_warn(dev, "pid %d passed too short report\n",
task_pid_nr(current));
ret = -EINVAL; goto out;
}
buf = kmalloc(count, GFP_KERNEL); if (!buf) {
ret = -ENOMEM; goto out;
}
/* * Read the first byte from the user. This is the report number, * which is passed to hid_hw_raw_request().
*/ if (copy_from_user(&report_number, buffer, 1)) {
ret = -EFAULT; goto out_free;
}
ret = __hid_hw_raw_request(dev, report_number, buf, count, report_type,
HID_REQ_GET_REPORT, (u64)(long)file, false);
if (ret < 0) goto out_free;
len = (ret < count) ? ret : count;
if (copy_to_user(buffer, buf, len)) {
ret = -EFAULT; goto out_free;
}
/* * Technically not writing to the hidraw_table but a write lock is * required to protect the device refcount. This is symmetrical to * hidraw_release().
*/
down_write(&minors_rwsem); if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
err = -ENODEV; goto out_unlock;
}
dev = hidraw_table[minor]; if (!dev->open++) {
err = hid_hw_power(dev->hid, PM_HINT_FULLON); if (err < 0) {
dev->open--; goto out_unlock;
}
return hidraw_revoke(list);
} default: /* * None of the above ioctls can return -EAGAIN, so * use it as a marker that we need to check variable * length ioctls.
*/ return -EAGAIN;
}
return 0;
}
staticlong hidraw_rw_variable_size_ioctl(struct file *file, struct hidraw *dev, unsignedint cmd, void __user *user_arg)
{ int len = _IOC_SIZE(cmd);
switch (cmd & ~IOCSIZE_MASK) { case HIDIOCSFEATURE(0): return hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); case HIDIOCGFEATURE(0): return hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); case HIDIOCSINPUT(0): return hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT); case HIDIOCGINPUT(0): return hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT); case HIDIOCSOUTPUT(0): return hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT); case HIDIOCGOUTPUT(0): return hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
}
return -EINVAL;
}
staticlong hidraw_ro_variable_size_ioctl(struct file *file, struct hidraw *dev, unsignedint cmd, void __user *user_arg)
{ struct hid_device *hid = dev->hid; int len = _IOC_SIZE(cmd); int field_len;
switch (cmd & ~IOCSIZE_MASK) { case HIDIOCGRAWNAME(0):
field_len = strlen(hid->name) + 1; if (len > field_len)
len = field_len; return copy_to_user(user_arg, hid->name, len) ? -EFAULT : len; case HIDIOCGRAWPHYS(0):
field_len = strlen(hid->phys) + 1; if (len > field_len)
len = field_len; return copy_to_user(user_arg, hid->phys, len) ? -EFAULT : len; case HIDIOCGRAWUNIQ(0):
field_len = strlen(hid->uniq) + 1; if (len > field_len)
len = field_len; return copy_to_user(user_arg, hid->uniq, len) ? -EFAULT : len;
}
down_read(&minors_rwsem);
dev = hidraw_table[minor]; if (!dev || !dev->exist || hidraw_is_revoked(list)) {
ret = -ENODEV; goto out;
}
if (_IOC_TYPE(cmd) != 'H') {
ret = -EINVAL; goto out;
}
if (_IOC_NR(cmd) > HIDIOCTL_LAST || _IOC_NR(cmd) == 0) {
ret = -ENOTTY; goto out;
}
ret = hidraw_fixed_size_ioctl(file, dev, cmd, user_arg); if (ret != -EAGAIN) goto out;
switch (_IOC_DIR(cmd)) { case (_IOC_READ | _IOC_WRITE):
ret = hidraw_rw_variable_size_ioctl(file, dev, cmd, user_arg); break; case _IOC_READ:
ret = hidraw_ro_variable_size_ioctl(file, dev, cmd, user_arg); break; default: /* Any other IOC_DIR is wrong */
ret = -EINVAL;
}
int __init hidraw_init(void)
{ int result;
dev_t dev_id;
result = alloc_chrdev_region(&dev_id, HIDRAW_FIRST_MINOR,
HIDRAW_MAX_DEVICES, "hidraw"); if (result < 0) {
pr_warn("can't get major number\n"); goto out;
}
hidraw_major = MAJOR(dev_id);
result = class_register(&hidraw_class); if (result) goto error_cdev;
cdev_init(&hidraw_cdev, &hidraw_ops);
result = cdev_add(&hidraw_cdev, dev_id, HIDRAW_MAX_DEVICES); if (result < 0) goto error_class;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.