aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/mon/mon_text.c
blob: 755a4570477fdeb5a5f3fd714df3a7500948903e (plain)
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
/*
 * The USB Monitor, inspired by Dave Harding's USBMon.
 *
 * This is a text format reader.
 */

#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/usb.h>
#include <linux/time.h>
#include <asm/uaccess.h>

#include "usb_mon.h"

/*
 * No, we do not want arbitrarily long data strings.
 * Use the binary interface if you want to capture bulk data!
 */
#define DATA_MAX  32

/*
 * This limit exists to prevent OOMs when the user process stops reading.
 */
#define EVENT_MAX  25

#define PRINTF_DFL  120

struct mon_event_text {
	struct list_head e_link;
	int type;		/* submit, complete, etc. */
	unsigned int pipe;	/* Pipe */
	unsigned long id;	/* From pointer, most of the time */
	unsigned int tstamp;
	int length;		/* Depends on type: xfer length or act length */
	int status;
	char data_flag;
	unsigned char data[DATA_MAX];
};

#define SLAB_NAME_SZ  30
struct mon_reader_text {
	kmem_cache_t *e_slab;
	int nevents;
	struct list_head e_list;
	struct mon_reader r;	/* In C, parent class can be placed anywhere */

	wait_queue_head_t wait;
	int printf_size;
	char *printf_buf;
	struct semaphore printf_lock;

	char slab_name[SLAB_NAME_SZ];
};

static void mon_text_ctor(void *, kmem_cache_t *, unsigned long);
static void mon_text_dtor(void *, kmem_cache_t *, unsigned long);

/*
 * mon_text_submit
 * mon_text_complete
 *
 * May be called from an interrupt.
 *
 * This is called with the whole mon_bus locked, so no additional lock.
 */

static inline char mon_text_get_data(struct mon_event_text *ep, struct urb *urb,
    int len, char ev_type)
{
	int pipe = urb->pipe;
	unsigned char *data;

	/*
	 * The check to see if it's safe to poke at data has an enormous
	 * number of corner cases, but it seems that the following is
	 * more or less safe.
	 *
	 * We do not even try to look transfer_buffer, because it can
	 * contain non-NULL garbage in case the upper level promised to
	 * set DMA for the HCD.
	 */
	if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
		return 'D';

	if (len <= 0)
		return 'L';

	if ((data = urb->transfer_buffer) == NULL)
		return 'Z';	/* '0' would be not as pretty. */

	/*
	 * Bulk is easy to shortcut reliably. 
	 * XXX Control needs setup packet taken.
	 * XXX Other pipe types need consideration. Currently, we overdo it
	 * and collect garbage for them: better more than less.
	 */
	if (usb_pipebulk(pipe) || usb_pipecontrol(pipe)) {
		if (usb_pipein(pipe)) {
			if (ev_type == 'S')
				return '<';
		} else {
			if (ev_type == 'C')
				return '>';
		}
	}

	if (len >= DATA_MAX)
		len = DATA_MAX;
	memcpy(ep->data, urb->transfer_buffer, len);
	return 0;
}

static inline unsigned int mon_get_timestamp(void)
{
	struct timeval tval;
	unsigned int stamp;

	do_gettimeofday(&tval);
	stamp = tval.tv_sec & 0xFFFF;	/* 2^32 = 4294967296. Limit to 4096s. */
	stamp = stamp * 1000000 + tval.tv_usec;
	return stamp;
}

static void mon_text_event(struct mon_reader_text *rp, struct urb *urb,
    char ev_type)
{
	struct mon_event_text *ep;
	unsigned int stamp;

	stamp = mon_get_timestamp();

	if (rp->nevents >= EVENT_MAX ||
	    (ep = kmem_cache_alloc(rp->e_slab, SLAB_ATOMIC)) == NULL) {
		rp->r.m_bus->cnt_text_lost++;
		return;
	}

	ep->type = ev_type;
	ep->pipe = urb->pipe;
	ep->id = (unsigned long) urb;
	ep->tstamp = stamp;
	ep->length = (ev_type == 'S') ?
	    urb->transfer_buffer_length : urb->actual_length;
	/* Collecting status makes debugging sense for submits, too */
	ep->status = urb->status;

	ep->data_flag = mon_text_get_data(ep, urb, ep->length, ev_type);

	rp->nevents++;
	list_add_tail(&ep->e_link, &rp->e_list);
	wake_up(&rp->wait);
}

static void mon_text_submit(void *data, struct urb *urb)
{
	struct mon_reader_text *rp = data;
	mon_text_event(rp, urb, 'S');
}

static void mon_text_complete(void *data, struct urb *urb)
{
	struct mon_reader_text *rp = data;
	mon_text_event(rp, urb, 'C');
}

/*
 * Fetch next event from the circular buffer.
 */
static struct mon_event_text *mon_text_fetch(struct mon_reader_text *rp,
    struct mon_bus *mbus)
{
	struct list_head *p;
	unsigned long flags;

	spin_lock_irqsave(&mbus->lock, flags);
	if (list_empty(&rp->e_list)) {
		spin_unlock_irqrestore(&mbus->lock, flags);
		return NULL;
	}
	p = rp->e_list.next;
	list_del(p);
	--rp->nevents;
	spin_unlock_irqrestore(&mbus->lock, flags);
	return list_entry(p, struct mon_event_text, e_link);
}

/*
 */
static int mon_text_open(struct inode *inode, struct file *file)
{
	struct mon_bus *mbus;
	struct usb_bus *ubus;
	struct mon_reader_text *rp;
	int rc;

	down(&mon_lock);
	mbus = inode->u.generic_ip;
	ubus = mbus->u_bus;

	rp = kmalloc(sizeof(struct mon_reader_text), GFP_KERNEL);
	if (rp == NULL) {
		rc = -ENOMEM;
		goto err_alloc;
	}
	memset(rp, 0, sizeof(struct mon_reader_text));
	INIT_LIST_HEAD(&rp->e_list);
	init_waitqueue_head(&rp->wait);
	init_MUTEX(&rp->printf_lock);

	rp->printf_size = PRINTF_DFL;
	rp->printf_buf = kmalloc(rp->printf_size, GFP_KERNEL);
	if (rp->printf_buf == NULL) {
		rc = -ENOMEM;
		goto err_alloc_pr;
	}

	rp->r.m_bus = mbus;
	rp->r.r_data = rp;
	rp->r.rnf_submit = mon_text_submit;
	rp->r.rnf_complete = mon_text_complete;

	snprintf(rp->slab_name, SLAB_NAME_SZ, "mon%dt_%lx", ubus->busnum,
	    (long)rp);
	rp->e_slab = kmem_cache_create(rp->slab_name,
	    sizeof(struct mon_event_text), sizeof(long), 0,
	    mon_text_ctor, mon_text_dtor);
	if (rp->e_slab == NULL) {
		rc = -ENOMEM;
		goto err_slab;
	}

	mon_reader_add(mbus, &rp->r);

	file->private_data = rp;
	up(&mon_lock);
	return 0;

// err_busy:
//	kmem_cache_destroy(rp->e_slab);
err_slab:
	kfree(rp->printf_buf);
err_alloc_pr:
	kfree(rp);
err_alloc:
	up(&mon_lock);
	return rc;
}

/*
 * For simplicity, we read one record in one system call and throw out
 * what does not fit. This means that the following does not work:
 *   dd if=/dbg/usbmon/0t bs=10
 * Also, we do not allow seeks and do not bother advancing the offset.
 */
static ssize_t mon_text_read(struct file *file, char __user *buf,
				size_t nbytes, loff_t *ppos)
{
	struct mon_reader_text *rp = file->private_data;
	struct mon_bus *mbus = rp->r.m_bus;
	DECLARE_WAITQUEUE(waita, current);
	struct mon_event_text *ep;
	int cnt, limit;
	char *pbuf;
	char udir, utype;
	int data_len, i;

	add_wait_queue(&rp->wait, &waita);
	set_current_state(TASK_INTERRUPTIBLE);
	while ((ep = mon_text_fetch(rp, mbus)) == NULL) {
		if (file->f_flags & O_NONBLOCK) {
			set_current_state(TASK_RUNNING);
			remove_wait_queue(&rp->wait, &waita);
			return -EWOULDBLOCK;	/* Same as EAGAIN in Linux */
		}
		/*
		 * We do not count nwaiters, because ->release is supposed
		 * to be called when all openers are gone only.
		 */
		schedule();
		if (signal_pending(current)) {
			remove_wait_queue(&rp->wait, &waita);
			return -EINTR;
		}
		set_current_state(TASK_INTERRUPTIBLE);
	}
	set_current_state(TASK_RUNNING);
	remove_wait_queue(&rp->wait, &waita);

	down(&rp->printf_lock);
	cnt = 0;
	pbuf = rp->printf_buf;
	limit = rp->printf_size;

	udir = usb_pipein(ep->pipe) ? 'i' : 'o';
	switch (usb_pipetype(ep->pipe)) {
	case PIPE_ISOCHRONOUS:	utype = 'Z'; break;
	case PIPE_INTERRUPT:	utype = 'I'; break;
	case PIPE_CONTROL:	utype = 'C'; break;
	default: /* PIPE_BULK */  utype = 'B';
	}
	cnt += snprintf(pbuf + cnt, limit - cnt,
	    "%lx %u %c %c%c:%03u:%02u %d %d",
	    ep->id, ep->tstamp, ep->type,
	    utype, udir, usb_pipedevice(ep->pipe), usb_pipeendpoint(ep->pipe),
	    ep->status, ep->length);

	if ((data_len = ep->length) > 0) {
		if (ep->data_flag == 0) {
			cnt += snprintf(pbuf + cnt, limit - cnt, " =");
			if (data_len >= DATA_MAX)
				data_len = DATA_MAX;
			for (i = 0; i < data_len; i++) {
				if (i % 4 == 0) {
					cnt += snprintf(pbuf + cnt, limit - cnt,
					    " ");
				}
				cnt += snprintf(pbuf + cnt, limit - cnt,
				    "%02x", ep->data[i]);
			}
			cnt += snprintf(pbuf + cnt, limit - cnt, "\n");
		} else {
			cnt += snprintf(pbuf + cnt, limit - cnt,
			    " %c\n", ep->data_flag);
		}
	} else {
		cnt += snprintf(pbuf + cnt, limit - cnt, "\n");
	}

	if (copy_to_user(buf, rp->printf_buf, cnt))
		cnt = -EFAULT;
	up(&rp->printf_lock);
	kmem_cache_free(rp->e_slab, ep);
	return cnt;
}

static int mon_text_release(struct inode *inode, struct file *file)
{
	struct mon_reader_text *rp = file->private_data;
	struct mon_bus *mbus;
	/* unsigned long flags; */
	struct list_head *p;
	struct mon_event_text *ep;

	down(&mon_lock);
	mbus = inode->u.generic_ip;

	if (mbus->nreaders <= 0) {
		printk(KERN_ERR TAG ": consistency error on close\n");
		up(&mon_lock);
		return 0;
	}
	mon_reader_del(mbus, &rp->r);

	/*
	 * In theory, e_list is protected by mbus->lock. However,
	 * after mon_reader_del has finished, the following is the case:
	 *  - we are not on reader list anymore, so new events won't be added;
	 *  - whole mbus may be dropped if it was orphaned.
	 * So, we better not touch mbus.
	 */
	/* spin_lock_irqsave(&mbus->lock, flags); */
	while (!list_empty(&rp->e_list)) {
		p = rp->e_list.next;
		ep = list_entry(p, struct mon_event_text, e_link);
		list_del(p);
		--rp->nevents;
		kmem_cache_free(rp->e_slab, ep);
	}
	/* spin_unlock_irqrestore(&mbus->lock, flags); */

	kmem_cache_destroy(rp->e_slab);
	kfree(rp->printf_buf);
	kfree(rp);

	up(&mon_lock);
	return 0;
}

struct file_operations mon_fops_text = {
	.owner =	THIS_MODULE,
	.open =		mon_text_open,
	.llseek =	no_llseek,
	.read =		mon_text_read,
	/* .write =	mon_text_write, */
	/* .poll =		mon_text_poll, */
	/* .ioctl =	mon_text_ioctl, */
	.release =	mon_text_release,
};

/*
 * Slab interface: constructor.
 */
static void mon_text_ctor(void *mem, kmem_cache_t *slab, unsigned long sflags)
{
	/*
	 * Nothing to initialize. No, really!
	 * So, we fill it with garbage to emulate a reused object.
	 */
	memset(mem, 0xe5, sizeof(struct mon_event_text));
}

static void mon_text_dtor(void *mem, kmem_cache_t *slab, unsigned long sflags)
{
	;
}