raspberry pi4 bluetooth pairing with cli

살다 보면 터미널로 블루투스 페이링을 할 필요가 있다. bluetoothctl가 제공하는 scan on, pair, trust, connect로 가능하다.

[bluetooth]# pair 34:88:5D:5C:C2:65
Device 34:88:5D:5C:C2:65 not available
[bluetooth]# scan on
Discovery started
[CHG] Controller AA:AA:AA:AA:AA:AA Discovering: yes
[CHG] Device 74:31:32:C6:8F:67 RSSI: -72
[CHG] Device 74:31:32:C6:8F:67 TxPower: 12
[CHG] Device 00:17:55:B0:CF:2E RSSI: -84
[CHG] Device 5F:B7:26:83:54:58 RSSI: -73
[CHG] Device 5F:B7:26:83:54:58 TxPower: 26
[CHG] Device 75:CB:B4:32:0F:29 RSSI: -85
[CHG] Device 5F:5C:FD:38:FC:36 RSSI: -84
[CHG] Device 5F:5C:FD:38:FC:36 TxPower: 6
[CHG] Device 68:47:92:B1:39:9C RSSI: -73
[CHG] Device 68:47:92:B1:39:9C TxPower: 8
[CHG] Device 58:7B:3E:E0:3F:F5 RSSI: -72
[CHG] Device 58:7B:3E:E0:3F:F5 TxPower: 7
[CHG] Device 42:39:EF:80:06:A0 RSSI: -71
[CHG] Device 42:39:EF:80:06:A0 TxPower: 12
[CHG] Device 00:17:55:B0:CF:2C RSSI: -89
[CHG] Device 00:17:55:B0:CC:14 RSSI: -79
[CHG] Device 7D:72:08:EE:56:18 RSSI: -72
[CHG] Device 7D:72:08:EE:56:18 TxPower: 24
[CHG] Device 42:39:EF:80:06:A0 AdvertisingFlags:
  00                                               .               
[CHG] Device 00:17:55:B0:C3:AA RSSI: -88
[CHG] Device 43:AF:C1:01:A4:9E RSSI: -73
[CHG] Device 43:AF:C1:01:A4:9E TxPower: 7
[CHG] Device 00:17:55:B0:CF:27 RSSI: -89
[CHG] Device 00:17:55:B0:CC:16 RSSI: -91
[CHG] Device 43:AF:C1:01:A4:9E AdvertisingFlags:
  00                                               .               
[CHG] Device 00:17:55:B0:CC:16 AdvertisingFlags:
  00                                               .               
[CHG] Device 00:17:55:B0:CF:27 AdvertisingFlags:
  00                                               .               
[CHG] Device 53:02:B7:C1:DA:EF RSSI: -90
[CHG] Device 00:17:55:B0:CF:2C AdvertisingFlags:
  00                                               .               
[CHG] Device 53:02:B7:C1:DA:EF AdvertisingFlags:
  00                                               .               
[NEW] Device 34:88:5D:5C:C2:65 Keyboard K480
[CHG] Device DC:B5:4F:0A:1B:C7 RSSI: -79
[bluetooth]# pair 34:88:5D:5C:C2:65
Attempting to pair with 34:88:5D:5C:C2:65
Failed to pair: org.bluez.Error.ConnectionAttemptFailed
[CHG] Device 42:39:EF:80:06:A0 AdvertisingFlags:
  1a                                               .               
[CHG] Device 00:17:55:B0:CC:16 AdvertisingFlags:
  06                                               .               
[CHG] Device 2E:23:6D:1E:EC:1A RSSI: -85
[CHG] Device 43:AF:C1:01:A4:9E AdvertisingFlags:
  1a                                               .               
[CHG] Device 00:17:55:B0:CF:27 AdvertisingFlags:
  06                                               .               
[CHG] Device 70:4A:71:F9:0C:89 RSSI: -92
[CHG] Device 70:4A:71:F9:0C:89 TxPower: 7
[CHG] Device 70:4A:71:F9:0C:89 AdvertisingFlags:
  1a                                               .               
[CHG] Device 53:02:B7:C1:DA:EF AdvertisingFlags:
  02                                               .               
[CHG] Device 68:47:92:B1:39:9C RSSI: -86
[CHG] Device 75:CB:B4:32:0F:29 RSSI: -97
[NEW] Device 00:17:55:B0:CE:F5 44881938.01000222
[CHG] Device 53:02:B7:C1:DA:EF AdvertisingFlags:
  00                                               .               
[CHG] Device 00:17:55:B0:CF:2C AdvertisingFlags:
  06                                               .               
[bluetooth]# pair 34:88:5D:5C:C2:65
Attempting to pair with 34:88:5D:5C:C2:65
[CHG] Device 34:88:5D:5C:C2:65 Connected: yes
[agent] Passkey: 091237
[agent] Passkey: 091237
[agent] Passkey: 091237
[agent] Passkey: 091237
[agent] Passkey: 091237
[agent] Passkey: 091237
[agent] Passkey: 091237
[CHG] Device 34:88:5D:5C:C2:65 Modalias: usb:v046DpB33Cd2802
[CHG] Device 34:88:5D:5C:C2:65 UUIDs: 00001000-0000-1000-8000-00805f9b34fb
[CHG] Device 34:88:5D:5C:C2:65 UUIDs: 00001124-0000-1000-8000-00805f9b34fb
[CHG] Device 34:88:5D:5C:C2:65 UUIDs: 00001200-0000-1000-8000-00805f9b34fb
[CHG] Device 34:88:5D:5C:C2:65 ServicesResolved: yes
[CHG] Device 34:88:5D:5C:C2:65 Paired: yes
Pairing successful
[CHG] Device 34:88:5D:5C:C2:65 ServicesResolved: no
[CHG] Device 34:88:5D:5C:C2:65 Connected: no
[CHG] Device 00:17:55:B0:CC:14 RSSI: -89
[CHG] Device 53:02:B7:C1:DA:EF AdvertisingFlags:
  02                                               .               
[CHG] Device 68:47:92:B1:39:9C RSSI: -78
[CHG] Device 5F:5C:FD:38:FC:36 ManufacturerData Key: 0x004c
[CHG] Device 5F:5C:FD:38:FC:36 ManufacturerData Value:
  10 06 7e 1e 2a 4b a0 b5                          ..~.*K..        
[CHG] Device 75:CB:B4:32:0F:29 RSSI: -88
[CHG] Device 75:CB:B4:32:0F:29 AdvertisingFlags:
  00                                               .               
[CHG] Device 74:31:32:C6:8F:67 RSSI: -87
[CHG] Device 7F:70:C6:B1:18:7B RSSI: -90
[CHG] Device 7F:70:C6:B1:18:7B TxPower: 12
[CHG] Device 00:17:55:B0:CF:27 AdvertisingFlags:
  00                                               .               
[bluetooth]# trust 34:88:5D:5C:C2:65
[CHG] Device 34:88:5D:5C:C2:65 Trusted: yes
Changing 34:88:5D:5C:C2:65 trust succeeded
[CHG] Device 74:31:32:C6:8F:67 RSSI: -71
[CHG] Device 00:17:55:B0:CC:14 RSSI: -80
[CHG] Device 75:CB:B4:32:0F:29 AdvertisingFlags:
  02                                               .               
[NEW] Device 79:2D:EA:FA:C8:07 79-2D-EA-FA-C8-07
[CHG] Device 43:AF:C1:01:A4:9E AdvertisingFlags:
  00                                               .               
[CHG] Device 42:39:EF:80:06:A0 AdvertisingFlags:
  00                                               .               
[CHG] Device 68:47:92:B1:39:9C AdvertisingFlags:
  00                                               .               
[CHG] Device 53:02:B7:C1:DA:EF AdvertisingFlags:
  00                                               .               
[CHG] Device 00:17:55:B0:C3:D0 RSSI: -94
[CHG] Device 00:17:55:B0:CF:27 AdvertisingFlags:
  06                                               .               
[DEL] Device 7D:72:08:EE:56:18 7D-72-08-EE-56-18
[DEL] Device 00:17:55:B0:CF:2E 44881995.01000222
[DEL] Device 5F:B7:26:83:54:58 5F-B7-26-83-54-58
[DEL] Device 72:1E:96:C2:47:34 72-1E-96-C2-47-34
[DEL] Device 74:31:32:C6:8F:67 74-31-32-C6-8F-67
[DEL] Device 5F:5C:FD:38:FC:36 5F-5C-FD-38-FC-36
[DEL] Device 58:7B:3E:E0:3F:F5 58-7B-3E-E0-3F-F5
[DEL] Device 68:47:92:B1:39:9C 68-47-92-B1-39-9C
[DEL] Device 00:17:55:B0:CC:14 44881201.01000222
[DEL] Device 42:39:EF:80:06:A0 42-39-EF-80-06-A0
[DEL] Device 2E:23:6D:1E:EC:1A 2E-23-6D-1E-EC-1A
[DEL] Device 52:16:A9:CA:B5:11 52-16-A9-CA-B5-11
[DEL] Device 00:17:55:B0:CF:27 44881988.01000222
[DEL] Device 7F:70:C6:B1:18:7B 7F-70-C6-B1-18-7B
[DEL] Device 50:1E:AF:D4:17:76 50-1E-AF-D4-17-76
[DEL] Device 00:17:55:B0:C3:AA 44879047.01000222
[DEL] Device 6F:3D:D3:F2:4A:31 6F-3D-D3-F2-4A-31
[DEL] Device DC:B5:4F:0A:1B:C7 Mike Bouchon
[DEL] Device 00:17:55:B0:CC:16 44881203.01000222
[DEL] Device 6F:44:C7:02:3B:D0 6F-44-C7-02-3B-D0
[DEL] Device 4A:41:FE:14:FE:2A 4A-41-FE-14-FE-2A
[DEL] Device 00:17:55:B0:CF:17 00-17-55-B0-CF-17
[DEL] Device 40:CC:C6:57:E4:40 40-CC-C6-57-E4-40
[DEL] Device 00:17:55:B0:CF:25 44881986.01000222
[DEL] Device 70:4A:71:F9:0C:89 70-4A-71-F9-0C-89
[DEL] Device 00:17:55:B0:CF:2C 44881993.01000222
[DEL] Device 00:17:55:B0:C3:D0 00-17-55-B0-C3-D0
[DEL] Device 76:7F:82:5E:9F:77 76-7F-82-5E-9F-77
[DEL] Device 53:02:B7:C1:DA:EF 53-02-B7-C1-DA-EF
[DEL] Device 57:09:04:A4:37:BD 57-09-04-A4-37-BD
[DEL] Device 75:CB:B4:32:0F:29 75-CB-B4-32-0F-29
[DEL] Device 43:AF:C1:01:A4:9E 43-AF-C1-01-A4-9E
[DEL] Device 00:17:55:B0:CE:F5 44881938.01000222
[DEL] Device 79:2D:EA:FA:C8:07 79-2D-EA-FA-C8-07
[NEW] Device 7F:70:C6:B1:18:7B 7F-70-C6-B1-18-7B
[bluetooth]# connect 34:88:5D:5C:C2:65
Attempting to connect to 34:88:5D:5C:C2:65
[CHG] Device 34:88:5D:5C:C2:65 Connected: yes
Connection successful
[NEW] Device DC:B5:4F:0A:1B:C7 Mike Bouchon
[CHG] Device 34:88:5D:5C:C2:65 ServicesResolved: yes
[NEW] Device 43:AF:C1:01:A4:9E 43-AF-C1-01-A4-9E
[NEW] Device 5F:B7:26:83:54:58 5F-B7-26-83-54-58
[NEW] Device 53:02:B7:C1:DA:EF 53-02-B7-C1-DA-EF
[NEW] Device 7D:72:08:EE:56:18 7D-72-08-EE-56-18
[NEW] Device 68:47:92:B1:39:9C 68-47-92-B1-39-9C
[NEW] Device 10:B8:5D:5C:03:7E 10-B8-5D-5C-03-7E
[NEW] Device 6F:1D:B5:96:AE:86 6F-1D-B5-96-AE-86
[NEW] Device 74:31:32:C6:8F:67 74-31-32-C6-8F-67
[NEW] Device 00:17:55:B0:CF:2E 44881995.01000222
[NEW] Device 75:CB:B4:32:0F:29 75-CB-B4-32-0F-29
[NEW] Device 42:39:EF:80:06:A0 42-39-EF-80-06-A0
[NEW] Device 79:2D:EA:FA:C8:07 79-2D-EA-FA-C8-07
[NEW] Device 00:17:55:B0:CF:27 44881988.01000222
[NEW] Device 6F:3D:D3:F2:4A:31 6F-3D-D3-F2-4A-31
[NEW] Device 00:17:55:B0:CC:14 44881201.01000222
[NEW] Device 00:17:55:B0:C3:AA 44879047.01000222
[CHG] Device 00:17:55:B0:CF:27 AdvertisingFlags:
  00                                               .               
[NEW] Device 00:17:55:B0:CF:2C 44881993.01000222
[NEW] Device 00:17:55:B0:CC:16 44881203.01000222
[CHG] Device DC:B5:4F:0A:1B:C7 RSSI: -80

처음 블루투스를 실행하면 잘 안되는데, 여기를 참조했다. ExecStart에 옵션을 잘 넣어주면 된다.

pi@raspberrypi:/etc/systemd/system/bluetooth.target.wants $ pwd
/etc/systemd/system/bluetooth.target.wants
pi@raspberrypi:/etc/systemd/system/bluetooth.target.wants $ cat bluetooth.service bluetooth.service 
[Unit]
Description=Bluetooth service
Documentation=man:bluetoothd(8)
ConditionPathIsDirectory=/sys/class/bluetooth

[Service]
Type=dbus
BusName=org.bluez
#ExecStart=/usr/lib/bluetooth/bluetoothd
ExecStart=/usr/lib/bluetooth/bluetoothd --compat --noplugin=sap -E 
NotifyAccess=main
#WatchdogSec=10
#Restart=on-failure
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
LimitNPROC=1
ProtectHome=true
ProtectSystem=full

[Install]
WantedBy=bluetooth.target
Alias=dbus-org.bluez.service
[Unit]
Description=Bluetooth service
Documentation=man:bluetoothd(8)
ConditionPathIsDirectory=/sys/class/bluetooth

[Service]
Type=dbus
BusName=org.bluez
#ExecStart=/usr/lib/bluetooth/bluetoothd
ExecStart=/usr/lib/bluetooth/bluetoothd --compat --noplugin=sap -E 
NotifyAccess=main
#WatchdogSec=10
#Restart=on-failure
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
LimitNPROC=1
ProtectHome=true
ProtectSystem=full

[Install]
WantedBy=bluetooth.target
Alias=dbus-org.bluez.service

xbox 360 드라이버 만지기1

내 라즈베리 파이에 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

리눅스 커널의 구조와 원리

isbn: 9791158391980

저자가 디버깅, 커널을 강조하다보니, 제목이 너무 길어졌다. 다행히 초심자가 쉽게 이해할 수 있는 내용이다. 책 2권이다 보니 사기 아까웠지만, 읽고나면 살만한 책이다. 이 책이 자세하게 설명하여 커널이 어떻게 동작하는지 알 수 있다. 내용이 인터넷에도 있겠지만, 내가 찾는 수고를 저자가 했고, 틀린 내용을 저자가 걸러줬다. 이렇게 보면 지출할 만한 가격이다.

20년 전 리눅스를 시작하려 해도 괜찮은 책, 문서가 없었다. 그 시절 인터넷도 잘 발달되지 않아 따라 하기 어려웠다. 커널 구조, 동작 방식을 설명하는 책도 찾기 어려웠고, 파편화 된 영문 문서를 종합하여 이해할만한 시간, 능력, 의지도 없었다. PC 성능도 낮아 한번 커널 컴파일하면 4시간 정도 걸렸다. 커널 패닉에 닥치면 어떻게 해결할지 막막했다. 지금같이 휴대폰으로 찾아볼 수 없다보니. 진심이 아닌 취미로 접근하기 너무나도 어려웠다.

다행히 시대가 변해 성능좋고 값싼 하드웨어를 쉽게 구할 수 있다. 이 책과 같은 좋은 교재로 짧은 시간에 익힐 수 있다. 커널에서 지원하는 디버그 툴이 함수 호출 내역을 표시한다. 인터넷도 발달되어 남이 작성한 문서를 쉽게 찾고, 사용할 수 있다. 의지만 있다면 과거 5년 학습 결과를 지금 6개월에 달성할 수 있어 보인다.

책 차례를 보면 커널을 어떻게 접근할지 보인다. OS 기본이 프로세스이므로 task descriptor에서 시작한다. 인터럽트 또한 중요하여 다음에 있다. softirq, workqueue도 인터럽트 다음으로 쉽게 알 수 있다. 2권을 보면 그 외 타이머, 동기화, 스케줄러 등 기본 지식을 학습할 수 있다.

책을 읽고 나면 커널을 쉽게 접근할 수 있고, 남이 작성한 문서를 이해할 수 있다는 자신감-실력이 아닌-을 갖는다. IoT 시대를 대비할 수 있는 지금 배우지 않을 이유가 없다. ON/OFF 스위치를 만들더라도 인터넷도 되고, 터치 패드 있는 제품이 뽀대 난다. 얼마인지 모르겠으나 정말 있다!! 수화물 5kg를 희생하여 미국까지 들고 온 보람있다.

spinlock, mutex 실습

모듈을 로딩하면 kernel thread부터 만들어야 한다. 같은 책 초반에 나왔는데, 다시 보니 기억할 수 없다. 스레드를 만든 후 함수와 데이터를 넘겨야 하는데, 데이터가 void 포인터다. struct로 캐스팅 하고 싶은데, 에러가 났다. kernel 코드를 보고 괄호를 몇 번 붙였다. 아! ㅅㅂ. 구글 찾아보기보다 시(간)성비가 더 좋다.

pi@raspberrypi:~/linux $ grep -wn "kthread_create" -r ./drivers
./drivers/usb/usbip/usbip_common.h:285:		= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
./drivers/usb/atm/ueagle-atm.c:2228:	sc->kthread = kthread_create(uea_kthread, sc, "ueagle-atm");
./drivers/usb/atm/usbatm.c:977:	t = kthread_create(usbatm_do_heavy_init, instance, "%s",
./drivers/usb/gadget/file_storage.c:3527:	fsg->thread_task = kthread_create(fsg_main_thread, fsg,
./drivers/usb/gadget/function/u_serial.c:1059:	info->console_thread = kthread_create(gs_console_thread,
./drivers/usb/gadget/function/f_mass_storage.c:2924:			kthread_create(fsg_main_thread, common, "file-storage");
./drivers/usb/host/dwc_common_port/dwc_common_fbsd.c:981:	retval = kthread_create((void (*)(void *))func, data, &thread->proc,
./drivers/iio/adc/ina2xx-adc.c:840:	task = kthread_create(ina2xx_capture_thread, (void *)indio_dev,

pi@raspberrypi:~/linux $ vi ./drivers/usb/host/dwc_common_port/dwc_common_fbsd.c
dwc_thread_t *DWC_THREAD_RUN(dwc_thread_function_t func, char *name, void *data)
{
        int retval;
        dwc_thread_t *thread = DWC_ALLOC(sizeof(*thread));

        if (!thread) {
                return NULL;
        }

        thread->abort = 0; 
        retval = kthread_create((void (*)(void *))func, data, &thread->proc,
                                RFPROC | RFNOWAIT, 0, "%s", name);
        if (retval) {
                DWC_FREE(thread);
                return NULL;
        }

        return thread;
}

spinlock을 만든 후 초기화 하지 않으면, 사용할 수 없다. kernel에 메모리를 할당 받으면 초기화는 꼭 해줘야 하는 느낌이다.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>     //GPIO


#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/fcntl.h>


#define GPIO_10_OUT (10)
#define DEVICE_DATA_MAX 256

#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/spinlock.h>


unsigned int GPIO_irqNumber;

/*사용자 데이터 부분*/
struct my_device_data{
	struct cdev cdev;
	int index;
	char my_string[DEVICE_DATA_MAX];
	struct timer_list simple_timer;
	spinlock_t lock;
	struct task_struct *kthread1;
	struct task_struct *kthread2;
} ;

int run_this_thread1(void* data);
int run_this_thread2(void* data);

int run_this_thread1(void* data)
{
	//while(1)로 하면 kthread_stop을 할 수 없음.
	//루프가 없으면 kthread_stop을 불렀을 경우, segment error.
	//thread가 없다면, 종료를 할 수 없어 보임..
	while(!kthread_should_stop())
	{
	//data is my_device_data
	struct my_device_data *ptr_main_data;
	//ptr_main_data = data;
	ptr_main_data = (struct my_device_data(*))data;

	//공통이 구조체 접근.
	spin_lock(&ptr_main_data->lock);
	ptr_main_data->index++;
	//pr_info("spin lock is %0x\n",ptr_main_data->lock);
	pr_info("==============\n");
	pr_info("[+]index is %d\n", ptr_main_data->index);
	pr_info("==============\n");
	spin_unlock(&ptr_main_data->lock);
	//pr_info("spin lock is %0x\n",ptr_main_data->lock);

	msleep(500);
	}
	return 0;

}

int run_this_thread2(void* data)
{
	while(!kthread_should_stop())
	{
	//data is my_device_data
	struct my_device_data *ptr_main_data;
	ptr_main_data = (struct my_device_data(*))data;
	//
	//공통이 구조체 접근.
	spin_lock(&ptr_main_data->lock);
	ptr_main_data->index++;
	pr_info("==============\n");
	pr_info("[+]index is %d\n", ptr_main_data->index);
	pr_info("==============\n");
	spin_unlock(&ptr_main_data->lock);
	msleep(500);
	}

	return 0;

}

static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
	/*Scheduling work queue*/
	return IRQ_HANDLED;

}


//device driver 작성 부분.
/*************드라이버 함수 ******************/
static int mydriver_open(struct inode *inode, struct file *file);
static int mydriver_release(struct inode *inode, struct file *file);
static ssize_t mydriver_read(struct file *flip,
		char *buf, size_t len, loff_t *off);
static ssize_t mydriver_write(struct file *flip,
		const char *buf, size_t len, loff_t *off);
/********************************************/



//file operation structure

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.read = mydriver_read,
	.write = mydriver_write,
	.open = mydriver_open,
	.release = mydriver_release,
};

static int mydriver_open(struct inode *inode, struct file *file)
{
	pr_info("Deviced file was opend.\n");
	return 0;

}

static int mydriver_release(struct inode *inode, struct file *file)
{
	pr_info("Deviced file was closed.\n");
	return 0;
}


static int mydriver_read(struct file *file,
		char *buf, size_t len, loff_t *off)
{
	pr_info("read\n");
	return 0;
}


static int mydriver_write(struct file *flip,
		const char *buf, size_t len, loff_t *off)
{
	return 0;

}

dev_t dev = 0;
static struct cdev my_cdev;
static struct class *dev_class;
struct my_device_data *main_data;
static int __init init_hw(void)
{
	//디바이스 등록
	if(( alloc_chrdev_region(&dev, 0, 1, "test_device") < 0))
	{
		pr_err("[!]character device was not allocated\n");
		goto r_unreg;

	}
	pr_info("[=]%d-%d, was allocated\n", MAJOR(dev), MINOR(dev));


	//초기화
	cdev_init(&my_cdev, &fops);
	pr_info("[=]driver was initialized\n");


	//시스템에 추가
	if((cdev_add(&my_cdev, dev, 1)) < 0)
	{
		pr_err("[!]cannot add device to kernel\n");
		goto r_del;

	}


	//class 만듦.
	if((dev_class=class_create(THIS_MODULE, "my_class")) == NULL)
	{
		pr_err("[!]cannot add class\n");
		goto r_class;
	}


	if((device_create(dev_class, NULL, dev, NULL, "my_device")) == NULL)
	{

		pr_err("[!]cannot create device\n");
		goto r_device;
	}


	//gpio 10번을 사용.
	//export하여 간단히 사용.
	//입력은 값을 써 넣을 수 없음. 출력으로 설정.
	GPIO_irqNumber = gpio_to_irq(GPIO_10_OUT);
	pr_info("[=]irq %d was assinged\n",GPIO_irqNumber);

	//interrupt 등록 필요
	if (request_irq(GPIO_irqNumber,
				(void*)gpio_irq_handler,
				IRQF_TRIGGER_RISING,
				"my_device",
				NULL))
	{
		pr_err("[!]my_device: cannot register IRQ\n");
		goto r_gpio;
	}
	pr_info("[=]module was installed\n");


	//메모리 공간 할당.
	main_data = kmalloc(sizeof(struct my_device_data), GFP_KERNEL);


	//spin lock init
	spin_lock_init(&main_data->lock);
	main_data->index=0;
	if(!main_data)
	{
		pr_err("[!]cannot alloc memory\n");
		goto r_memory;
	}
	pr_info("[=]got memory\n");

	//thread create.
	main_data->kthread1 = kthread_create(run_this_thread1, main_data, "my_thread1");
	main_data->kthread2 = kthread_create(run_this_thread2, main_data, "my_thread2");

	if(main_data->kthread1)
	{
		wake_up_process(main_data->kthread1);
		pr_info("wake up thread1 at %p\n", main_data->kthread1);
	}
	if(main_data->kthread2)
	{
		wake_up_process(main_data->kthread2);
		pr_info("wake up thread2 at %p\n", main_data->kthread2);
	}

	return 0;
r_gpio:
	gpio_free(GPIO_10_OUT);
r_device:
	device_destroy(dev_class,dev);

r_class:
	class_destroy(dev_class);
r_del:
	cdev_del(&my_cdev);

r_unreg:
	unregister_chrdev_region(dev,1);
r_memory:
	;

	return -1;
}

static void __exit exit_hw(void) {
	free_irq(GPIO_irqNumber, NULL);
	gpio_free(GPIO_10_OUT);
	//flush_work(struct work_struct *work);
	device_destroy(dev_class,dev);
	//class_unregister(dev_class);
	class_destroy(dev_class);
	cdev_del(&my_cdev);
	unregister_chrdev_region(dev,1);
	if(main_data->kthread1)
		kthread_stop(main_data->kthread1);
	if(main_data->kthread2)
		kthread_stop(main_data->kthread2);
	pr_info("kthread was stopped\n");
	pr_info("wake up thread1 at %p\n", main_data->kthread1);
	pr_info("wake up thread2 at %p\n", main_data->kthread2);


	kfree(main_data);
	printk(KERN_INFO "module was removed\n");
}


module_init(init_hw);
module_exit(exit_hw);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("noname");
MODULE_DESCRIPTION("Hello, world!");

spin lock을 적용하지 않을 경우, 겹쳐쓴다. 적용하면 순서대로 실행된다 믿는다.

[ 1749.848372] ==============
[ 1749.848376] [+]index is 108
[ 1749.848380] ==============
[ 1750.378381] ==============
[ 1750.378392] [+]index is 109
[ 1750.378399] ==============
[ 1750.378414] ==============
[ 1750.378421] [+]index is 110
[ 1750.378428] ==============
[ 1750.898416] ==============
[ 1750.898431] [+]index is 111
[ 1750.898439] ==============
[ 1750.898461] ==============
[ 1750.898470] [+]index is 112
[ 1750.898478] ==============
[ 1751.418404] ==============
[ 1751.418417] [+]index is 113
[ 1751.418426] ==============
[ 1751.418446] ==============
[ 1751.418455] [+]index is 114
[ 1751.418463] ==============
[ 1751.938485] ==============
[ 1751.938501] [+]index is 115
[ 1751.938512] ==============
[ 1751.938537] ==============
[ 1751.938547] [+]index is 116
[ 1751.938556] ==============
[ 1752.458509] ==============
[ 1752.458529] [+]index is 117
[ 1752.458538] ==============
[ 1752.458560] ==============
[ 1752.458570] [+]index is 118
[ 1752.458578] ==============
[ 1752.978388] ==============
[ 1752.978394] [+]index is 119
[ 1752.978398] ==============
[ 1753.498497] kthread was stopped
[ 1753.498515] wake up thread1 at 39208d2a
[ 1753.498524] wake up thread2 at 5bdf5278
[ 1753.498536] module was removed
[ 1773.404079] [=]236-0, was allocated
[ 1773.404089] [=]driver was initialized
[ 1773.406890] [=]irq 54 was assinged
[ 1773.406942] [=]module was installed
[ 1773.406952] [=]got memory
[ 1773.410837] wake up thread1 at 6acc0d97
[ 1773.410859] wake up thread2 at 9613521a
[ 1773.411132] ==============
[ 1773.411143] [+]index is 1
[ 1773.411151] ==============
[ 1773.411794] ==============
[ 1773.411805] [+]index is 2
[ 1773.411812] ==============
[ 1773.528622] ==============
[ 1773.528626] ==============
[ 1773.528636] [+]index is 4
[ 1773.528637] [+]index is 4
[ 1773.528640] ==============
[ 1773.528641] ==============
[ 1773.648584] ==============
[ 1773.648585] ==============
[ 1773.648589] [+]index is 6
[ 1773.648590] [+]index is 6
[ 1773.648593] ==============
[ 1773.648594] ==============
[ 1773.768583] ==============
[ 1773.768584] ==============
[ 1773.768588] [+]index is 8
[ 1773.768589] [+]index is 8
[ 1773.768591] ==============
[ 1773.768594] ==============
[ 1773.888583] ==============
[ 1773.888587] [+]index is 9
[ 1773.888589] ==============
[ 1773.888591] ==============
[ 1773.888595] [+]index is 10
[ 1773.888598] ==============
[ 1774.008583] ==============
[ 1774.008585] ==============
[ 1774.008588] [+]index is 12
[ 1774.008591] [+]index is 12
[ 1774.008594] ==============
[ 1774.008596] ==============
[ 1774.128592] ==============
[ 1774.128593] ==============
[ 1774.128597] [+]index is 14
[ 1774.128599] [+]index is 14
[ 1774.128601] ==============
[ 1774.128604] ==============
[ 1774.248635] ==============
[ 1774.248638] ==============
[ 1774.248648] [+]index is 16
[ 1774.248652] [+]index is 16
[ 1774.248657] ==============
[ 1774.248663] ==============
[ 1774.368631] ==============
[ 1774.368642] [+]index is 17
[ 1774.368649] ==============
[ 1774.378613] ==============
[ 1774.378622] [+]index is 18
https://embetronicx.com/tutorials/linux/device-drivers/linux-device-drivers-tutorial-kernel-thread/

https://embetronicx.com/tutorials/linux/device-drivers/spinlock-in-linux-kernel-1/

https://embetronicx.com/tutorials/linux/device-drivers/linux-device-driver-tutorial-mutex-in-linux-kernel/

timer 실습

kernel timer를 실습했다. callback 함수 argrument로 timer를 가지고 있는 구조체 데이터를 전달할 수 있다. work queue와 같은 방식이다. 커널 특정 버전부터 이런 식으로 변경되었는 듯 하다. 타이머가 만료되면 다시 등록하도록 했다.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>     //GPIO


#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/fcntl.h>

#include <linux/timer.h>
#include <linux/jiffies.h>

#define GPIO_10_OUT (10)
#define DEVICE_DATA_MAX 256

unsigned int GPIO_irqNumber;

/*\uc0ac\uc6a9\uc790 \ub370\uc774\ud130 \ubd80\ubd84*/
static struct my_device_data{
	struct cdev cdev;
	int index;
	char my_string[DEVICE_DATA_MAX];
	struct timer_list simple_timer;
} my_data1;


void timer_action_fn(struct timer_list *t);

void timer_action_fn(struct timer_list *t)
{
	struct my_device_data *ptr1;
	pr_info("%ld: timer function was activated\n", jiffies);
	ptr1=from_timer(ptr1, t, simple_timer);
	ptr1->index++;
	pr_info("index is %d\n", ptr1->index);
	mod_timer(&my_data1.simple_timer, jiffies+msecs_to_jiffies(1000));


}


static int init_timer(void){
	timer_setup(&my_data1.simple_timer, timer_action_fn, 0);
	pr_info("%ld: timer was setup\n", jiffies);
	mod_timer(&my_data1.simple_timer, jiffies+msecs_to_jiffies(1000));
	return 0;

}

static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
	/*Scheduling work queue*/
	return IRQ_HANDLED;

}


//device driver \uc791\uc131 \ubd80\ubd84.
/*************\ub4dc\ub77c\uc774\ubc84 \ud568\uc218 ******************/
static int mydriver_open(struct inode *inode, struct file *file);
static int mydriver_release(struct inode *inode, struct file *file);
static ssize_t mydriver_read(struct file *flip,
		char *buf, size_t len, loff_t *off);
static ssize_t mydriver_write(struct file *flip,
		const char *buf, size_t len, loff_t *off);
/********************************************/



//file operation structure

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.read = mydriver_read,
	.write = mydriver_write,
	.open = mydriver_open,
	.release = mydriver_release,
};

static int mydriver_open(struct inode *inode, struct file *file)
{
	pr_info("Deviced file was opend.\n");
	return 0;

}

static int mydriver_release(struct inode *inode, struct file *file)
{
	pr_info("Deviced file was closed.\n");
	return 0;
}


static int mydriver_read(struct file *file,
		char *buf, size_t len, loff_t *off)
{
	pr_info("read\n");
	return 0;
}


static int mydriver_write(struct file *flip,
		const char *buf, size_t len, loff_t *off)
{
	return 0;

}

dev_t dev = 0;
static struct cdev my_cdev;
static struct class *dev_class;
static int __init init_hw(void)
{
	//\ub514\ubc14\uc774\uc2a4 \ub4f1\ub85d
	if(( alloc_chrdev_region(&dev, 0, 1, "test_device") < 0))
	{
		pr_err("[!]character device was not allocated\n");
		goto r_unreg;

	}
	pr_info("[=]%d-%d, was allocated\n", MAJOR(dev), MINOR(dev));


	//\ucd08\uae30\ud654
	cdev_init(&my_cdev, &fops);
	pr_info("[=]driver was initialized\n");


	//\uc2dc\uc2a4\ud15c\uc5d0 \ucd94\uac00
	if((cdev_add(&my_cdev, dev, 1)) < 0)
	{
		pr_err("[!]cannot add device to kernel\n");
		goto r_del;

	}


	//class \ub9cc\ub4e6.
	if((dev_class=class_create(THIS_MODULE, "my_class")) == NULL)
	{
		pr_err("[!]cannot add class\n");
		goto r_class;
	}


	if((device_create(dev_class, NULL, dev, NULL, "my_device")) == NULL)
	{

		pr_err("[!]cannot create device\n");
		goto r_device;
	}


	//gpio 10\ubc88\uc744 \uc0ac\uc6a9.
	//export\ud558\uc5ec \uac04\ub2e8\ud788 \uc0ac\uc6a9.
	//\uc785\ub825\uc740 \uac12\uc744 \uc368 \ub123\uc744 \uc218 \uc5c6\uc74c. \ucd9c\ub825\uc73c\ub85c \uc124\uc815.
	GPIO_irqNumber = gpio_to_irq(GPIO_10_OUT);
	pr_info("[=]irq %d was assinged\n",GPIO_irqNumber);

	//interrupt \ub4f1\ub85d \ud544\uc694
	if (request_irq(GPIO_irqNumber,
				(void*)gpio_irq_handler,
				IRQF_TRIGGER_RISING,
				"my_device",
				NULL))
	{
		pr_err("[!]my_device: cannot register IRQ\n");
		goto r_gpio;
	}
	pr_info("[=]module was installed\n");

	//timer setup
	init_timer();
	return 0;
r_gpio:
	gpio_free(GPIO_10_OUT);
r_device:
	device_destroy(dev_class,dev);

r_class:
	class_destroy(dev_class);
r_del:
	cdev_del(&my_cdev);

r_unreg:
	unregister_chrdev_region(dev,1);

	return -1;
}

static void __exit exit_hw(void) {
	free_irq(GPIO_irqNumber, NULL);
	gpio_free(GPIO_10_OUT);
	//flush_work(struct work_struct *work);
	device_destroy(dev_class,dev);
	//class_unregister(dev_class);
	class_destroy(dev_class);
	cdev_del(&my_cdev);
	unregister_chrdev_region(dev,1);
	//timer delete
	del_timer(&my_data1.simple_timer);
	printk(KERN_INFO "module was removed\n");
}


module_init(init_hw);
module_exit(exit_hw);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("noname");
MODULE_DESCRIPTION("Hello, world!");
[146685.757495] [=]236-0, was allocated
[146685.757504] [=]driver was initialized
[146685.757960] [=]irq 54 was assinged
[146685.757984] [=]module was installed
[146685.757991] 14638325: timer was setup
[146686.818385] 14638432: timer function was activated
[146686.818398] index is 1
[146687.858398] 14638536: timer function was activated
[146687.858402] index is 2
[146688.898415] 14638640: timer function was activated
[146688.898428] index is 3
[146689.938432] 14638744: timer function was activated
[146689.938445] index is 4
[146690.978452] 14638848: timer function was activated
[146690.978457] index is 5
[146692.018469] 14638952: timer function was activated
[146692.018483] index is 6
[146693.058494] 14639056: timer function was activated
[146693.058500] index is 7
[146694.098512] 14639160: timer function was activated
[146694.098548] index is 8
[146695.138529] 14639264: timer function was activated
[146695.138563] index is 9
[146696.178545] 14639368: timer function was activated
[146696.178573] index is 10
[146697.218575] 14639472: timer function was activated
[146697.218607] index is 11
[146698.258571] 14639576: timer function was activated
[146698.258587] index is 12

https://embetronicx.com/tutorials/linux/device-drivers/using-kernel-timer-in-linux-device-driver/

https://stackoverflow.com/questions/14953871/how-to-pass-custom-argument-to-linux-timers-function