Note: This blog is from my college days back in 2013. But I think it’s still pretty interesting stuff.
UPDATE May 2018: Thanks to additional work/cleanup by Johannes Lundber this work is now in the FreeBSD codebase.
From revision 333633 this driver is now part of FreeBSD base.
https://svnweb.freebsd.org/base?view=revision&revision=333633
The device will show up as /dev/uhid_snes when plugged in.
I have a DragonRise USB gamepad and of course it doesn’t work for FreeBSD. On Linux you have to customize the kernel to get it to run. I have a Logitech USB gamepad that works fine but where’s the fun in that? I’m going to try to do this but first I need to read… a lot. I’m going to purchase the following book: http://nostarch.com/bsddrivers
I’ve heard nothing but good things about it so this should be fun. ALL progress will be noted here.
This is the gamepad: http://www.amazon.com/Classic-USB-Super-Nintendo-Controller/dp/B002JAU20W/ref=sr_1_3?ie=UTF8&qid=1375088417&sr=8-3&keywords=snes+usb
Driver: https://github.com/cwithmichael/dragon_rise_driver
Day 1
I ordered the book and did the first exercise. I’ll post the exercise code and try to explain it(to myself basically).
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
static int
hello_modevent(module_t mod __unused, int event, void *arg __unused)
{
int error = 0;
switch(event){
case MOD_LOAD:
uprintf("Hello, world!\n");
break;
case MOD_UNLOAD:
uprintf("Good-bye, cruel world!\n");
break;
default:
error = EOPNOTSUPP;
break;
} return(error);
}
static moduledata_t hello_mod={"hello",hello_modevent, NULL};
DECLARE_MODULE(hello, hello_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
Basically every FreeBSD kernel module has two things:
- Module event handler
- DECLARE_MODULE macro call
The module event handler handles stuff like module initialization(whenever the module is loaded into the kernel) and shutdown(when it’s unloaded). The prototype is just a function that takes a module struct, event enum and a void pointer for any data if needed.
The DECLARE_MODULE
macro call is used to register the module and its associated event handler with the system. The prototype is (name , moduledata struct, kernel subsystem(usually drivers), order(order of initalization within the subsystem))
The moduledata struct has:
- The official module name
- The module’s event handler
- Pointer to private data
So in the code above:
static moduledata_t hello_mod={"hello",hello_modevent, NULL};
The official module name would be ‘hello’ and the module event will be the ‘hello_modevent’ function we defined in the file. There wasn’t any private data so we just pass NULL.
The code above just creates a driver that prints out stuff to the screen whenever the module is loaded and unloaded. It’s simple but definitely helpful for grasping some of these concepts.
Day 2
Seems like this code will be a big help: http://lxr.linux.no/#linux+v3.7.7/drivers/hid/hid-dr.c
Device Info using lsusb -v:
ugen3.2: <product 0x0011 vendor 0x0079> at usbus3, cfg=0 md=HOST spd=LOW (1.5Mbps) pwr=ON
bLength = 0x0012
bDescriptorType = 0x0001
bcdUSB = 0x0100
bDeviceClass = 0x0000
bDeviceSubClass = 0x0000
bDeviceProtocol = 0x0000
bMaxPacketSize0 = 0x0008
idVendor = 0x0079
idProduct = 0x0011
bcdDevice = 0x0106
iManufacturer = 0x0000 <no string>
iProduct = 0x0002 <retrieving string failed>
iSerialNumber = 0x0000 <no string>
bNumConfigurations = 0x0001
Bus /dev/usb Device /dev/ugen3.2: ID 0079:0011 DragonRise Inc.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x0079 DragonRise Inc.
idProduct 0x0011
bcdDevice 1.06
iManufacturer 0
iProduct 2 USB Gamepad
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 100mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.10
bCountryCode 33 US
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 101
Report Descriptor: (length is 101)
Item(Global): Usage Page, data= [ 0x01 ] 1
Generic Desktop Controls
Item(Local ): Usage, data= [ 0x04 ] 4
Joystick
Item(Main ): Collection, data= [ 0x01 ] 1
Application
Item(Main ): Collection, data= [ 0x02 ] 2
Logical
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x05 ] 5
Item(Global): Logical Minimum, data= [ 0x00 ] 0
Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
Item(Global): Physical Minimum, data= [ 0x00 ] 0
Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255
Item(Local ): Usage, data= [ 0x30 ] 48
Direction-X
Item(Local ): Usage, data= [ 0x30 ] 48
Direction-X
Item(Local ): Usage, data= [ 0x30 ] 48
Direction-X
Item(Local ): Usage, data= [ 0x30 ] 48
Direction-X
Item(Local ): Usage, data= [ 0x31 ] 49
Direction-Y
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Global): Report Size, data= [ 0x04 ] 4
Item(Global): Report Count, data= [ 0x01 ] 1
Item(Global): Logical Maximum, data= [ 0x07 ] 7
Item(Global): Physical Maximum, data= [ 0x3b 0x01 ] 315
Item(Global): Unit, data= [ 0x14 ] 20
System: English Rotation, Unit: Degrees
Item(Local ): Usage, data= [ 0x00 ] 0
Undefined
Item(Main ): Input, data= [ 0x42 ] 66
Data Variable Absolute No_Wrap Linear
Preferred_State Null_State Non_Volatile Bitfield
Item(Global): Unit, data= [ 0x00 ] 0
System: None, Unit: (None)
Item(Global): Report Size, data= [ 0x01 ] 1
Item(Global): Report Count, data= [ 0x0a ] 10
Item(Global): Logical Maximum, data= [ 0x01 ] 1
Item(Global): Physical Maximum, data= [ 0x01 ] 1
Item(Global): Usage Page, data= [ 0x09 ] 9
Buttons
Item(Local ): Usage Minimum, data= [ 0x01 ] 1
Button 1 (Primary)
Item(Local ): Usage Maximum, data= [ 0x0a ] 10
(null)
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Global): Usage Page, data= [ 0x00 0xff ] 65280
(null)
Item(Global): Report Size, data= [ 0x01 ] 1
Item(Global): Report Count, data= [ 0x0a ] 10
Item(Global): Logical Maximum, data= [ 0x01 ] 1
Item(Global): Physical Maximum, data= [ 0x01 ] 1
Item(Local ): Usage, data= [ 0x01 ] 1
(null)
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Main ): End Collection, data=none
Item(Main ): Collection, data= [ 0x02 ] 2
Logical
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x04 ] 4
Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255
Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
Item(Local ): Usage, data= [ 0x02 ] 2
(null)
Item(Main ): Output, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Main ): End Collection, data=none
Item(Main ): End Collection, data=none
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 10
Device Status: 0x0000
(Bus Powered)
Day 3
Okay the driver is now able to be dynamically loaded. I didn’t bring my controller to school with me, so I’ll definitely start testing it tonight. The output below shows that the module is indeed loaded (id 17).
I might be going about this the wrong way though. I think I should be focusing more on the hid code in the kernel source.
If you look at this code: http://svnweb.freebsd.org/base/head/sys/dev/usb/input/usb_rdesc.h?view=markup&pathrev=190581
It seems that I need to add a struct with my gamepad’s info.
I added the DragonRise product info to:
usbdevs
Example of this file here: http://bintree.net/freebsd/dc/dca/usbdevs_8h_source.html
And I added the u8 pid0011_rdesc_fixed[]
info from here:
http://lxr.linux.no/#linux+v3.7.7/drivers/hid/hid-dr.c
I added this info to my usb_rdesc.h
file in /sys/dev/usb/input
Screenshot of that:
And I added the relevant code to the uhid_attach
method in the uhid.c
file which is in the same directory.
I recompiled the usb modules and the compilation was successful! I will definitely test this when I get home.
Hopefully this will WORK!
Day 4
Unfortunately loading my driver and plugging in the SNES controller causes my kernel to panic.
It’s a start I guess.
Relevant error:
panic: make_dev_credv: bad si_name (error=17, si_name=snes_usb0)
Okay so I did a bit of debugging and changed the attach function and my kernel doesn’t panic anymore!
Here’s the output of the snes_usb driver recognizing my device when I plug it in:
Sample output from usblyzer:
After reading this: http://www.stealth-x.com/programming/driver-writing-with-python.php
And after mashing buttons during the sniff like a crazy person I noticed a pattern with the 6th bit
For the most part all I get is this sequence in repetition:
01 7F 7F 7F 7F 0F 00 00
01 7F 7F 7F 7F 0F 00 00
01 7F 7F 7F 7F 0F 00 00
But whenever various buttons are pressed some of the bytes change
i.e.:
01 7F 7F 7F 7F 2F 00 00
01 7F 7F 00 FF FF 01 00
01 7F 7F FF 00 0F 00 00
The first three bytes and the last byte never seem to change. I emailed myself a copy of the data(in html format) before I left for the day.
I’m installing XP right now on my other laptop so I can run the test again. The data I gathered earlier that told me explicitly if buttons were being pressed is at school on one of the lab workstations :sad:.
Direction Pad Data
Right:
01 7f 7f ff 7f 0f 00 00
Left:
01 7f 7f 00 ff 0f 00 00
Up:
01 7f 7f 7f 00 0f 00 00
Down:
01 7f 7f 7f ff 0f 00 00
X button(pressed):
01 7f 7f 7f 7f 1f 00 00
Y button(pressed):
01 7f 7f 7f 7f 8f 00 00
A button(pressed): `01 7f 7f 7f 7f 2f 00 00
B button(pressed):
01 7f 7f 7f 7f 4f 00 00
Select button(pressed):
01 7f 7f 7f 7f 0f 10 00
Start button(pressed):
01 7f 7f 7f 7f 0f 20 00
Left trigger(pressed):
01 7f 7f 7f 7f 0f 01 00
Right trigger(pressed): I lost the piece to it whenever I was mucking around trying to see if I could get info from the board. I hope not too many SNES games use the right trigger. 😅
And funny enough I got this info from the original libusb program I wrote. Remember how the output was just the same thing over and over? That’s because I wasn’t pressing any buttons :face_palm:. I ran the list program and started pressing buttons and got the data.
Day 5
It’s responding now. 😃 Those /’s are me holding down the ‘A’ button, the 0’s are me holding down the ‘B’ button. For some reason it only seems to be responding to the B and A buttons and the Right and Down d-pad buttons.
Output:
The main issue now is figuring out how to get the raw data from the transfer buffer. Once I can find that hopefully I can just use something like a switch statement to bitwise AND it with the commands I found previously.
The issue now I believe is how the data is being read.
In the snes_usb_read_callback
function whenever I try to transfer the data from kernel to userland I get an ERROR 14
.
Error 14 is EFAULT: BAD MEMORY ADDRESS
e.g.
erro = usbd_copy_out_user(pc, 0, ¤t_status, 1);
And apparently that function calls the kernel function copyout: http://www.unix.com/man-page/FreeBSD/9/copyout/
The error either has to be coming from the usb_page_cache
I’m passing in or the ptr variable.
Day 6
After some debugging I’m getting actual data now when I press buttons:
I improved the output format and increased the buffer size to 128. There’s a pattern but it’s weird. I can’t make heads or tails of it at the moment.
I’m on my windows laptop right now but the pattern (32 bytes) is
x x x x x x x x x x x x x x x x y y x x x x x x x x x x x x x x x
x x x x x x x x x x x x x x x x y y x x x x x x x x x x x x x x x
So I guess I’ll try something like
if(status[16] == whatever && status [17] == whatever) youve pressed B
After some tinkering I have it responding to button presses with the correct data now! The same results I got earlier from libusb and the usb sniffer from Windows.
http://forums.freebsd.org/showthread.php?t=31111
This site has given me some insight.
I think I have to implement the ioctl
function because whenever I call usbhidctl
on my ‘snes driver device file’ it prints out the single statement I have located in the ioctl
function.
If I make a symbolic links of /dev/snes_usb0
to /dev/uhid0
then the emulator recognizes it but it calls my ioctl function and then closes the device.
Output:
Day 7
Okay it’s kind of working now but I can’t choose a button in the emulator.
Output:
Hmm now some buttons are working but others aren’t
sigh Let me keep hacking away on it.
I’ve darn near completely rewritten it so it’ll be more like the uhid driver. Now it’s back to kernel panics and debugging.
Day 8
Finally got it working! It turns out in the read back status I had a while loop where I shouldn’t have. I’m playing a game right now on ZSNES 😃.
You can see the updated code on the github page. https://github.com/lispandtrees/dragon_rise_driver