xref: /linux/drivers/hid/bpf/progs/Huion__Inspiroy-2-S.bpf.c (revision 55d0969c451159cff86949b38c39171cab962069)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Copyright (c) 2024 Red Hat, Inc
3  */
4 
5 #include "vmlinux.h"
6 #include "hid_bpf.h"
7 #include "hid_bpf_helpers.h"
8 #include "hid_report_helpers.h"
9 #include <bpf/bpf_tracing.h>
10 
11 #define VID_HUION 0x256C
12 #define PID_INSPIROY_2_S 0x0066
13 
14 HID_BPF_CONFIG(
15 	HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_INSPIROY_2_S),
16 );
17 
18 /* Filled in by udev-hid-bpf */
19 char UDEV_PROP_HUION_FIRMWARE_ID[64];
20 
21 /* The prefix of the firmware ID we expect for this device. The full firmware
22  * string has a date suffix, e.g. HUION_T21j_221221
23  */
24 char EXPECTED_FIRMWARE_ID[] = "HUION_T21j_";
25 
26 /* How this BPF program works: the tablet has two modes, firmware mode and
27  * tablet mode. In firmware mode (out of the box) the tablet sends button events
28  * and the dial as keyboard combinations. In tablet mode it uses a vendor specific
29  * hid report to report everything instead.
30  * Depending on the mode some hid reports are never sent and the corresponding
31  * devices are mute.
32  *
33  * To switch the tablet use e.g.  https://github.com/whot/huion-switcher
34  * or one of the tools from the digimend project
35  *
36  * This BPF works for both modes. The huion-switcher tool sets the
37  * HUION_FIRMWARE_ID udev property - if that is set then we disable the firmware
38  * pad and pen reports (by making them vendor collections that are ignored).
39  * If that property is not set we fix all hidraw nodes so the tablet works in
40  * either mode though the drawback is that the device will show up twice if
41  * you bind it to all event nodes
42  *
43  * Default report descriptor for the first exposed hidraw node:
44  *
45  * # HUION Huion Tablet_H641P
46  * # Report descriptor length: 18 bytes
47  * # 0x06, 0x00, 0xff,              // Usage Page (Vendor Defined Page 0xFF00)   0
48  * # 0x09, 0x01,                    // Usage (Vendor Usage 0x01)                 3
49  * # 0xa1, 0x01,                    // Collection (Application)                  5
50  * # 0x85, 0x08,                    //   Report ID (8)                           7
51  * # 0x75, 0x58,                    //   Report Size (88)                        9
52  * # 0x95, 0x01,                    //   Report Count (1)                        11
53  * # 0x09, 0x01,                    //   Usage (Vendor Usage 0x01)               13
54  * # 0x81, 0x02,                    //   Input (Data,Var,Abs)                    15
55  * # 0xc0,                          // End Collection                            17
56  * R: 18 06 00 ff 09 01 a1 01 85 08 75 58 95 01 09 01 81 02 c0
57  *
58  * This rdesc does nothing until the tablet is switched to raw mode, see
59  * https://github.com/whot/huion-switcher
60  *
61  *
62  * Second hidraw node is the Pen. This one sends events until the tablet is
63  * switched to raw mode, then it's mute.
64  *
65  * # Report descriptor length: 93 bytes
66  * # 0x05, 0x0d,          // Usage Page (Digitizers)                   0
67  * # 0x09, 0x02,          // Usage (Pen)                               2
68  * # 0xa1, 0x01,          // Collection (Application)                  4
69  * # 0x85, 0x0a,          //   Report ID (10)                          6
70  * # 0x09, 0x20,          //   Usage (Stylus)                          8
71  * # 0xa1, 0x01,          //   Collection (Application)                10
72  * # 0x09, 0x42,          //     Usage (Tip Switch)                    12
73  * # 0x09, 0x44,          //     Usage (Barrel Switch)                 14
74  * # 0x09, 0x45,          //     Usage (Eraser)                        16
75  * # 0x09, 0x3c,          //     Usage (Invert)                        18 <-- has no Invert eraser
76  * # 0x15, 0x00,          //     Logical Minimum (0)                   20
77  * # 0x25, 0x01,          //     Logical Maximum (1)                   22
78  * # 0x75, 0x01,          //     Report Size (1)                       24
79  * # 0x95, 0x06,          //     Report Count (6)                      26
80  * # 0x81, 0x02,          //     Input (Data,Var,Abs)                  28
81  * # 0x09, 0x32,          //     Usage (In Range)                      30
82  * # 0x75, 0x01,          //     Report Size (1)                       32
83  * # 0x95, 0x01,          //     Report Count (1)                      34
84  * # 0x81, 0x02,          //     Input (Data,Var,Abs)                  36
85  * # 0x81, 0x03,          //     Input (Cnst,Var,Abs)                  38
86  * # 0x05, 0x01,          //     Usage Page (Generic Desktop)          40
87  * # 0x09, 0x30,          //     Usage (X)                             42
88  * # 0x09, 0x31,          //     Usage (Y)                             44
89  * # 0x55, 0x0d,          //     Unit Exponent (-3)                    46 <-- change to -2
90  * # 0x65, 0x33,          //     Unit (EnglishLinear: in³)             48 <-- change in³ to in
91  * # 0x26, 0xff, 0x7f,    //     Logical Maximum (32767)               50
92  * # 0x35, 0x00,          //     Physical Minimum (0)                  53
93  * # 0x46, 0x00, 0x08,    //     Physical Maximum (2048)               55 <-- invalid size
94  * # 0x75, 0x10,          //     Report Size (16)                      58
95  * # 0x95, 0x02,          //     Report Count (2)                      60
96  * # 0x81, 0x02,          //     Input (Data,Var,Abs)                  62
97  * # 0x05, 0x0d,          //     Usage Page (Digitizers)               64
98  * # 0x09, 0x30,          //     Usage (Tip Pressure)                  66
99  * # 0x26, 0xff, 0x1f,    //     Logical Maximum (8191)                68
100  * # 0x75, 0x10,          //     Report Size (16)                      71
101  * # 0x95, 0x01,          //     Report Count (1)                      73
102  * # 0x81, 0x02,          //     Input (Data,Var,Abs)                  75
103  * # 0x09, 0x3d,          //     Usage (X Tilt)                        77 <-- No tilt reported
104  * # 0x09, 0x3e,          //     Usage (Y Tilt)                        79
105  * # 0x15, 0x81,          //     Logical Minimum (-127)                81
106  * # 0x25, 0x7f,          //     Logical Maximum (127)                 83
107  * # 0x75, 0x08,          //     Report Size (8)                       85
108  * # 0x95, 0x02,          //     Report Count (2)                      87
109  * # 0x81, 0x02,          //     Input (Data,Var,Abs)                  89
110  * # 0xc0,                //   End Collection                          91
111  * # 0xc0,                // End Collection                            92
112  * R: 93 05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 45 09 3c 15 00 25 01 7501 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 09 3d09 3e 15 81 25 7f 75 08 95 02 81 02 c0 c0
113  *
114  * Third hidraw node is the pad which sends a combination of keyboard shortcuts until
115  * the tablet is switched to raw mode, then it's mute:
116  *
117  * # Report descriptor length: 65 bytes
118  * # 0x05, 0x01,          // Usage Page (Generic Desktop)              0
119  * # 0x09, 0x06,          // Usage (Keyboard)                          2
120  * # 0xa1, 0x01,          // Collection (Application)                  4
121  * # 0x85, 0x03,          //   Report ID (3)                           6
122  * # 0x05, 0x07,          //   Usage Page (Keyboard/Keypad)            8
123  * # 0x19, 0xe0,          //   UsageMinimum (224)                      10
124  * # 0x29, 0xe7,          //   UsageMaximum (231)                      12
125  * # 0x15, 0x00,          //   Logical Minimum (0)                     14
126  * # 0x25, 0x01,          //   Logical Maximum (1)                     16
127  * # 0x75, 0x01,          //   Report Size (1)                         18
128  * # 0x95, 0x08,          //   Report Count (8)                        20
129  * # 0x81, 0x02,          //   Input (Data,Var,Abs)                    22
130  * # 0x05, 0x07,          //   Usage Page (Keyboard/Keypad)            24
131  * # 0x19, 0x00,          //   UsageMinimum (0)                        26
132  * # 0x29, 0xff,          //   UsageMaximum (255)                      28
133  * # 0x26, 0xff, 0x00,    //   Logical Maximum (255)                   30
134  * # 0x75, 0x08,          //   Report Size (8)                         33
135  * # 0x95, 0x06,          //   Report Count (6)                        35
136  * # 0x81, 0x00,          //   Input (Data,Arr,Abs)                    37
137  * # 0xc0,                // End Collection                            39
138  * # 0x05, 0x0c,          // Usage Page (Consumer)                     40
139  * # 0x09, 0x01,          // Usage (Consumer Control)                  42
140  * # 0xa1, 0x01,          // Collection (Application)                  44
141  * # 0x85, 0x04,          //   Report ID (4)                           46
142  * # 0x19, 0x00,          //   UsageMinimum (0)                        48
143  * # 0x2a, 0x3c, 0x02,    //   UsageMaximum (572)                      50
144  * # 0x15, 0x00,          //   Logical Minimum (0)                     53
145  * # 0x26, 0x3c, 0x02,    //   Logical Maximum (572)                   55
146  * # 0x95, 0x01,          //   Report Count (1)                        58
147  * # 0x75, 0x10,          //   Report Size (16)                        60
148  * # 0x81, 0x00,          //   Input (Data,Arr,Abs)                    62
149  * # 0xc0,                // End Collection                            64
150  * R: 65 05 01 09 06 a1 01 85 03 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 0507 19 00 29 ff 26 ff 00 75 08 95 06 81 00 c0 05 0c 09 01 a1 01 85 04 19 00 2a 3c02 15 00 26 3c 02 95 01 75 10 81 00 c0
151  * N: HUION Huion Tablet_H641P
152  */
153 
154 #define PAD_REPORT_DESCRIPTOR_LENGTH 65
155 #define PEN_REPORT_DESCRIPTOR_LENGTH 93
156 #define VENDOR_REPORT_DESCRIPTOR_LENGTH 18
157 #define PAD_REPORT_ID 3
158 #define PEN_REPORT_ID 10
159 #define VENDOR_REPORT_ID 8
160 #define PAD_REPORT_LENGTH 8
161 #define PEN_REPORT_LENGTH 10
162 #define VENDOR_REPORT_LENGTH 12
163 
164 
165 __u8 last_button_state;
166 
167 static const __u8 fixed_rdesc_pad[] = {
168 	UsagePage_GenericDesktop
169 	Usage_GD_Keypad
170 	CollectionApplication(
171 		// -- Byte 0 in report
172 		ReportId(PAD_REPORT_ID)
173 		LogicalRange_i8(0, 1)
174 		UsagePage_Digitizers
175 		Usage_Dig_TabletFunctionKeys
176 		CollectionPhysical(
177 			// Byte 1 in report - just exists so we get to be a tablet pad
178 			Usage_Dig_BarrelSwitch // BTN_STYLUS
179 			ReportCount(1)
180 			ReportSize(1)
181 			Input(Var|Abs)
182 			ReportCount(7) // padding
183 			Input(Const)
184 			// Bytes 2/3 in report - just exists so we get to be a tablet pad
185 			UsagePage_GenericDesktop
186 			Usage_GD_X
187 			Usage_GD_Y
188 			ReportCount(2)
189 			ReportSize(8)
190 			Input(Var|Abs)
191 			// Byte 4 in report is the wheel
192 			Usage_GD_Wheel
193 			LogicalRange_i8(-1, 1)
194 			ReportCount(1)
195 			ReportSize(8)
196 			Input(Var|Rel)
197 			// Byte 5 is the button state
198 			UsagePage_Button
199 			UsageRange_i8(0x01, 0x6)
200 			LogicalRange_i8(0x01, 0x6)
201 			ReportCount(1)
202 			ReportSize(8)
203 			Input(Arr|Abs)
204 		)
205 		// Make sure we match our original report length
206 		FixedSizeVendorReport(PAD_REPORT_LENGTH)
207 	)
208 };
209 
210 static const __u8 fixed_rdesc_pen[] = {
211 	UsagePage_Digitizers
212 	Usage_Dig_Pen
213 	CollectionApplication(
214 		// -- Byte 0 in report
215 		ReportId(PEN_REPORT_ID)
216 		Usage_Dig_Pen
217 		CollectionPhysical(
218 			// -- Byte 1 in report
219 			Usage_Dig_TipSwitch
220 			Usage_Dig_BarrelSwitch
221 			Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
222 			LogicalRange_i8(0, 1)
223 			ReportSize(1)
224 			ReportCount(3)
225 			Input(Var|Abs)
226 			ReportCount(4)  // Padding
227 			Input(Const)
228 			Usage_Dig_InRange
229 			ReportCount(1)
230 			Input(Var|Abs)
231 			ReportSize(16)
232 			ReportCount(1)
233 			PushPop(
234 				UsagePage_GenericDesktop
235 				Unit(cm)
236 				UnitExponent(-1)
237 				PhysicalRange_i16(0, 160)
238 				LogicalRange_i16(0, 32767)
239 				Usage_GD_X
240 				Input(Var|Abs) // Bytes 2+3
241 				PhysicalRange_i16(0, 100)
242 				LogicalRange_i16(0, 32767)
243 				Usage_GD_Y
244 				Input(Var|Abs) // Bytes 4+5
245 			)
246 			UsagePage_Digitizers
247 			Usage_Dig_TipPressure
248 			LogicalRange_i16(0, 8191)
249 			Input(Var|Abs) // Byte 6+7
250 			// Two bytes padding so we don't need to change the report at all
251 			ReportSize(8)
252 			ReportCount(2)
253 			Input(Const) // Byte 6+7
254 		)
255 	)
256 };
257 
258 static const __u8 fixed_rdesc_vendor[] = {
259 	UsagePage_Digitizers
260 	Usage_Dig_Pen
261 	CollectionApplication(
262 		// Byte 0
263 		// We leave the pen on the vendor report ID
264 		ReportId(VENDOR_REPORT_ID)
265 		Usage_Dig_Pen
266 		CollectionPhysical(
267 			// Byte 1 are the buttons
268 			LogicalRange_i8(0, 1)
269 			ReportSize(1)
270 			Usage_Dig_TipSwitch
271 			Usage_Dig_BarrelSwitch
272 			Usage_Dig_SecondaryBarrelSwitch
273 			ReportCount(3)
274 			Input(Var|Abs)
275 			ReportCount(4) // Padding
276 			Input(Const)
277 			Usage_Dig_InRange
278 			ReportCount(1)
279 			Input(Var|Abs)
280 			ReportSize(16)
281 			ReportCount(1)
282 			PushPop(
283 				UsagePage_GenericDesktop
284 				Unit(cm)
285 				UnitExponent(-1)
286 				// Note: reported logical range differs
287 				// from the pen report ID for x and y
288 				LogicalRange_i16(0, 32000)
289 				PhysicalRange_i16(0, 160)
290 				// Bytes 2/3 in report
291 				Usage_GD_X
292 				Input(Var|Abs)
293 				LogicalRange_i16(0, 20000)
294 				PhysicalRange_i16(0, 100)
295 				// Bytes 4/5 in report
296 				Usage_GD_Y
297 				Input(Var|Abs)
298 			)
299 			// Bytes 6/7 in report
300 			LogicalRange_i16(0, 8192)
301 			Usage_Dig_TipPressure
302 			Input(Var|Abs)
303 		)
304 	)
305 	UsagePage_GenericDesktop
306 	Usage_GD_Keypad
307 	CollectionApplication(
308 		// Byte 0
309 		ReportId(PAD_REPORT_ID)
310 		LogicalRange_i8(0, 1)
311 		UsagePage_Digitizers
312 		Usage_Dig_TabletFunctionKeys
313 		CollectionPhysical(
314 			// Byte 1 are the buttons
315 			Usage_Dig_BarrelSwitch	 // BTN_STYLUS, needed so we get to be a tablet pad
316 			ReportCount(1)
317 			ReportSize(1)
318 			Input(Var|Abs)
319 			ReportCount(7) // Padding
320 			Input(Const)
321 			// Bytes 2/3 - x/y just exist so we get to be a tablet pad
322 			UsagePage_GenericDesktop
323 			Usage_GD_X
324 			Usage_GD_Y
325 			ReportCount(2)
326 			ReportSize(8)
327 			Input(Var|Abs)
328 			// Byte 4 is the button state
329 			UsagePage_Button
330 			UsageRange_i8(0x01, 0x6)
331 			LogicalRange_i8(0x0, 0x1)
332 			ReportCount(6)
333 			ReportSize(1)
334 			Input(Var|Abs)
335 			ReportCount(2)
336 			Input(Const)
337 			// Byte 5 is the wheel
338 			UsagePage_GenericDesktop
339 			Usage_GD_Wheel
340 			LogicalRange_i8(-1, 1)
341 			ReportCount(1)
342 			ReportSize(8)
343 			Input(Var|Rel)
344 		)
345 		// Make sure we match our original report length
346 		FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
347 	)
348 };
349 
350 static const __u8 disabled_rdesc_pen[] = {
351 	FixedSizeVendorReport(PEN_REPORT_LENGTH)
352 };
353 
354 static const __u8 disabled_rdesc_pad[] = {
355 	FixedSizeVendorReport(PAD_REPORT_LENGTH)
356 };
357 
358 SEC(HID_BPF_RDESC_FIXUP)
359 int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
360 {
361 	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
362 	__s32 rdesc_size = hctx->size;
363 	__u8 have_fw_id;
364 
365 	if (!data)
366 		return 0; /* EPERM check */
367 
368 	/* If we have a firmware ID and it matches our expected prefix, we
369 	 * disable the default pad/pen nodes. They won't send events
370 	 * but cause duplicate devices.
371 	 */
372 	have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
373 				      EXPECTED_FIRMWARE_ID,
374 				      sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
375 	if (rdesc_size == PAD_REPORT_DESCRIPTOR_LENGTH) {
376 		if (have_fw_id) {
377 			__builtin_memcpy(data, disabled_rdesc_pad, sizeof(disabled_rdesc_pad));
378 			return sizeof(disabled_rdesc_pad);
379 		}
380 
381 		__builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
382 		return sizeof(fixed_rdesc_pad);
383 	}
384 	if (rdesc_size == PEN_REPORT_DESCRIPTOR_LENGTH) {
385 		if (have_fw_id) {
386 			__builtin_memcpy(data, disabled_rdesc_pen, sizeof(disabled_rdesc_pen));
387 			return sizeof(disabled_rdesc_pen);
388 		}
389 
390 		__builtin_memcpy(data, fixed_rdesc_pen, sizeof(fixed_rdesc_pen));
391 		return sizeof(fixed_rdesc_pen);
392 	}
393 	/* Always fix the vendor mode so the tablet will work even if nothing sets
394 	 * the udev property (e.g. huion-switcher run manually)
395 	 */
396 	if (rdesc_size == VENDOR_REPORT_DESCRIPTOR_LENGTH) {
397 		__builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
398 		return sizeof(fixed_rdesc_vendor);
399 
400 	}
401 	return 0;
402 }
403 
404 SEC(HID_BPF_DEVICE_EVENT)
405 int BPF_PROG(inspiroy_2_fix_events, struct hid_bpf_ctx *hctx)
406 {
407 	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
408 
409 	if (!data)
410 		return 0; /* EPERM check */
411 
412 	/* Only sent if tablet is in default mode */
413 	if (data[0] == PAD_REPORT_ID) {
414 		/* Nicely enough, this device only supports one button down at a time so
415 		 * the reports are easy to match. Buttons numbered from the top
416 		 *   Button released: 03 00 00 00 00 00 00 00
417 		 *   Button 1: 03 00 05 00 00 00 00 00 -> b
418 		 *   Button 2: 03 00 0c 00 00 00 00 00 -> i
419 		 *   Button 3: 03 00 08 00 00 00 00 00 -> e
420 		 *   Button 4: 03 01 16 00 00 00 00 00 -> Ctrl S
421 		 *   Button 5: 03 00 2c 00 00 00 00 00 -> space
422 		 *   Button 6: 03 05 1d 00 00 00 00 00 -> Ctrl Alt Z
423 		 *
424 		 *   Wheel down: 03 01 2d 00 00 00 00 00 -> Ctrl -
425 		 *   Wheel up:   03 01 2e 00 00 00 00 00 -> Ctrl =
426 		 */
427 		__u8 button = 0;
428 		__u8 wheel = 0;
429 
430 		switch (data[1] << 8 | data[2]) {
431 		case 0x0000:
432 			break;
433 		case 0x0005:
434 			button = 1;
435 			break;
436 		case 0x000c:
437 			button = 2;
438 			break;
439 		case 0x0008:
440 			button = 3;
441 			break;
442 		case 0x0116:
443 			button = 4;
444 			break;
445 		case 0x002c:
446 			button = 5;
447 			break;
448 		case 0x051d:
449 			button = 6;
450 			break;
451 		case 0x012d:
452 			wheel = -1;
453 			break;
454 		case 0x012e:
455 			wheel = 1;
456 			break;
457 
458 		}
459 
460 		__u8 report[6] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, wheel, button};
461 
462 		__builtin_memcpy(data, report, sizeof(report));
463 		return sizeof(report);
464 	}
465 
466 	/* Nothing to do for the PEN_REPORT_ID, it's already mapped */
467 
468 	/* Only sent if tablet is in raw mode */
469 	if (data[0] == VENDOR_REPORT_ID) {
470 		/* Pad reports */
471 		if (data[1] & 0x20) {
472 			/* See fixed_rdesc_pad */
473 			struct pad_report {
474 				__u8 report_id;
475 				__u8 btn_stylus;
476 				__u8 x;
477 				__u8 y;
478 				__u8 buttons;
479 				__u8 wheel;
480 			} __attribute__((packed)) *pad_report;
481 			__u8 wheel = 0;
482 
483 			/* Wheel report */
484 			if (data[1] == 0xf1) {
485 				if (data[5] == 2)
486 					wheel = 0xff;
487 				else
488 					wheel = data[5];
489 			} else {
490 				/* data[4] are the buttons, mapped correctly */
491 				last_button_state = data[4];
492 				wheel = 0; // wheel
493 			}
494 
495 			pad_report = (struct pad_report *)data;
496 
497 			pad_report->report_id = PAD_REPORT_ID;
498 			pad_report->btn_stylus = 0;
499 			pad_report->x = 0;
500 			pad_report->y = 0;
501 			pad_report->buttons = last_button_state;
502 			pad_report->wheel = wheel;
503 
504 			return sizeof(struct pad_report);
505 		}
506 
507 		/* Pen reports need nothing done */
508 	}
509 
510 	return 0;
511 }
512 
513 HID_BPF_OPS(inspiroy_2) = {
514 	.hid_device_event = (void *)inspiroy_2_fix_events,
515 	.hid_rdesc_fixup = (void *)hid_fix_rdesc,
516 };
517 
518 SEC("syscall")
519 int probe(struct hid_bpf_probe_args *ctx)
520 {
521 	switch (ctx->rdesc_size) {
522 	case PAD_REPORT_DESCRIPTOR_LENGTH:
523 	case PEN_REPORT_DESCRIPTOR_LENGTH:
524 	case VENDOR_REPORT_DESCRIPTOR_LENGTH:
525 		ctx->retval = 0;
526 		break;
527 	default:
528 		ctx->retval = -EINVAL;
529 	}
530 
531 	return 0;
532 }
533 
534 char _license[] SEC("license") = "GPL";
535