As a Ruby developer exploring the lower level Linux world, I recently ventured into trying to control the lights on PlayStation 2 Buzz controllers (wired). I discovered a promising method in the Linux kernel source but hit a few brick walls. Here’s the journey, the mistakes, and how I improved the code and added more functionality for practice.
The Goal
Control the LEDs on PS2 Buzz controllers from a Linux system using C. The method I want to invoke is:
static void buzz_set_leds(struct sony_sc *sc)
Found in the Linux kernel source file:
/linux/drivers/hid/hid-sony.c (line 1512)
Initial (Fail) Attempt
I tried this C code:
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/leds.h>
#include <linux/power_supply.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/idr.h>
#include <linux/input/mt.h>
#include "hid-ids.h"
However, compiling it gave the error:
fatal error: linux/device.h: No such file or directory
Explanation of the Error
This error occurs because you’re trying to include Linux kernel headers in user-space C code. Kernel headers (like linux/device.h
) are only available and meaningful inside the Linux kernel or when building kernel modules not user-space applications.
Mistake: Including kernel-space headers in user-space code.
Correct Approach: Use user-space interfaces like libusb
, hidraw
, or udev
, instead of trying to directly compile kernel source code.
How to Actually Control Buzz LEDs
If buzz_set_leds()
is defined inside the kernel module (hid-sony.c
), and your device is recognized by the system, you should interact with it using user-space HID or USB libraries.
Using hidraw
in C to Send LED Command
Here’s a minimal practice program to write data to a hidraw
device:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <linux/hidraw.h>
#include <sys/ioctl.h>
int main() {
const char *device = "/dev/hidraw0"; // Adjust as needed
int fd = open(device, O_RDWR | O_NONBLOCK);
if (fd < 0) {
perror("Failed to open device");
return 1;
}
unsigned char buf[8];
memset(buf, 0x00, sizeof(buf));
// Construct an example LED command (hypothetical, may need adjustment)
buf[0] = 0x00; // Report ID
buf[1] = 0x01; // LED control code (depends on device)
buf[2] = 0x0F; // Turn on all LEDs
ssize_t res = write(fd, buf, sizeof(buf));
if (res < 0) {
perror("Write failed");
} else {
printf("Wrote %zd bytes to %s\n", res, device);
}
close(fd);
return 0;
}
More Practice Functionality
Add the following features to extend the program:
Detect HID Device Name
char name[256];
if (ioctl(fd, HIDIOCGRAWNAME(256), name) >= 0)
printf("Device name: %s\n", name);
Send Different LED Patterns
Loop through various LED configurations:
for (int i = 0; i < 16; ++i) {
buf[2] = i; // Pattern
write(fd, buf, sizeof(buf));
usleep(500000); // 0.5 seconds
}
Add CLI Input
Allow users to pass LED states from command line:
if (argc > 1)
buf[2] = (unsigned char)strtol(argv[1], NULL, 0);
Better Way to Work With Kernel Code
If you really want to call buzz_set_leds()
from C directly, you’d need to:
- Build your own custom kernel module
- Include the relevant source code (
sony_sc
,buzz_set_leds
) inside your module - Hook into the Buzz device properly via kernel APIs
However, this is strongly discouraged unless you’re developing drivers or customizing the kernel itself.
Conclusion
You don’t need to compile or include kernel source code to control your Buzz controller LEDs. Instead, use user-space interfaces like hidraw
or libusb
. That buzz_set_leds()
method is used internally by the kernel driver your interaction should go through /dev/hidrawX
.