내 라즈베리 파이에 usb 타입 xbox360 패드를 붙여 보고싶다. 리눅스 커널 xpad.c에 마이크로소프트 거의 모든 제품 usb vendor, product id가 등록되어 있다. xpad.c가 조금 복작하여 usb 인터럽트 사용 방법을 잘 이해할 수 없다. 키 입력 설정을 어떻게 할지는 나중 문제고… usb-skeletion.c 문서 역시 어렵다. 여기는 인터럽트 대신 대용량 데이터 전송을 목표로 했다.
그 중 가장 비슷한 usb 마우스 드라이버를 커널에서 찾았다. 커널이 성경과 같아 모든 물음에 대한 답을 한다. 200줄 약간 넘어가는 코드로 정말 인터럽트 핵심만 작성했다.
대략 구성은 아래와 같이 된다.
- usb 구조체 선언.
- 사용가능한 usb 번호 찾음, 등록.
- 입력 설정.
- 인터럽트 설정.
usb_fill_int_urb로 call 함수를 등록하여 인터럽트를 사용하면 된다. usb 인터럽트가 일정 주기로 발생한다. 계속 실행하기 위해 인터럽트로 실행할 함수 안에 usb_submit_urb를 넣는다. 알고보면 참 쉬운데 처음 이해하기 어렵다. xpad.c도 같은 방식이다. 처음에 왜 안보였는지…아직 키 입력을 설정하지 않았고, 커널에 test를 출력하도록 했다.
usb 구조체는 여러 정보를 쉽게 사용하려 군더더기를 많이 붙이는 듯 하다.
#include <linux/kernel.h> #include <linux/input.h> #include <linux/module.h> #include <linux/usb/input.h> #include <linux/init.h> #include <linux/device.h> #include <linux/usb/input.h> #include <linux/slab.h> #define USB_XBOX_MINOR_BASE 1 //구조체 선언. struct usb_xpad{ struct input_dev *dev; /* input device interface */ struct usb_device *udev; /* usb device */ struct usb_interface *intf; /* usb interface */ __u8 irq_in_endpointAddr; /* interrupt in address*/ __u8 irq_out_endpointAddr; /* interrupt out address*/ struct usb_endpoint_descriptor *endpoint_in, *endpoint_out; signed char *data; /* input data */ struct urb *irq_in; /* urb for interrupt in report*/ struct work_struct work; /* init/remove device from callback */ }; static struct usb_xpad *myPad; static int xpad_init_input(struct usb_xpad *xpad); static void xpad_deinit_input(struct usb_xpad *xpad); static void usb_xpad_irq(struct urb *urb){ pr_info("test\n"); usb_submit_urb(urb, GFP_KERNEL); } static int xpad_init_urb(struct usb_interface *intf, struct usb_xpad *xpad); static ssize_t xbox_read(struct file *file, char *buffer, size_t count, loff_t *ppos) { pr_info("device was read.\n"); return 0; } static ssize_t xbox_write(struct file *file, const char *user_buffer, size_t count, loff_t *ppos) { return 0; } static int xbox_open(struct inode *inode, struct file *file) { int retval; struct urb *ptr_urb; ptr_urb = usb_alloc_urb(0, GFP_KERNEL); pr_info("device was opened.\n"); if (!ptr_urb) { retval = -ENOMEM; goto error; } return 0; error: if(ptr_urb) { usb_free_urb(ptr_urb); } return retval; } static int xbox_release(struct inode *inode, struct file *file) { pr_info("xbox was released\n"); return 0; } static int xbox_flush(struct file *file, fl_owner_t id) { return 0; } static const struct file_operations xboxtest_fops = { .owner = THIS_MODULE, .read = xbox_read, .write = xbox_write, .open = xbox_open, .release = xbox_release, .flush = xbox_flush, .llseek = noop_llseek, }; /* * usb class driver info in order to get a minor number from the usb core, * and to have the device registered with the driver core */ static struct usb_class_driver xbox_class = { .name = "xbox%d", .fops = &xboxtest_fops, .minor_base = USB_XBOX_MINOR_BASE, }; static struct usb_driver xpad_test_driver; static const struct usb_device_id xpad_table[] = { //{ USB_INTERFACE_INFO('X', 'B', 0) }, /* X-Box USB-IF not approved class */ //XPAD_XBOX360_VENDOR(0x045e), /* Microsoft X-Box 360 controllers */ {USB_DEVICE(0x045e, 0x028e)}, { } }; static const signed short xpad_common_btn[] = { BTN_A, BTN_B, BTN_X, BTN_Y, /* "analog" buttons */ BTN_START, BTN_SELECT, BTN_THUMBL, BTN_THUMBR, /* start/back/sticks */ -1 /* terminating entry */ }; MODULE_DEVICE_TABLE(usb, xpad_table); static int xpad_init_input(struct usb_xpad *xpad) { struct input_dev *input_dev; int i, error; int pipe, maxp; input_dev = input_allocate_device(); if (!input_dev) return -ENOMEM; xpad->dev = input_dev; usb_to_input_id(xpad->udev, &input_dev->id); input_set_drvdata(input_dev, xpad); /* set up standard buttons */ for (i = 0; xpad_common_btn[i] >= 0; i++) input_set_capability(input_dev, EV_KEY, xpad_common_btn[i]); pipe = usb_rcvintpipe(xpad->udev, xpad->irq_in_endpointAddr); maxp = usb_maxpacket(xpad->udev, pipe, usb_pipeout(pipe)); usb_fill_int_urb(xpad->irq_in, xpad->udev, pipe, xpad->data, (maxp > 8 ? 8 : maxp), usb_xpad_irq, xpad, xpad->endpoint_in->bInterval); error = input_register_device(input_dev); if (error) goto err_free_dev; pr_info("usb input was registered\n"); usb_submit_urb(xpad->irq_in, GFP_KERNEL); return 0; //return ok; err_free_dev: input_free_device(input_dev); return error; } static void xpad_deinit_input(struct usb_xpad *xpad) { pr_info("xpad is %p, ->dev is %p.\n",xpad, xpad->dev); if(xpad->dev) input_unregister_device(xpad->dev); } static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_xpad *xpad; struct usb_device *udev; struct usb_endpoint_descriptor *ep_irq_in,*ep_irq_out; struct usb_host_interface *intf_tmp; int retval; udev = interface_to_usbdev(intf); xpad = kzalloc(sizeof(struct usb_xpad), GFP_KERNEL); if (!xpad) return -ENOMEM; //초기화 //kzallocd으로 0으로 초기화 //xpad->odata_serial = 0; xpad->udev = udev; xpad->intf = intf; myPad = xpad; pr_info("xpad is %p, myPad is %p\n", xpad->intf, myPad->intf); pr_info("interface is %p\n", intf); //if (intf->cur_altsetting->desc.bNumEndpoints != 2) // return -ENODEV; intf_tmp = intf->cur_altsetting; // find common usb endpoint helper 사용 //https://lkml.org/lkml/2020/9/21/1239 /* set up the endpoint information */ /* use only the first bulk-in and bulk-out endpoints */ retval = usb_find_common_endpoints(intf_tmp, NULL, NULL, &ep_irq_in, &ep_irq_out); if (retval) { dev_err(&intf->dev, "Could not find both irq-in and irq-out endpoints\n"); goto error; } xpad->irq_in_endpointAddr = ep_irq_in->bEndpointAddress; xpad->irq_out_endpointAddr = ep_irq_out->bEndpointAddress; xpad->endpoint_in = ep_irq_in; xpad->endpoint_out = ep_irq_out; xpad->irq_in = usb_alloc_urb(0, GFP_KERNEL); if (!xpad->irq_in) { retval = -ENOMEM; goto err_free_in_urb; } usb_set_intfdata(intf, xpad); pr_info("[info]: found interrupt end point, in: %d, out: %d", ep_irq_in->bEndpointAddress, ep_irq_out->bEndpointAddress); pr_info("probe \n"); //pr_info("%04X:%04X pluged \n", id->idVendor, id->idProduct); retval = usb_register_dev(intf, &xbox_class); if (retval< 0) { pr_err("usb_register failed for the "__FILE__ "driver." "Error number %d", retval); usb_set_intfdata(intf, NULL); goto error; } /* let the user know what node this device is now attached to */ dev_info(&intf->dev, "USB Skeleton device now attached to USBSkel-%d", intf->minor); //input으로 등록. xpad_init_input(xpad); return 0; error: kfree(xpad); return retval; err_free_in_urb: usb_free_urb(xpad->irq_in); return retval; } static void xpad_disconnect(struct usb_interface *intf) { struct usb_xpad *xpad; xpad = usb_get_intfdata(intf); pr_info("xpad address is %p\n", xpad); xpad_deinit_input(xpad); usb_deregister_dev(intf, &xbox_class); usb_set_intfdata(intf, NULL); kfree(xpad); pr_info("disconnected\n"); } static int xpad_suspend(struct usb_interface *intf, pm_message_t message) { pr_info("suspendes\n"); return 0; } static int xpad_resume(struct usb_interface *intf) { pr_info("resumed\n"); return 0; } static struct usb_driver xpad_test_driver = { .name = "xbox360_test", .probe = xpad_probe, .disconnect = xpad_disconnect, .suspend = xpad_suspend, .resume = xpad_resume, .id_table = xpad_table, }; static int xpad_init_urb(struct usb_interface *intf, struct usb_xpad *xpad) { int retval; xpad->irq_in= usb_alloc_urb(0, GFP_KERNEL); if (!xpad->irq_in) { retval = -ENOMEM; goto error; } return 0; error: return retval; } // 모듈 loading, unloading 테스트를 위해 과거 방식으로 사용 //module_init(usb_xboxtest_init); //module_exit(usb_xboxtest_exit); //Macro module_usb_driver(xpad_test_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("now0930"); MODULE_DESCRIPTION("Hello, xbox pad!");
[45180.903315] xpad is ea6ce566, myPad is ea6ce566 [45180.903332] interface is ea6ce566 [45180.903349] [info]: found interrupt end point, in: 129, out: 1 [45180.903360] probe [45180.903636] xbox360_test 1-1.2:1.0: USB Skeleton device now attached to USBSkel-1 [45180.903815] input: Unspecified device as /devices/virtual/input/input8 [45180.904290] usb input was registered [45180.904475] xpad is 50c63d58, myPad is 50c63d58 [45180.904489] interface is 50c63d58 [45180.904506] [info]: found interrupt end point, in: 130, out: 2 [45180.904518] probe [45180.904685] xbox360_test 1-1.2:1.1: USB Skeleton device now attached to USBSkel-2 [45180.904841] input: Unspecified device as /devices/virtual/input/input9 [45180.905155] usb input was registered [45180.905310] xpad is 3bfd76ed, myPad is 3bfd76ed [45180.905323] interface is 3bfd76ed [45180.905342] xbox360_test 1-1.2:1.2: Could not find both irq-in and irq-out endpoints [45180.905432] xpad is 07d8d175, myPad is 07d8d175 [45180.905451] interface is 07d8d175 [45180.905469] xbox360_test 1-1.2:1.3: Could not find both irq-in and irq-out endpoints [45180.905585] usbcore: registered new interface driver xbox360_test [45180.909177] test [45180.913170] test [45210.971462] test [45210.984592] test [45210.997466] test [45211.010595] test [45211.075485] test [45211.088606] test [45211.109483] test [45211.130612] test [45211.151488] test [45211.168617] test [45211.181493] test [45211.754713] test [45211.799598] test [45211.812711] test [45212.329679] test [45217.545918] usbcore: deregistering interface driver xbox360_test [45217.546248] test [45217.546378] xpad address is 99d70f22 [45217.546411] xpad is 99d70f22, ->dev is 8784f33d. [45217.699130] disconnected [45217.699578] test [45217.699674] xpad address is 0764c1af [45217.699692] xpad is 0764c1af, ->dev is 379dff77. [45217.779137] disconnected