드디어 마지막이다. xbox 패드를 마우스처럼 사용하고 싶어 이렇게 동작하도록 수정했다. 처음 연결하면 조이스틱으로 동작하고, 가운데 버튼을 누르면 마우스로 동작하도록 했다. 리눅스에서 조이스틱 쓸 일이 있나?? 초기값을 마우스로 바꿨어야 했다.
input_device에 name을 설정하지 않고 등록하면 xserver로 이벤트를 전달할 수 없다. input_device에 name을 설정하지 않으면 evtest에 이름이 표시되지 않는다.
xbox 스틱으로 들어오는 값을 그대로 사용하면 값이 너무 커서 커서가 화면 끝에서 끝까지 점프한다. 대략 2,000으로 나눴다. 바꿔 말하면 큰 값을 짧은 시간에 한번에 전달하기 위해 조이스틱을 사용한다.
버튼을 누르면 rising edge, falling edge 두 번 동작한다. 비트 시프트로 특정 값을 만족하면 모드가 바뀌도록 했다. 인터럽트 등록할 때 rising, falling edge 필터링 할 수 있는데, urb로 등록하면 그런 게 없는 듯 하다. 짱구를 많이 굴렸다.
xpad.c 드라이버를 따라 한 수준이지만 커스터 마이징을 해 보면서 무엇을 모르고 무엇을 학습했는지 알 수 있었다. 책만 보고서는 절대 이해하지 못했을 지식이다. 사용되는 함수들은 소스 코드를 보지 않는 한 절대 사용하지 못할 함수 들이다. 이미 내가 고민했던 내용을 누군가 해결하여 코드로 구현했다. 소스코드 검색을 자주 해야 시간을 줄일 수 있다.
xpad.c에 보면 사용할 struct에 관심있는 주소를 모두 때려 넣었다. 구조가 복잡하고, 주소가 너무 많아 북마크처럼 주소를 관리해야 한다. 커널에서 내려오면 항상 메모리를 해제할 수 있도록 했다. 보면 볼수록 container_of 매크로로 위력을 실감한다. 부분을 알면 구조체 멤버에 무제한으로 접근할 수 있다. 와!! 이거 생각해낸 사람이 정말 대단하다.
usb로 들어오는 버튼 입력을 테스트 했다. LED를 켜는 출력과 비슷하게 진행된다. 역시 출력을 보는 작업이 제일 어려웠다.
입력 디바이스 할당: input_allocate_device();
urb 할당
usb_alloc_coherent
usb_alloc_urb
인터럽트로 urb 설정: usb_rcvintpipe, usb_fill_int_urb
input device 주소 변경?: input_set_drvdata
open, close 함수 오버로드
input_dev->open = xpad_open
input_dev->close = xpad_close
usb_submit_urb call back 함수 설정
input_dev->close에 usb_kill_urb로 urb를 해제하지 않으면 다시 입력 디이스를 열었을 때 사용할 수 없다.
xpad.c 드라이버는 인터럽트 실행될 때 완료 함수를 등록했다. 그 함수 안에있는 work queue가 input_report_key, input_sync로 입력 키를 이벤트로 OS에 전달한다.
버튼이 눌렸을 경우 이벤트가 0, 1 값으로 전달되어야 한다. urb로 들어오는 data 패킷 중 2번, 3번 주소에 mask를 씌워 EV_KEY 값 상태를 확인한다.
인터럽트 발생 확인.
urb data 값 수신.
해당하는 주소에 mask를 씌워 특정 이벤트를 OS에 전달.
리눅스 시스템 내에서 사용할 수 있는 이벤트가 이미 정해져 있어 사용자 맘대로 설정할 수 없다. 그 말은 조이스틱 이동을 마우스 이동 키로 할당하면 마우스 대신 조이스틱을 사용할 수 있단 말이다. 조이스틱을 눌렀을 때 어느 데이터가 켜지는지는 기기마다 다르기 때문에 몇 번 테스트를 해야 한다.
고유한 번호를 부여하기 위하여 define_ida, ida_simple_get, ida_simple_remove 형식으로 사용한다.
//사용할 구조체 선언.
struct usb_xpad{
struct input_dev *dev; /* input device interface */
...
int pad_nr; // order
};
...
static DEFINE_IDA(xpad_pad_seq);
static int xpad_led_probe(struct usb_xpad *xpad)
{
//ida 할당
//pad_nr을 계속 증가..
//led가 몇번에 연결되어 있는지 알 수 있음.
//0~0x8,000,000 - 1까지 증가
xpad->pad_nr = ida_simple_get(&xpad_pad_seq, 0, 0, GFP_KERNEL);
if (xpad->pad_nr < 0){
retval = xpad->pad_nr;
goto free_mem;
}
free_mem:
kfree(led);
return retval;
}
xbox 360 패드가 4개 램프를 가지고 있다. 플레이어에 따라 램프를 달리 켜줘야 하는데, xpad->pad_nr을 ida 번호를 부여하여 4로 나눈 나머지에 2를 더했다. 5번째 패드를 붙여도 불을 것 같지만 xpad_led5이런 식으로 추가될 듯 하다. 플레이어 1과 같은 램프가 켜질 듯 하다.
메모리가 부족하면 ida를 할당하지 못한다. xpad.c를 보면 다시 해제 하도록 했다.
led+숫자로 등록하여 usb_unregister로 led 디바이스를 해제하면 된다. 이제 xbox 컨트롤러를 입력으로 사용하는 방법으로 넘어가도 된다.
xbox 360 컨트롤러가 4개 LED를 가지고 있다. 총 4명 플레이어가 있을 경우 1 ~ 4까지 시계 방향으로 램프를 켜주게 된다. usb -> input 버튼 입력을 받아 들이기 전 간단해 보는 LED 제어를 해보기로 했다. 하..어렵다. 입력 받아들이는 것 보다 더 어려운 듯 하다.
cdev_led_register를 실행하면 /sys/class/led에 led 모듈이 등록된다.
pi@raspberrypi:/sys/class/leds $ dmesg | tail -20
[ 7044.723682] leds xpad_led: Setting an LED's brightness failed (-524)
[ 7044.723824] disconnected
[ 7344.482279] xpad is 56bd80be, xpad->udev is 7ea0c81d
[ 7344.482296] interface is 49be3431
[ 7344.482318] xbox360 1-1.1:1.0: interrupt in, out found. 2cedb0f6, 62b37858
[ 7344.482579] xbox360 1-1.1:1.0: usb xbox360 driver was registerd
[ 7344.482861] xpad is db7e959d, xpad->udev is 7ea0c81d
[ 7344.482875] interface is c0605ae3
[ 7344.482893] xbox360 1-1.1:1.1: interrupt in, out found. 6497d636, 4bf83f77
[ 7344.483394] xbox360 1-1.1:1.1: usb xbox360 driver was registerd
[ 7344.483545] usb 1-1.1: Led xpad_led renamed to xpad_led_1 due to name collision
[ 7344.483699] xpad is 2c597e01, xpad->udev is 7ea0c81d
[ 7344.483713] interface is dfef8df2
[ 7344.483730] xbox360 1-1.1:1.2: Could not find both interrupt-in and interrpt-out endpoints
[ 7344.483743] error -6
[ 7344.483824] xpad is 2c597e01, xpad->udev is 7ea0c81d
[ 7344.483837] interface is f1417f63
[ 7344.483852] xbox360 1-1.1:1.3: Could not find both interrupt-in and interrpt-out endpoints
[ 7344.483865] error -6
[ 7344.483985] usbcore: registered new interface driver xbox360
pi@raspberrypi:/sys/class/leds $ ls -tl
total 0
lrwxrwxrwx 1 root root 0 Jan 4 23:52 xpad_led -> ../../devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/leds/xpad_led
lrwxrwxrwx 1 root root 0 Jan 4 23:52 xpad_led_1 -> ../../devices/platform/scb/fd500000.pcie/pci0000
xbox 컨트롤러가 총 4개를 등록할 수 있는데, 인터럽트로 2개를 등록할 수 있다. 나머지 2개는 무엇인지 모르겠다. 암튼 xpad.c는 2번만 등록했다.
CDEV_LED로 등록을 하면 brightness_set 함수를 overload 할 수 있다. brightness_set -> xpad_led_set -> xpad_send_led_command으로 연결한다. xpad_send_led_command 안에서 LED 제어하는 패킷을 만들어 URB에 넣어 준다. urb->transfer_buffer_length를 잘 넣어줘야 URB 패킷을 전송한다.
인터럽트로 한번 등록하면 일정 주기로 계속 명령을 실행한다.
[ 340.057841] xpad is c97d9e68, xpad->udev is decb56d0
[ 340.057858] interface is d89e5572
[ 340.057871] cur_altsetting is 2
[ 340.057893] xbox360 1-1.1:1.0: interrupt in, out found. 00000000, 3b46194c
[ 340.058231] xbox360 1-1.1:1.0: usb xbox360 driver was registerd
[ 340.058253] dma output address 6ce62de4 with size 64, point to f2c4e403 was allocated
[ 340.058266] actual address is 6ce62de4
[ 340.058280] urb was allocated
[ 340.058292] xpad->endpoint_out->bInterval is 8
[ 340.058306] XPAD_OUT_LED_IDX: 2
[ 340.058318] XPAD_NUM_OUT_PACKETS: 3
[ 340.058440] xpad->led_command->data[0]:1,[1]:3,[2]:10, with 3 len
[ 340.058463] xpad_send_led_command: completed
[ 340.058828] usbcore: registered new interface driver xbox360
[ 359.127170] usbcore: deregistering interface driver xbox360
[ 359.127225] xpad address is c97d9e68, intf is d89e5572
[ 359.129187] xpad->led_command->data[0]:1,[1]:3,[2]:0, with 3 len
[ 359.129217] xbox360 1-1.1:1.0: xpad_send_led_command - usb_submit_urb failed with -ENOENT
[ 359.131553] xpad led was unregistered
[ 359.131575] dma output address 6ce62de4 with size 64 was freed
[ 359.131590] disconnected
집에서 놀고 있던 8년 전에 구매한 xbox 360 유선 usb 패드를 raspberry pi4에 등록하여 사용하고 싶다. 나온 지 오래되어 이미 xpad 리눅스 드라이버가 공개되어 있다. 그러나 usb, input을 한번에 사용하여 잘 이해되지 않는다. 남은 출장 기간 중 천천히 공부 하기로 했다.
usb를 등록하려면 vendor id, product id, 연결되면 실행되는 call back probe, disconnect 등 을 알아야 한다. 인터넷에 공개된 좋은 자료, 소스를 공부했다. 관련 코드는 여기에 저장했다.
usb-devices로 xbox 패드를 확인하면 (I가 interface 인듯하다) 0, 1, 2, 3인터페이스가 등록되지 않았다.