Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
  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
/*
 * shmiq.c: shared memory input queue driver
 * written 1997 Miguel de Icaza (miguel@nuclecu.unam.mx)
 *
 * We implement /dev/shmiq, /dev/qcntlN here
 * this is different from IRIX that has shmiq as a misc
 * streams device and the and qcntl devices as a major device.
 *
 * minor number 0 implements /dev/shmiq,
 * any other number implements /dev/qcntl${minor-1}
 *
 * /dev/shmiq is used by the X server for two things:
 * 
 *    1. for I_LINK()ing trough ioctl the file handle of a
 *       STREAMS device.
 *
 *    2. To send STREAMS-commands to the devices with the
 *       QIO ioctl interface.
 *
 * I have not yet figured how to make multiple X servers share
 * /dev/shmiq for having different servers running.  So, for now
 * I keep a kernel-global array of inodes that are pushed into
 * /dev/shmiq.
 *
 * /dev/qcntlN is used by the X server for two things:
 *
 *    1. Issuing the QIOCATTACH for mapping the shared input
 *       queue into the address space of the X server (yeah, yeah,
 *       I did not invent this interface).
 *
 *    2. used by select.  I bet it is used for checking for events on
 *       the queue.
 *
 * Now the problem is that there does not seem anything that
 * establishes a connection between /dev/shmiq and the qcntlN file.  I
 * need an strace from an X server that runs on a machine with more
 * than one keyboard.  And this is a problem since the file handles
 * are pushed in /dev/shmiq, while the events should be dispatched to
 * the /dev/qcntlN device. 
 *
 * Until then, I just allow for 1 qcntl device.
 *
 */

#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/file.h>
#include <linux/interrupt.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
#include <linux/major.h>
#include <linux/smp_lock.h>

#include <asm/shmiq.h>
#include <asm/mman.h>
#include <asm/uaccess.h>
#include <asm/poll.h>
#include "graphics.h"

/* we are not really getting any more than a few files in the shmiq */
#define MAX_SHMIQ_DEVS 10

/*
 * One per X server running, not going to get very big.
 * Even if we have this we now assume just 1 /dev/qcntl can be
 * active, I need to find how this works on multi-headed machines.
 */
#define MAX_SHMI_QUEUES 4

static struct {
	int                 used;
	struct file         *filp;
	struct shmiqsetcpos cpos;
} shmiq_pushed_devices [MAX_SHMIQ_DEVS];

/* /dev/qcntlN attached memory regions, location and size of the event queue */
static struct {
	int    opened;		/* if this device has been opened */
	void   *shmiq_vaddr;	/* mapping in kernel-land */
	int    tail;		/* our copy of the shmiq->tail */
	int    events;
	int    mapped;
	
	struct wait_queue    *proc_list;
	struct fasync_struct *fasync;
} shmiqs [MAX_SHMI_QUEUES];

void
shmiq_push_event (struct shmqevent *e)
{
	struct sharedMemoryInputQueue *s;
	int    device = 0;	/* FIXME: here is the assumption /dev/shmiq == /dev/qcntl0 */
	int    tail_next;

	if (!shmiqs [device].mapped)
		return;
	s = shmiqs [device].shmiq_vaddr;

	s->flags = 0;
	if (s->tail != shmiqs [device].tail){
		s->flags |= SHMIQ_CORRUPTED;
		return;
	}
	tail_next = (s->tail + 1) % (shmiqs [device].events);
	
	if (tail_next == s->head){
		s->flags |= SHMIQ_OVERFLOW;
		return;
	}
	
	e->un.time = jiffies;
	s->events [s->tail] = *e;
	printk ("KERNEL: dev=%d which=%d type=%d flags=%d\n",
		e->data.device, e->data.which, e->data.type, e->data.flags);
	s->tail = tail_next;
	shmiqs [device].tail = tail_next;
	if (shmiqs [device].fasync)
		kill_fasync (shmiqs [device].fasync, SIGIO);
	wake_up_interruptible (&shmiqs [device].proc_list);
}

static int
shmiq_manage_file (struct file *filp)
{
	int i;

	if (!filp->f_op || !filp->f_op->ioctl)
		return -ENOSR;

	for (i = 0; i < MAX_SHMIQ_DEVS; i++){
		if (shmiq_pushed_devices [i].used)
			continue;
		if ((*filp->f_op->ioctl)(filp->f_dentry->d_inode, filp, SHMIQ_ON, i) != 0)
			return -ENOSR;
		shmiq_pushed_devices [i].used = 1;
		shmiq_pushed_devices [i].filp = filp;
		shmiq_pushed_devices [i].cpos.x = 0;
		shmiq_pushed_devices [i].cpos.y = 0;
		return i;
	}
	return -ENOSR;
}

static int
shmiq_forget_file (unsigned long fdes)
{
	struct file *filp;

	if (fdes > MAX_SHMIQ_DEVS)
		return -EINVAL;
	
	if (!shmiq_pushed_devices [fdes].used)
		return -EINVAL;

	filp = shmiq_pushed_devices [fdes].filp;
	if (filp){
		(*filp->f_op->ioctl)(filp->f_dentry->d_inode, filp, SHMIQ_OFF, 0);
		shmiq_pushed_devices [fdes].filp = 0;
		fput (filp);
	}
	shmiq_pushed_devices [fdes].used = 0;

	return 0;
}

static int
shmiq_sioc (int device, int cmd, struct strioctl *s)
{
	switch (cmd){
	case QIOCGETINDX:
		/*
		 * Ok, we just return the index they are providing us
		 */
		printk ("QIOCGETINDX: returning %d\n", *(int *)s->ic_dp);
		return 0;

	case QIOCIISTR: {
		struct muxioctl *mux = (struct muxioctl *) s->ic_dp;
		
		printk ("Double indirect ioctl: [%d, %x\n", mux->index, mux->realcmd);
		return -EINVAL;
	}

	case QIOCSETCPOS: {
		if (copy_from_user (&shmiq_pushed_devices [device].cpos, s->ic_dp,
				    sizeof (struct shmiqsetcpos)))
			return -EFAULT;
		return 0;
	}
	}
	printk ("Unknown I_STR request for shmiq device: 0x%x\n", cmd);
	return -EINVAL;
}

static int
shmiq_ioctl (struct inode *inode, struct file *f, unsigned int cmd, unsigned long arg)
{
	struct file *file;
	struct strioctl sioc;
	int v;

	switch (cmd){
		/*
		 * They are giving us the file descriptor for one
		 * of their streams devices
		 */

	case I_LINK:
		file = fget (arg);
		if (!file)
			goto bad_file;

		v = shmiq_manage_file (file);
		return v;

		/*
		 * Remove a device from our list of managed
		 * stream devices
		 */
	case I_UNLINK:
		v = shmiq_forget_file (arg);
		return v;
		
	case I_STR:
		v = get_sioc (&sioc, arg);
		if (v)
			return v;
		
		/* FIXME: This forces device = 0 */
		return shmiq_sioc (0, sioc.ic_cmd, &sioc);
	}

	return -EINVAL;
bad_file:
	unlock_kernel ();
	return -EBADF;
}

extern sys_munmap(unsigned long addr, size_t len);

static int
qcntl_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg, int minor)
{
	struct shmiqreq req;
	struct vm_area_struct *vma;
	int v;
	
	switch (cmd){
		/*
		 * The address space is already mapped as a /dev/zero
		 * mapping.  FIXME: check that /dev/zero is what the user
		 * had mapped before :-)
		 */
	case QIOCATTACH: {
		unsigned long vaddr;
		int s;
		
		v = verify_area (VERIFY_READ, (void *) arg, sizeof (struct shmiqreq));
		if (v)
			return v;
		if (copy_from_user (&req, (void *) arg, sizeof (req)))
			return -EFAULT;
		/* Do not allow to attach to another region if it has been already attached */
		if (shmiqs [minor].mapped){
			printk ("SHMIQ:The thingie is already mapped\n");
			return -EINVAL;
		}

		vaddr = (unsigned long) req.user_vaddr;
		vma = find_vma (current->mm, vaddr);
		if (!vma){
			printk ("SHMIQ: could not find %lx the vma\n", vaddr);
			return -EINVAL;
		}
		s = req.arg * sizeof (struct shmqevent) + sizeof (struct sharedMemoryInputQueue);
		v = sys_munmap (vaddr, s);
		do_mmap (filp, vaddr, s, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 0);
		shmiqs [minor].events = req.arg;
		shmiqs [minor].mapped = 1;
		return 0;
	}
	}
	return -EINVAL;
}

unsigned long
shmiq_nopage (struct vm_area_struct *vma, unsigned long address, int write_access)
{
	/* Do not allow for mremap to expand us */
	return 0;
}

static struct vm_operations_struct qcntl_mmap = {
	NULL,			/* no special mmap-open */
	NULL,			/* no special mmap-close */
	NULL,			/* no special mmap-unmap */
	NULL,			/* no special mmap-protect */
	NULL,			/* no special mmap-sync */
	NULL,			/* no special mmap-advise */
	shmiq_nopage,		/* our magic no-page fault handler */
	NULL,			/* no special mmap-wppage */
	NULL,			/* no special mmap-swapout */
	NULL			/* no special mmap-swapin */
};

static int
shmiq_qcntl_mmap (struct file *file, struct vm_area_struct *vma)
{
	int           minor = MINOR (file->f_dentry->d_inode->i_rdev), error;
	unsigned int  size;
	unsigned long mem, start;
	
	/* mmap is only supported on the qcntl devices */
	if (minor-- == 0)
		return -EINVAL;

	if (vma->vm_offset != 0)
		return -EINVAL;

	size  = vma->vm_end - vma->vm_start;
	start = vma->vm_start; 
	mem = (unsigned long) shmiqs [minor].shmiq_vaddr =  vmalloc_uncached (size);
	if (!mem)
		return -EINVAL;

	/* Prevent the swapper from considering these pages for swap and touching them */
	vma->vm_flags    |= (VM_SHM  | VM_LOCKED | VM_IO);
	vma->vm_ops = &qcntl_mmap;
	
	/* Uncache the pages */
	vma->vm_page_prot = PAGE_USERIO;

	error = vmap_page_range (vma->vm_start, size, mem);

	shmiqs [minor].tail = 0;
	/* Init the shared memory input queue */
	memset (shmiqs [minor].shmiq_vaddr, 0, size);
	
	return error;
}
		  
static int
shmiq_qcntl_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
	int minor = MINOR (inode->i_rdev);

	lock_kernel ();

	if (minor-- == 0)
		return shmiq_ioctl (inode, filp, cmd, arg);

	return qcntl_ioctl (inode, filp, cmd, arg, minor);
}

static unsigned int
shmiq_qcntl_poll (struct file *filp, poll_table *wait)
{
	struct sharedMemoryInputQueue *s;
	int minor = MINOR (filp->f_dentry->d_inode->i_rdev);

	if (minor-- == 0)
		return 0;

	if (!shmiqs [minor].mapped)
		return 0;
	
	poll_wait (filp, &shmiqs [minor].proc_list, wait);
	s = shmiqs [minor].shmiq_vaddr;
	if (s->head != s->tail)
		return POLLIN | POLLRDNORM;
	return 0;
}

static int
shmiq_qcntl_open (struct inode *inode, struct file *filp)
{
	int minor = MINOR (inode->i_rdev);

	if (minor == 0)
		return 0;

	minor--;
	if (minor > MAX_SHMI_QUEUES)
		return -EINVAL;
	if (shmiqs [minor].opened)
		return -EBUSY;

	lock_kernel ();
	shmiqs [minor].opened      = 1;
	shmiqs [minor].shmiq_vaddr = 0;
	unlock_kernel ();
	return 0;
}

static int
shmiq_qcntl_fasync (struct file *file, int on)
{
	int retval;
	int minor = MINOR (file->f_dentry->d_inode->i_rdev);

	retval = fasync_helper (file, on, &shmiqs [minor].fasync);
	if (retval < 0)
		return retval;
	return 0;
}

static int
shmiq_qcntl_close (struct inode *inode, struct file *filp)
{
	int minor = MINOR (inode->i_rdev);
	int j;
	
	if (minor-- == 0){
		for (j = 0; j < MAX_SHMIQ_DEVS; j++)
			shmiq_forget_file (j);
	}

	if (minor > MAX_SHMI_QUEUES)
		return -EINVAL;
	if (shmiqs [minor].opened == 0)
		return -EINVAL;

	lock_kernel ();
	shmiq_qcntl_fasync (filp, 0);
	shmiqs [minor].opened      = 0;
	shmiqs [minor].mapped      = 0;
	shmiqs [minor].events      = 0;
	shmiqs [minor].fasync      = 0;
	vfree (shmiqs [minor].shmiq_vaddr);
	shmiqs [minor].shmiq_vaddr = 0;
	unlock_kernel ();
	return 0;
}


static struct
file_operations shmiq_fops =
{
        NULL,                   /* seek */
        NULL,                   /* read */
        NULL,                   /* write */
        NULL,                   /* readdir */
        shmiq_qcntl_poll,       /* poll */
        shmiq_qcntl_ioctl,      /* ioctl */
        shmiq_qcntl_mmap,       /* mmap */
        shmiq_qcntl_open,       /* open */
	NULL,			/* flush */
        shmiq_qcntl_close,      /* close */
        NULL,                   /* fsync */
        shmiq_qcntl_fasync,     /* fasync */
        NULL,                   /* check_media_change */
        NULL,                   /* revalidate */
};

void
shmiq_init (void)
{
	printk ("SHMIQ setup\n");
	register_chrdev (SHMIQ_MAJOR, "shmiq", &shmiq_fops);
}