aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/input/hiddev.c
blob: 70477f02cc29fc49d93bf9f808ce56bb72b19b3b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
#define TSI108_ETH_PORT_NUM		2
#define TSI108_PBM_PORT			2
#define TSI108_SDRAM_PORT		4

#define TSI108_MAC_CFG1			(0x000)
#define TSI108_MAC_CFG1_SOFTRST		(1 << 31)
#define TSI108_MAC_CFG1_LOOPBACK	(1 << 8)
#define TSI108_MAC_CFG1_RXEN		(1 << 2)
#define TSI108_MAC_CFG1_TXEN		(1 << 0)

#define TSI108_MAC_CFG2			(0x004)
#define TSI108_MAC_CFG2_DFLT_PREAMBLE	(7 << 12)
#define TSI108_MAC_CFG2_IFACE_MASK	(3 << 8)
#define TSI108_MAC_CFG2_NOGIG		(1 << 8)
#define TSI108_MAC_CFG2_GIG		(2 << 8)
#define TSI108_MAC_CFG2_PADCRC		(1 << 2)
#define TSI108_MAC_CFG2_FULLDUPLEX	(1 << 0)

#define TSI108_MAC_MII_MGMT_CFG		(0x020)
#define TSI108_MAC_MII_MGMT_CLK		(7 << 0)
#define TSI108_MAC_MII_MGMT_RST		(1 << 31)

#define TSI108_MAC_MII_CMD		(0x024)
#define TSI108_MAC_MII_CMD_READ		(1 << 0)

#define TSI108_MAC_MII_ADDR		(0x028)
#define TSI108_MAC_MII_ADDR_REG		0
#define TSI108_MAC_MII_ADDR_PHY		8

#define TSI108_MAC_MII_DATAOUT		(0x02c)
#define TSI108_MAC_MII_DATAIN		(0x030)

#define TSI108_MAC_MII_IND		(0x034)
#define TSI108_MAC_MII_IND_NOTVALID	(1 << 2)
#define TSI108_MAC_MII_IND_SCANNING	(1 << 1)
#define TSI108_MAC_MII_IND_BUSY		(1 << 0)

#define TSI108_MAC_IFCTRL		(0x038)
#define TSI108_MAC_IFCTRL_PHYMODE	(1 << 24)

#define TSI108_MAC_ADDR1		(0x040)
#define TSI108_MAC_ADDR2		(0x044)

#define TSI108_STAT_RXBYTES		(0x06c)
#define TSI108_STAT_RXBYTES_CARRY	(1 << 24)

#define TSI108_STAT_RXPKTS		(0x070)
#define TSI108_STAT_RXPKTS_CARRY	(1 << 18)

#define TSI108_STAT_RXFCS		(0x074)
#define TSI108_STAT_RXFCS_CARRY		(1 << 12)

#define TSI108_STAT_RXMCAST		(0x078)
#define TSI108_STAT_RXMCAST_CARRY	(1 << 18)

#define TSI108_STAT_RXALIGN		(0x08c)
#define TSI108_STAT_RXALIGN_CARRY	(1 << 12)

#define TSI108_STAT_RXLENGTH		(0x090)
#define TSI108_STAT_RXLENGTH_CARRY	(1 << 12)

#define TSI108_STAT_RXRUNT		(0x09c)
#define TSI108_STAT_RXRUNT_CARRY	(1 << 12)

#define TSI108_STAT_RXJUMBO		(0x0a0)
#define TSI108_STAT_RXJUMBO_CARRY	(1 << 12)

#define TSI108_STAT_RXFRAG		(0x0a4)
#define TSI108_STAT_RXFRAG_CARRY	(1 << 12)

#define TSI108_STAT_RXJABBER		(0x0a8)
#define TSI108_STAT_RXJABBER_CARRY	(1 << 12)

#define TSI108_STAT_RXDROP		(0x0ac)
#define TSI108_STAT_RXDROP_CARRY	(1 << 12)

#define TSI108_STAT_TXBYTES		(0x0b0)
#define TSI108_STAT_TXBYTES_CARRY	(1 << 24)

#define TSI108_STAT_TXPKTS		(0x0b4)
#define TSI108_STAT_TXPKTS_CARRY	(1 << 18)

#define TSI108_STAT_TXEXDEF		(0x0c8)
#define TSI108_STAT_TXEXDEF_CARRY	(1 << 12)

#define TSI108_STAT_TXEXCOL		(0x0d8)
#define TSI108_STAT_TXEXCOL_CARRY	(1 << 12)

#define TSI108_STAT_TXTCOL		(0x0dc)
#define TSI108_STAT_TXTCOL_CARRY	(1 << 13)

#define TSI108_STAT_TXPAUSEDROP		(0x0e4)
#define TSI108_STAT_TXPAUSEDROP_CARRY	(1 << 12)

#define TSI108_STAT_CARRY1		(0x100)
#define TSI108_STAT_CARRY1_RXBYTES	(1 << 16)
#define TSI108_STAT_CARRY1_RXPKTS	(1 << 15)
#define TSI108_STAT_CARRY1_RXFCS	(1 << 14)
#define TSI108_STAT_CARRY1_RXMCAST	(1 << 13)
#define TSI108_STAT_CARRY1_RXALIGN	(1 << 8)
#define TSI108_STAT_CARRY1_RXLENGTH	(1 << 7)
#define TSI108_STAT_CARRY1_RXRUNT	(1 << 4)
#define TSI108_STAT_CARRY1_RXJUMBO	(1 << 3)
#define TSI108_STAT_CARRY1_RXFRAG	(1 << 2)
#define TSI108_STAT_CARRY1_RXJABBER	(1 << 1)
#define TSI108_STAT_CARRY1_RXDROP	(1 << 0)

#define TSI108_STAT_CARRY2		(0x104)
#define TSI108_STAT_CARRY2_TXBYTES	(1 << 13)
#define TSI108_STAT_CARRY2_TXPKTS	(1 << 12)
#define TSI108_STAT_CARRY2_TXEXDEF	(1 << 7)
#define TSI108_STAT_CARRY2_TXEXCOL	(1 << 3)
#define TSI108_STAT_CARRY2_TXTCOL	(1 << 2)
#define TSI108_STAT_CARRY2_TXPAUSE	(1 << 0)

#define TSI108_STAT_CARRYMASK1		(0x108)
#define TSI108_STAT_CARRYMASK2		(0x10c)

#define TSI108_EC_PORTCTRL		(0x200)
#define TSI108_EC_PORTCTRL_STATRST	(1 << 31)
#define TSI108_EC_PORTCTRL_STATEN	(1 << 28)
#define TSI108_EC_PORTCTRL_NOGIG	(1 << 18)
#define TSI108_EC_PORTCTRL_HALFDUPLEX	(1 << 16)

#define TSI108_EC_INTSTAT		(0x204)
#define TSI108_EC_INTMASK		(0x208)

#define TSI108_INT_ANY			(1 << 31)
#define TSI108_INT_SFN			(1 << 30)
#define TSI108_INT_RXIDLE		(1 << 29)
#define TSI108_INT_RXABORT		(1 << 28)
#define TSI108_INT_RXERROR		(1 << 27)
#define TSI108_INT_RXOVERRUN		(1 << 26)
#define TSI108_INT_RXTHRESH		(1 << 25)
#define TSI108_INT_RXWAIT		(1 << 24)
#define TSI108_INT_RXQUEUE0		(1 << 16)
#define TSI108_INT_STATCARRY		(1 << 15)
#define TSI108_INT_TXIDLE		(1 << 13)
#define TSI108_INT_TXABORT		(1 << 12)
#define TSI108_INT_TXERROR		(1 << 11)
#define TSI108_INT_TXUNDERRUN		(1 << 10)
#define TSI108_INT_TXTHRESH		(1 <<  9)
#define TSI108_INT_TXWAIT		(1 <<  8)
#define TSI108_INT_TXQUEUE0		(1 <<  0)

#define TSI108_EC_TXCFG			(0x220)
#define TSI108_EC_TXCFG_RST		(1 << 31)

#define TSI108_EC_TXCTRL		(0x224)
#define TSI108_EC_TXCTRL_IDLEINT	(1 << 31)
#define TSI108_EC_TXCTRL_ABORT		(1 << 30)
#define TSI108_EC_TXCTRL_GO		(1 << 15)
#define TSI108_EC_TXCTRL_QUEUE0		(1 <<  0)

#define TSI108_EC_TXSTAT		(0x228)
#define TSI108_EC_TXSTAT_ACTIVE		(1 << 15)
#define TSI108_EC_TXSTAT_QUEUE0		(1 << 0)

#define TSI108_EC_TXESTAT		(0x22c)
#define TSI108_EC_TXESTAT_Q0_ERR	(1 << 24)
#define TSI108_EC_TXESTAT_Q0_DESCINT	(1 << 16)
#define TSI108_EC_TXESTAT_Q0_EOF	(1 <<  8)
#define TSI108_EC_TXESTAT_Q0_EOQ	(1 <<  0)

#define TSI108_EC_TXERR			(0x278)

#define TSI108_EC_TXQ_CFG		(0x280)
#define TSI108_EC_TXQ_CFG_DESC_INT	(1 << 20)
#define TSI108_EC_TXQ_CFG_EOQ_OWN_INT	(1 << 19)
#define TSI108_EC_TXQ_CFG_WSWP		(1 << 11)
#define TSI108_EC_TXQ_CFG_BSWP		(1 << 10)
#define TSI108_EC_TXQ_CFG_SFNPORT	0

#define TSI108_EC_TXQ_BUFCFG		(0x284)
#define TSI108_EC_TXQ_BUFCFG_BURST8	(0 << 8)
#define TSI108_EC_TXQ_BUFCFG_BURST32	(1 << 8)
#define TSI108_EC_TXQ_BUFCFG_BURST128	(2 << 8)
#define TSI108_EC_TXQ_BUFCFG_BURST256	(3 << 8)
#define TSI108_EC_TXQ_BUFCFG_WSWP	(1 << 11)
#define TSI108_EC_TXQ_BUFCFG_BSWP	(1 << 10)
#define TSI108_EC_TXQ_BUFCFG_SFNPORT	0

#define TSI108_EC_TXQ_PTRLOW		(0x288)

#define TSI108_EC_TXQ_PTRHIGH		(0x28c)
#define TSI108_EC_TXQ_PTRHIGH_VALID	(1 << 31)

#define TSI108_EC_TXTHRESH		(0x230)
#define TSI108_EC_TXTHRESH_STARTFILL	0
#define TSI108_EC_TXTHRESH_STOPFILL	16

#define TSI108_EC_RXCFG			(0x320)
#define TSI108_EC_RXCFG_RST		(1 << 31)

#define TSI108_EC_RXSTAT		(0x328)
#define TSI108_EC_RXSTAT_ACTIVE		(1 << 15)
#define TSI108_EC_RXSTAT_QUEUE0		(1 << 0)

#define TSI108_EC_RXESTAT		(0x32c)
#define TSI108_EC_RXESTAT_Q0_ERR	(1 << 24)
#define TSI108_EC_RXESTAT_Q0_DESCINT	(1 << 16)
#define TSI108_EC_RXESTAT_Q0_EOF	(1 <<  8)
#define TSI108_EC_RXESTAT_Q0_EOQ	(1 <<  0)

#define TSI108_EC_HASHADDR		(0x360)
#define TSI108_EC_HASHADDR_AUTOINC	(1 << 31)
#define TSI108_EC_HASHADDR_DO1STREAD	(1 << 30)
#define TSI108_EC_HASHADDR_UNICAST	(0 <<  4)
#define TSI108_EC_HASHADDR_MCAST	(1 <<  4)

#define TSI108_EC_HASHDATA		(0x364)

#define TSI108_EC_RXQ_PTRLOW		(0x388)

#define TSI108_EC_RXQ_PTRHIGH		(0x38c)
#define TSI108_EC_RXQ_PTRHIGH_VALID	(1 << 31)

/* Station Enable -- accept packets destined for us */
</*
 *  Copyright (c) 2001 Paul Stewart
 *  Copyright (c) 2001 Vojtech Pavlik
 *
 *  HID char devices, giving access to raw HID device events.
 *
 */

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * Should you need to contact me, the author, you can do so either by
 * e-mail - mail your message to Paul Stewart <stewart@wetlogic.net>
 */

#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/smp_lock.h>
#include <linux/input.h>
#include <linux/usb.h>
#include "hid.h"
#include <linux/hiddev.h>

#ifdef CONFIG_USB_DYNAMIC_MINORS
#define HIDDEV_MINOR_BASE	0
#define HIDDEV_MINORS		256
#else
#define HIDDEV_MINOR_BASE	96
#define HIDDEV_MINORS		16
#endif
#define HIDDEV_BUFFER_SIZE	64

struct hiddev {
	int exist;
	int open;
	wait_queue_head_t wait;
	struct hid_device *hid;
	struct hiddev_list *list;
};

struct hiddev_list {
	struct hiddev_usage_ref buffer[HIDDEV_BUFFER_SIZE];
	int head;
	int tail;
	unsigned flags;
	struct fasync_struct *fasync;
	struct hiddev *hiddev;
	struct hiddev_list *next;
};

static struct hiddev *hiddev_table[HIDDEV_MINORS];

/*
 * Find a report, given the report's type and ID.  The ID can be specified
 * indirectly by REPORT_ID_FIRST (which returns the first report of the given
 * type) or by (REPORT_ID_NEXT | old_id), which returns the next report of the
 * given type which follows old_id.
 */
static struct hid_report *
hiddev_lookup_report(struct hid_device *hid, struct hiddev_report_info *rinfo)
{
	unsigned flags = rinfo->report_id & ~HID_REPORT_ID_MASK;
	struct hid_report_enum *report_enum;
	struct list_head *list;

	if (rinfo->report_type < HID_REPORT_TYPE_MIN ||
	    rinfo->report_type > HID_REPORT_TYPE_MAX) return NULL;

	report_enum = hid->report_enum +
		(rinfo->report_type - HID_REPORT_TYPE_MIN);

	switch (flags) {
	case 0: /* Nothing to do -- report_id is already set correctly */
		break;

	case HID_REPORT_ID_FIRST:
		list = report_enum->report_
#define TSI108_RX_OVER	(1 <
		if (list #define TSI108_RX_TRUNC	(1 << 11)	/* Packet truncated due to excess length */
#define TSI108_RX_CRC	(1 << 12)	/* Packet had a CRC error */
#define TSI108_RX_INT	(1 << 13)	/* Generate an IRQ after frag. processed */
#define TSI108_RX_OWN	(1 << 15)	/* Set if the device owns the descriptor */

#define TSI108_RX_SKB_SIZE 1536		/* The RX skb length */

typedef struct {
	u32 buf0;		/* Base address of buffer */
	u32 buf1;		/* Base address of buffer */
	u32 next0;		/* Address of next descriptor, if any */
	u32 next1;		/* Address of next descriptor, if any */
	u16 vlan;		/* VLAN of received packet, first frag only */
	u16 len;		/* Length of received fragment in bytes */
	u16 blen;		/* Length of buffer in bytes */
	u16 misc;		/* See TSI108_RX_* above */
	u32 reserved0;		/* reserved0 and reserved1 are added to make the desc */
	u32 reserved1;		/* 32-byte aligned */
} __attribute__ ((aligned(32))) rx_desc;

#endif				/* __TSI108_ETH_H */
>, struct hiddev_usage_ref *uref) { int i, j; struct hid_report *report; struct hid_report_enum *report_enum; struct hid_field *field; if (uref->report_type < HID_REPORT_TYPE_MIN || uref->report_type > HID_REPORT_TYPE_MAX) return NULL; report_enum = hid->report_enum + (uref->report_type - HID_REPORT_TYPE_MIN); list_for_each_entry(report, &report_enum->report_list, list) for (i = 0; i < report->maxfield; i++) { field = report->field[i]; for (j = 0; j < field->maxusage; j++) { if (field->usage[j].hid == uref->usage_code) { uref->report_id = report->id; uref->field_index = i; uref->usage_index = j; return field; } } } return NULL; } static void hiddev_send_event(struct hid_device *hid, struct hiddev_usage_ref *uref) { struct hiddev *hiddev = hid->hiddev; struct hiddev_list *list = hiddev->list; while (list) { if (uref->field_index != HID_FIELD_INDEX_NONE || (list->flags & HIDDEV_FLAG_REPORT) != 0) { list->buffer[list->head] = *uref; list->head = (list->head + 1) & (HIDDEV_BUFFER_SIZE - 1); kill_fasync(&list->fasync, SIGIO, POLL_IN); } list = list->next; } wake_up_interruptible(&hiddev->wait); } /* * This is where hid.c calls into hiddev to pass an event that occurred over * the interrupt pipe */ void hiddev_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value, struct pt_regs *regs) { unsigned type = field->report_type; struct hiddev_usage_ref uref; uref.report_type = (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT : ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT : ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE:0)); uref.report_id = field->report->id; uref.field_index = field->index; uref.usage_index = (usage - field->usage); uref.usage_code = usage->hid; uref.value = value; hiddev_send_event(hid, &uref); } void hiddev_report_event(struct hid_device *hid, struct hid_report *report) { unsigned type = report->type; struct hiddev_usage_ref uref; memset(&uref, 0, sizeof(uref)); uref.report_type = (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT : ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT : ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE:0)); uref.report_id = report->id; uref.field_index = HID_FIELD_INDEX_NONE; hiddev_send_event(hid, &uref); } /* * fasync file op */ static int hiddev_fasync(int fd, struct file *file, int on) { int retval; struct hiddev_list *list = file->private_data; retval = fasync_helper(fd, file, on, &list->fasync); return retval < 0 ? retval : 0; } /* * release file op */ static int hiddev_release(struct inode * inode, struct file * file) { struct hiddev_list *list = file->private_data; struct hiddev_list **listptr; listptr = &list->hiddev->list; hiddev_fasync(-1, file, 0); while (*listptr && (*listptr != list)) listptr = &((*listptr)->next); *listptr = (*listptr)->next; if (!--list->hiddev->open) { if (list->hiddev->exist) hid_close(list->hiddev->hid); else kfree(list->hiddev); } kfree(list); return 0; } /* * open file op */ static int hiddev_open(struct inode * inode, struct file * file) { struct hiddev_list *list; int i = iminor(inode) - HIDDEV_MINOR_BASE; if (i >= HIDDEV_MINORS || !hiddev_table[i]) return -ENODEV; if (!(list = kzalloc(sizeof(struct hiddev_list), GFP_KERNEL))) return -ENOMEM; list->hiddev = hiddev_table[i]; list->next = hiddev_table[i]->list; hiddev_table[i]->list = list; file->private_data = list; if (!list->hiddev->open++) if (list->hiddev->exist) hid_open(hiddev_table[i]->hid); return 0; } /* * "write" file op */ static ssize_t hiddev_write(struct file * file, const char __user * buffer, size_t count, loff_t *ppos) { return -EINVAL; } /* * "read" file op */ static ssize_t hiddev_read(struct file * file, char __user * buffer, size_t count, loff_t *ppos) { DECLARE_WAITQUEUE(wait, current); struct hiddev_list *list = file->private_data; int event_size; int retval = 0; event_size = ((list->flags & HIDDEV_FLAG_UREF) != 0) ? sizeof(struct hiddev_usage_ref) : sizeof(struct hiddev_event); if (count < event_size) return 0; while (retval == 0) { if (list->head == list->tail) { add_wait_queue(&list->hiddev->wait, &wait); set_current_state(TASK_INTERRUPTIBLE); while (list->head == list->tail) { if (file->f_flags & O_NONBLOCK) { retval = -EAGAIN; break; } if (signal_pending(current)) { retval = -ERESTARTSYS; break; } if (!list->hiddev->exist) { retval = -EIO; break; } schedule(); set_current_state(TASK_INTERRUPTIBLE); } set_current_state(TASK_RUNNING); remove_wait_queue(&list->hiddev->wait, &wait); } if (retval) return retval; while (list->head != list->tail && retval + event_size <= count) { if ((list->flags & HIDDEV_FLAG_UREF) == 0) { if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE) { struct hiddev_event event; event.hid = list->buffer[list->tail].usage_code; event.value = list->buffer[list->tail].value; if (copy_to_user(buffer + retval, &event, sizeof(struct hiddev_event))) return -EFAULT; retval += sizeof(struct hiddev_event); } } else { if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE || (list->flags & HIDDEV_FLAG_REPORT) != 0) { if (copy_to_user(buffer + retval, list->buffer + list->tail, sizeof(struct hiddev_usage_ref))) return -EFAULT; retval += sizeof(struct hiddev_usage_ref); } } list->tail = (list->tail + 1) & (HIDDEV_BUFFER_SIZE - 1); } } return retval; } /* * "poll" file op * No kernel lock - fine */ static unsigned int hiddev_poll(struct file *file, poll_table *wait) { struct hiddev_list *list = file->private_data; poll_wait(file, &list->hiddev->wait, wait); if (list->head != list->tail) return POLLIN | POLLRDNORM; if (!list->hiddev->exist) return POLLERR | POLLHUP; return 0; } /* * "ioctl" file op */ static int hiddev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { struct hiddev_list *list = file->private_data; struct hiddev *hiddev = list->hiddev; struct hid_device *hid = hiddev->hid; struct usb_device *dev = hid->dev; struct hiddev_collection_info cinfo; struct hiddev_report_info rinfo; struct hiddev_field_info finfo; struct hiddev_usage_ref_multi *uref_multi=NULL; struct hiddev_usage_ref *uref; struct hiddev_devinfo dinfo; struct hid_report *report; struct hid_field *field; void __user *user_arg = (void __user *)arg; int i; if (!hiddev->exist) return -EIO; switch (cmd) { case HIDIOCGVERSION: return put_user(HID_VERSION, (int __user *)arg); case HIDIOCAPPLICATION: if (arg < 0 || arg >= hid->maxapplication) return -EINVAL; for (i = 0; i < hid->maxcollection; i++) if (hid->collection[i].type == HID_COLLECTION_APPLICATION && arg-- == 0) break; if (i == hid->maxcollection) return -EINVAL; return hid->collection[i].usage; case HIDIOCGDEVINFO: dinfo.bustype = BUS_USB; dinfo.busnum = dev->bus->busnum; dinfo.devnum = dev->devnum; dinfo.ifnum = hid->ifnum; dinfo.vendor = le16_to_cpu(dev->descriptor.idVendor); dinfo.product = le16_to_cpu(dev->descriptor.idProduct); dinfo.version = le16_to_cpu(dev->descriptor.bcdDevice); dinfo.num_applications = hid->maxapplication; if (copy_to_user(user_arg, &dinfo, sizeof(dinfo))) return -EFAULT; return 0; case HIDIOCGFLAG: if (put_user(list->flags, (int __user *)arg)) return -EFAULT; return 0; case HIDIOCSFLAG: { int newflags; if (get_user(newflags, (int __user *)arg)) return -EFAULT; if ((newflags & ~HIDDEV_FLAGS) != 0 || ((newflags & HIDDEV_FLAG_REPORT) != 0 && (newflags & HIDDEV_FLAG_UREF) == 0)) return -EINVAL; list->flags = newflags; return 0; } case HIDIOCGSTRING: { int idx, len; char *buf; if (get_user(idx, (int __user *)arg)) return -EFAULT; if ((buf = kmalloc(HID_STRING_SIZE, GFP_KERNEL)) == NULL) return -ENOMEM; if ((len = usb_string(dev, idx, buf, HID_STRING_SIZE-1)) < 0) { kfree(buf); return -EINVAL; } if (copy_to_user(user_arg+sizeof(int), buf, len+1)) { kfree(buf); return -EFAULT; } kfree(buf); return len; } case HIDIOCINITREPORT: hid_init_reports(hid); return 0; case HIDIOCGREPORT: if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) return -EFAULT; if (rinfo.report_type == HID_REPORT_TYPE_OUTPUT) return -EINVAL; if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) return -EINVAL; hid_submit_report(hid, report, USB_DIR_IN); hid_wait_io(hid); return 0; case HIDIOCSREPORT: if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) return -EFAULT; if (rinfo.report_type == HID_REPORT_TYPE_INPUT) return -EINVAL; if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) return -EINVAL; hid_submit_report(hid, report, USB_DIR_OUT); hid_wait_io(hid); return 0; case HIDIOCGREPORTINFO: if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) return -EFAULT; if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) return -EINVAL; rinfo.num_fields = report->maxfield; if (copy_to_user(user_arg, &rinfo, sizeof(rinfo))) return -EFAULT; return 0; case HIDIOCGFIELDINFO: if (copy_from_user(&finfo, user_arg, sizeof(finfo))) return -EFAULT; rinfo.report_type = finfo.report_type; rinfo.report_id = finfo.report_id; if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) return -EINVAL; if (finfo.field_index >= report->maxfield) return -EINVAL; field = report->field[finfo.field_index]; memset(&finfo, 0, sizeof(finfo)); finfo.report_type = rinfo.report_type; finfo.report_id = rinfo.report_id; finfo.field_index = field->report_count - 1; finfo.maxusage = field->maxusage; finfo.flags = field->flags; finfo.physical = field->physical; finfo.logical = field->logical; finfo.application = field->application; finfo.logical_minimum = field->logical_minimum; finfo.logical_maximum = field->logical_maximum; finfo.physical_minimum = field->physical_minimum; finfo.physical_maximum = field->physical_maximum; finfo.unit_exponent = field->unit_exponent; finfo.unit = field->unit; if (copy_to_user(user_arg, &finfo, sizeof(finfo))) return -EFAULT; return 0; case HIDIOCGUCODE: uref_multi = kmalloc(sizeof(struct hiddev_usage_ref_multi), GFP_KERNEL); if (!uref_multi) return -ENOMEM; uref = &uref_multi->uref; if (copy_from_user(uref, user_arg, sizeof(*uref))) goto fault; rinfo.report_type = uref->report_type; rinfo.report_id = uref->report_id; if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) goto inval; if (uref->field_index >= report->maxfield) goto inval; field = report->field[uref->field_index]; if (uref->usage_index >= field->maxusage) goto inval; uref->usage_code = field->usage[uref->usage_index].hid; if (copy_to_user(user_arg, uref, sizeof(*uref))) goto fault; kfree(uref_multi); return 0; case HIDIOCGUSAGE: case HIDIOCSUSAGE: case HIDIOCGUSAGES: case HIDIOCSUSAGES: case HIDIOCGCOLLECTIONINDEX: uref_multi = kmalloc(sizeof(struct hiddev_usage_ref_multi), GFP_KERNEL); if (!uref_multi) return -ENOMEM; uref = &uref_multi->uref; if (cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) { if (copy_from_user(uref_multi, user_arg, sizeof(*uref_multi))) goto fault; } else { if (copy_from_user(uref, user_arg, sizeof(*uref))) goto fault; } if (cmd != HIDIOCGUSAGE && cmd != HIDIOCGUSAGES && uref->report_type == HID_REPORT_TYPE_INPUT) goto inval; if (uref->report_id == HID_REPORT_ID_UNKNOWN) { field = hiddev_lookup_usage(hid, uref); if (field == NULL) goto inval; } else { rinfo.report_type = uref->report_type; rinfo.report_id = uref->report_id; if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) goto inval; if (uref->field_index >= report->maxfield) goto inval; field = report->field[uref->field_index]; if (cmd == HIDIOCGCOLLECTIONINDEX) { if (uref->usage_index >= field->maxusage) goto inval; } else if (uref->usage_index >= field->report_count) goto inval; else if ((cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) && (uref_multi->num_values > HID_MAX_MULTI_USAGES || uref->usage_index + uref_multi->num_values > field->report_count)) goto inval; } switch (cmd) { case HIDIOCGUSAGE: uref->value = field->value[uref->usage_index]; if (copy_to_user(user_arg, uref, sizeof(*uref))) goto fault; goto goodreturn; case HIDIOCSUSAGE: field->value[uref->usage_index] = uref->value; goto goodreturn; case HIDIOCGCOLLECTIONINDEX: kfree(uref_multi); return field->usage[uref->usage_index].collection_index; case HIDIOCGUSAGES: for (i = 0; i < uref_multi->num_values; i++) uref_multi->values[i] = field->value[uref->usage_index + i]; if (copy_to_user(user_arg, uref_multi, sizeof(*uref_multi))) goto fault; goto goodreturn; case HIDIOCSUSAGES: for (i = 0; i < uref_multi->num_values; i++) field->value[uref->usage_index + i] = uref_multi->values[i]; goto goodreturn; } goodreturn: kfree(uref_multi); return 0; fault: kfree(uref_multi); return -EFAULT; inval: kfree(uref_multi); return -EINVAL; case HIDIOCGCOLLECTIONINFO: if (copy_from_user(&cinfo, user_arg, sizeof(cinfo))) return -EFAULT; if (cinfo.index >= hid->maxcollection) return -EINVAL; cinfo.type = hid->collection[cinfo.index].type; cinfo.usage = hid->collection[cinfo.index].usage; cinfo.level = hid->collection[cinfo.index].level; if (copy_to_user(user_arg, &cinfo, sizeof(cinfo))) return -EFAULT; return 0; default: if (_IOC_TYPE(cmd) != 'H' || _IOC_DIR(cmd) != _IOC_READ) return -EINVAL; if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGNAME(0))) { int len; if (!hid->name) return 0; len = strlen(hid->name) + 1; if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); return copy_to_user(user_arg, hid->name, len) ? -EFAULT : len; } if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGPHYS(0))) { int len; if (!hid->phys) return 0; len = strlen(hid->phys) + 1; if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); return copy_to_user(user_arg, hid->phys, len) ? -EFAULT : len; } } return -EINVAL; } static struct file_operations hiddev_fops = { .owner = THIS_MODULE, .read = hiddev_read, .write = hiddev_write, .poll = hiddev_poll, .open = hiddev_open, .release = hiddev_release, .ioctl = hiddev_ioctl, .fasync = hiddev_fasync, }; static struct usb_class_driver hiddev_class = { .name = "hiddev%d", .fops = &hiddev_fops, .minor_base = HIDDEV_MINOR_BASE, }; /* * This is where hid.c calls us to connect a hid device to the hiddev driver */ int hiddev_connect(struct hid_device *hid) { struct hiddev *hiddev; int i; int retval; for (i = 0; i < hid->maxcollection; i++) if (hid->collection[i].type == HID_COLLECTION_APPLICATION && !IS_INPUT_APPLICATION(hid->collection[i].usage)) break; if (i == hid->maxcollection && (hid->quirks & HID_QUIRK_HIDDEV) == 0) return -1; if (!(hiddev = kzalloc(sizeof(struct hiddev), GFP_KERNEL))) return -1; retval = usb_register_dev(hid->intf, &hiddev_class); if (retval) { err("Not able to get a minor for this device."); kfree(hiddev); return -1; } init_waitqueue_head(&hiddev->wait); hiddev_table[hid->intf->minor - HIDDEV_MINOR_BASE] = hiddev; hiddev->hid = hid; hiddev->exist = 1; hid->minor = hid->intf->minor; hid->hiddev = hiddev; return 0; } /* * This is where hid.c calls us to disconnect a hiddev device from the * corresponding hid device (usually because the usb device has disconnected) */ static struct usb_class_driver hiddev_class; void hiddev_disconnect(struct hid_device *hid) { struct hiddev *hiddev = hid->hiddev; hiddev->exist = 0; hiddev_table[hiddev->hid->minor - HIDDEV_MINOR_BASE] = NULL; usb_deregister_dev(hiddev->hid->intf, &hiddev_class); if (hiddev->open) { hid_close(hiddev->hid); wake_up_interruptible(&hiddev->wait); } else { kfree(hiddev); } } /* Currently this driver is a USB driver. It's not a conventional one in * the sense that it doesn't probe at the USB level. Instead it waits to * be connected by HID through the hiddev_connect / hiddev_disconnect * routines. The reason to register as a USB device is to gain part of the * minor number space from the USB major. * * In theory, should the HID code be generalized to more than one physical * medium (say, IEEE 1384), this driver will probably need to register its * own major number, and in doing so, no longer need to register with USB. * At that point the probe routine and hiddev_driver struct below will no * longer be useful. */ /* We never attach in this manner, and rely on HID to connect us. This * is why there is no disconnect routine defined in the usb_driver either. */ static int hiddev_usbd_probe(struct usb_interface *intf, const struct usb_device_id *hiddev_info) { return -ENODEV; } static /* const */ struct usb_driver hiddev_driver = { .name = "hiddev", .probe = hiddev_usbd_probe, }; int __init hiddev_init(void) { return usb_register(&hiddev_driver); } void hiddev_exit(void) { usb_deregister(&hiddev_driver); }