Keybaords with arrays of RGB LEDs was Re: Future handling of complex RGB devices on Linux v2

Pavel Machek pavel at ucw.cz
Tue Jul 23 20:40:22 UTC 2024


Hi!

So... I got two gaming keyboards. One is totally unusable (and it
looks LEDs are not controllable from the host), and the second one is
.. HyperX Elite RGB. Needs 2 USB connections, very buggy, probably
needs repair, and I'd not recomend it to anyone. But that one seems to
be usable for RGB keyboard development.

(Unusable one is
https://www.trust.com/en/product/23651-gxt-835-azor-illuminated-gaming-keyboard
Usable one is
0951:16be Kingston Technology HyperX Alloy Elite RGB
)

First step is to create some kind of driver, I believe. And I did
that, userland version works quite good, and I started hacking kernel
one. Version is below, and help would be welcome. Especially if
someone knows how to do the probing right (it binds 3 times, log
below). What is worse, loading this driver kills keyboard
functionality -- input is no longer possible. Is there simple way to
keep that functionality?

Best regards,
								Pavel

[ 9880.950973] input: HyperX Alloy Elite RGB HyperX Alloy Elite RGB System Control as /devices/p
ci0000:00/0000:00:1d.0/usb1/1-1/1-1:1.2/0003:0951:16BE.0045/input/input216
[ 9881.009994] input: HyperX Alloy Elite RGB HyperX Alloy Elite RGB Consumer Control as /devices/pci0000:00/0000:00:1d.0/usb1/1-1/1-1:1.2/0003:0951:16BE.0045/input/input217
[ 9881.013758] input: HyperX Alloy Elite RGB HyperX Alloy Elite RGB Keyboard as /devices/pci0000:00/0000:00:1d.0/usb1/1-1/1-1:1.2/0003:0951:16BE.0045/input/input219
[ 9881.014528] hid-generic 0003:0951:16BE.0045: input,hiddev96,hidraw2: USB HID v1.11 Mouse [HyperX Alloy Elite RGB HyperX Alloy Elite RGB] on usb-0000:00:1d.0-1/input2
[ 9886.017646] input: HyperX Alloy Elite RGB HyperX Alloy Elite RGB as /devices/pci0000:00/0000:00:1d.0/usb1/1-1/1-1:1.0/0003:0951:16BE.0043/input/input221
[ 9886.218066] hx 0003:0951:16BE.0043: input,hidraw0: USB HID v1.11 Keyboard [HyperX Alloy Elite RGB HyperX Alloy Elite RGB] on usb-0000:00:1d.0-1/input0
[ 9886.218088] Have device.
...
[ 9899.399088] input: HyperX Alloy Elite RGB HyperX Alloy Elite RGB as /devices/pci0000:00/0000:00:1d.0/usb1/1-1/1-1:1.1/0003:0951:16BE.0044/input/input222
[ 9899.537173] hx 0003:0951:16BE.0044: input,hidraw1: USB HID v1.11 Keyboard [HyperX Alloy Elite RGB HyperX Alloy Elite RGB] on usb-0000:00:1d.0-1/input1
[ 9899.537194] Have device.
...
[ 9912.691800] input: HyperX Alloy Elite RGB HyperX Alloy Elite RGB as /devices/pci0000:00/0000:
00:1d.0/usb1/1-1/1-1:1.2/0003:0951:16BE.0045/input/input223
[ 9912.751478] hx 0003:0951:16BE.0045: input,hiddev96,hidraw2: USB HID v1.11 Mouse [HyperX Alloy
 Elite RGB HyperX Alloy Elite RGB] on usb-0000:00:1d.0-1/input2
[ 9912.751502] Have device.


// SPDX-License-Identifier: GPL-2.0-or-later
/*
 */

#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hid-roccat.h>
#include <linux/usb.h>

struct hx_device {};

static unsigned char keys[] = {
	0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
	0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x20, 0x21, 0x22,
	0x23, 0x24, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
	0x31, 0x32, 0x33, 0x34, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3E, 0x3F, 0x41,
	0x44, 0x45, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x51, 0x54, 0x55,
	0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5E, 0x5F, 0x61, 0x64, 0x65, 0x68, 0x69, 0x6A,
	0x6B, 0x6C, 0x6E, 0x6F, 0x74, 0x75, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E,
	0x7F, 0x81, 0x84, 0x85, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x91,
	0x94, 0x95 };

struct hid_device *one_hdev;

static int set_direct_color(struct hid_device *hdev, int color, int val)
{
	const int s = 264;
	unsigned char *buf = kmalloc(s, GFP_KERNEL);
	int i, ret;

	/* Zero out buffer */
	memset(buf, 0x00, s);

	/* Set up Direct packet */
	for (i = 0; i < sizeof(keys)/sizeof(keys[0]); i++) {
		buf[keys[i]] = val;
	}

	buf[0x00] = 0x07;
	buf[0x01] = 0x16; // HYPERX_ALLOY_ELITE_PACKET_ID_DIRECT
	buf[0x02] = color; // HYPERX_ALLOY_ELITE_COLOR_CHANNEL_GREEN
	buf[0x03] = 0xA0;

	ret = hid_hw_power(hdev, PM_HINT_FULLON);
	if (ret) {
		hid_err(hdev, "Failed to power on HID device\n");
		return ret;
	}

	// ioctl(3, HIDIOCSFEATURE(264), 0xbfce5974) = 264
	// -> hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
	//
	printk(KERN_INFO "Set feature report -- direct\n");
	i = hid_hw_raw_request(hdev, buf[0], buf, s, HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
	printk("raw: %d, einval %d, eagain %d\n", i, -EINVAL, -EAGAIN);
	msleep(100);
	return 0;
}

#define SIZE 128
const int real_size = SIZE;

static ssize_t hx_sysfs_read(struct file *fp, struct kobject *kobj,
			       struct bin_attribute * b,
			      char *buf, loff_t off, size_t count)
{
	struct device *dev = kobj_to_dev(kobj);
	struct hx_device *hx = hid_get_drvdata(dev_get_drvdata(dev));
	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
	int retval;

	if (off >= real_size)
		return 0;

	if (off != 0 || count != real_size)
		return -EINVAL;
	
	printk("read\n");
	set_direct_color(one_hdev, 2, 0xff);

	return retval ? retval : real_size;
}

static ssize_t hx_sysfs_write(struct file *fp, struct kobject *kobj,
			       struct bin_attribute * b,
		void const *buf, loff_t off, size_t count)
{
	struct device *dev = kobj_to_dev(kobj);
	struct hx_device *hx = hid_get_drvdata(dev_get_drvdata(dev));
	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
	int retval;

	if (off != 0 || count != real_size)
		return -EINVAL;

	printk("Write\n");

	return retval ? retval : real_size;
}

static struct bin_attribute hx_control_attr = { \
  .attr = { .name = "thingy", .mode = 0660 },		\
	.size = SIZE, \
	.read = hx_sysfs_read, \
};

static int hx_create_sysfs_attributes(struct usb_interface *intf)
{
  return sysfs_create_bin_file(&intf->dev.kobj, &hx_control_attr);
}

static void hx_remove_sysfs_attributes(struct usb_interface *intf)
{
  sysfs_remove_bin_file(&intf->dev.kobj, &hx_control_attr);
}

static int hx_init_hx_device_struct(struct usb_device *usb_dev,
		struct hx_device *hx)
{
	//mutex_init(&hx->hx_lock);
	return 0;
}

static int hx_init_specials(struct hid_device *hdev)
{
	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
	struct usb_device *usb_dev = interface_to_usbdev(intf);
	struct hx_device *hx;
	int retval;

	hx = kzalloc(sizeof(*hx), GFP_KERNEL);
	if (!hx) {
		hid_err(hdev, "can't alloc device descriptor\n");
		return -ENOMEM;
	}
	hid_set_drvdata(hdev, hx);

	retval = hx_create_sysfs_attributes(intf);
	if (retval) {
		hid_err(hdev, "cannot create sysfs files\n");
		goto exit;
	}

	return 0;
exit:
	kfree(hx);
	return retval;
}

static void hx_remove_specials(struct hid_device *hdev)
{
	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
	struct hx_device *hx;

	hx_remove_sysfs_attributes(intf);

	hx = hid_get_drvdata(hdev);
	kfree(hx);
}

static int num;

static int hx_probe(struct hid_device *hdev,
		const struct hid_device_id *id)
{
	int retval;

	if (!hid_is_usb(hdev))
		return -EINVAL;

	if (++num != 2)
		return -EINVAL;

	retval = hid_parse(hdev);
	if (retval) {
		hid_err(hdev, "parse failed\n");
		goto exit;
	}

	retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
	if (retval) {
		hid_err(hdev, "hw start failed\n");
		goto exit;
	}

	printk("Have device.\n");

	if (!hid_is_usb(hdev)) {
		printk("Not an usb?\n");
	}

	{
		struct usb_interface *interface = to_usb_interface(hdev->dev.parent);
		struct usb_device *dev = interface_to_usbdev(interface);
		struct usb_host_interface *iface_desc;
		struct usb_endpoint_descriptor *endpoint;
		char manufacturer[128];
		char product[128];

		// Retrieve manufacturer string
		retval = usb_string(dev, dev->descriptor.iManufacturer, manufacturer, sizeof(manufacturer));
		if (retval > 0)
			printk(KERN_INFO "Manufacturer: %s\n", manufacturer);
		else
			printk(KERN_ERR "Failed to get manufacturer string\n");

		// Retrieve product string
		retval = usb_string(dev, dev->descriptor.iProduct, product, sizeof(product));
		if (retval > 0)
			printk(KERN_INFO "Product: %s\n", product);
		else
			printk(KERN_ERR "Failed to get product string\n");

	}

	retval = hx_init_specials(hdev);
	if (retval) {
		hid_err(hdev, "couldn't install mouse\n");
		goto exit_stop;
	}

	// Example call to set_direct_color function
	for (int i=0; i<20; i++) {
		set_direct_color(hdev, 0x01, 0); // Example values
		set_direct_color(hdev, 0x02, 0); // Example values
		set_direct_color(hdev, 0x03, 0); // Example values
		set_direct_color(hdev, 0x01, 0xFF); // Example values
		set_direct_color(hdev, 0x02, 0xFF); // Example values
		set_direct_color(hdev, 0x03, 0xFF); // Example values
	}
	one_hdev = hdev;
	return 0;

exit_stop:
	hid_hw_stop(hdev);
exit:
	return retval;
}

static void hx_remove(struct hid_device *hdev)
{
	hx_remove_specials(hdev);
	hid_hw_stop(hdev);
}

static const struct hid_device_id hx_devices[] = {
	{ HID_USB_DEVICE(0x0951, 0x16be) },
	{ }
};

MODULE_DEVICE_TABLE(hid, hx_devices);

static struct hid_driver hx_driver = {
	.name = "hx",
	.id_table = hx_devices,
	.probe = hx_probe,
	.remove = hx_remove
};
module_hid_driver(hx_driver);

MODULE_AUTHOR("Pavel Machek");
MODULE_DESCRIPTION("USB HyperX elite backlight driver");
MODULE_LICENSE("GPL v2");


-- 
People of Russia, stop Putin before his war on Ukraine escalates.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 195 bytes
Desc: not available
URL: <https://lists.freedesktop.org/archives/dri-devel/attachments/20240723/75045dea/attachment.sig>


More information about the dri-devel mailing list