/alps/pcitool

To get this branch, use:
bzr branch http://suren.me/webbzr/alps/pcitool

« back to all changes in this revision

Viewing changes to driver/ioctl.c

  • Committer: Suren A. Chilingaryan
  • Date: 2011-02-13 02:07:11 UTC
  • Revision ID: csa@dside.dyndns.org-20110213020711-y9bjh3n4ke6p4t4n
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 *
 
3
 * @file ioctl.c
 
4
 * @author Guillermo Marcus
 
5
 * @date 2009-04-05
 
6
 * @brief Contains the functions handling the different ioctl calls.
 
7
 *
 
8
 */
 
9
#include <linux/version.h>
 
10
#include <linux/string.h>
 
11
#include <linux/slab.h>
 
12
#include <linux/types.h>
 
13
#include <linux/init.h>
 
14
#include <linux/module.h>
 
15
#include <linux/pci.h>
 
16
#include <linux/kernel.h>
 
17
#include <linux/errno.h>
 
18
#include <linux/fs.h>
 
19
#include <linux/cdev.h>
 
20
#include <linux/sysfs.h>
 
21
#include <asm/atomic.h>
 
22
#include <linux/pagemap.h>
 
23
#include <linux/spinlock.h>
 
24
#include <linux/list.h>
 
25
#include <asm/scatterlist.h>
 
26
#include <linux/vmalloc.h>
 
27
#include <linux/stat.h>
 
28
#include <linux/interrupt.h>
 
29
#include <linux/wait.h>
 
30
#include <linux/sched.h>
 
31
 
 
32
#include "config.h"                     /* Configuration for the driver */
 
33
#include "compat.h"                     /* Compatibility functions/definitions */
 
34
#include "pciDriver.h"                  /* External interface for the driver */
 
35
#include "common.h"                     /* Internal definitions for all parts */
 
36
#include "kmem.h"                       /* Internal definitions for kernel memory */
 
37
#include "umem.h"                       /* Internal definitions for user space memory */
 
38
#include "ioctl.h"                      /* Internal definitions for the ioctl part */
 
39
 
 
40
/** Declares a variable of the given type with the given name and copies it from userspace */
 
41
#define READ_FROM_USER(type, name) \
 
42
        type name; \
 
43
        if ((ret = copy_from_user(&name, (type*)arg, sizeof(name))) != 0) \
 
44
                return -EFAULT;
 
45
 
 
46
/** Writes back the given variable with the given type to userspace */
 
47
#define WRITE_TO_USER(type, name) \
 
48
        if ((ret = copy_to_user((type*)arg, &name, sizeof(name))) != 0) \
 
49
                return -EFAULT;
 
50
 
 
51
/**
 
52
 *
 
53
 * Sets the mmap mode for following mmap() calls.
 
54
 *
 
55
 * @param arg Not a pointer, but either PCIDRIVER_MMAP_PCI or PCIDRIVER_MMAP_KMEM
 
56
 *
 
57
 */
 
58
static int ioctl_mmap_mode(pcidriver_privdata_t *privdata, unsigned long arg)
 
59
{
 
60
        if ((arg != PCIDRIVER_MMAP_PCI) && (arg != PCIDRIVER_MMAP_KMEM))
 
61
                return -EINVAL;
 
62
 
 
63
        /* change the mode */
 
64
        privdata->mmap_mode = arg;
 
65
 
 
66
        return 0;
 
67
}
 
68
 
 
69
/**
 
70
 *
 
71
 * Sets the mmap area (BAR) for following mmap() calls.
 
72
 *
 
73
 */
 
74
static int ioctl_mmap_area(pcidriver_privdata_t *privdata, unsigned long arg)
 
75
{
 
76
        /* validate input */
 
77
        if ((arg < PCIDRIVER_BAR0) || (arg > PCIDRIVER_BAR5))
 
78
                return -EINVAL;
 
79
 
 
80
        /* change the PCI area to mmap */
 
81
        privdata->mmap_area = arg;
 
82
 
 
83
        return 0;
 
84
}
 
85
 
 
86
/**
 
87
 *
 
88
 * Reads/writes a byte/word/dword of the device's PCI config.
 
89
 *
 
90
 * @see pcidriver_pci_read
 
91
 * @see pcidriver_pci_write
 
92
 *
 
93
 */
 
94
static int ioctl_pci_config_read_write(pcidriver_privdata_t *privdata, unsigned int cmd, unsigned long arg)
 
95
{
 
96
        int ret;
 
97
        READ_FROM_USER(pci_cfg_cmd, pci_cmd);
 
98
 
 
99
        if (cmd == PCIDRIVER_IOC_PCI_CFG_RD) {
 
100
                switch (pci_cmd.size) {
 
101
                        case PCIDRIVER_PCI_CFG_SZ_BYTE:
 
102
                                ret = pci_read_config_byte( privdata->pdev, pci_cmd.addr, &(pci_cmd.val.byte) );
 
103
                                break;
 
104
                        case PCIDRIVER_PCI_CFG_SZ_WORD:
 
105
                                ret = pci_read_config_word( privdata->pdev, pci_cmd.addr, &(pci_cmd.val.word) );
 
106
                                break;
 
107
                        case PCIDRIVER_PCI_CFG_SZ_DWORD:
 
108
                                ret = pci_read_config_dword( privdata->pdev, pci_cmd.addr, &(pci_cmd.val.dword) );
 
109
                                break;
 
110
                        default:
 
111
                                return -EINVAL;         /* Wrong size setting */
 
112
                }
 
113
        } else {
 
114
                switch (pci_cmd.size) {
 
115
                        case PCIDRIVER_PCI_CFG_SZ_BYTE:
 
116
                                ret = pci_write_config_byte( privdata->pdev, pci_cmd.addr, pci_cmd.val.byte );
 
117
                                break;
 
118
                        case PCIDRIVER_PCI_CFG_SZ_WORD:
 
119
                                ret = pci_write_config_word( privdata->pdev, pci_cmd.addr, pci_cmd.val.word );
 
120
                                break;
 
121
                        case PCIDRIVER_PCI_CFG_SZ_DWORD:
 
122
                                ret = pci_write_config_dword( privdata->pdev, pci_cmd.addr, pci_cmd.val.dword );
 
123
                                break;
 
124
                        default:
 
125
                                return -EINVAL;         /* Wrong size setting */
 
126
                                break;
 
127
                }
 
128
        }
 
129
 
 
130
        WRITE_TO_USER(pci_cfg_cmd, pci_cmd);
 
131
 
 
132
        return 0;
 
133
}
 
134
 
 
135
/**
 
136
 *
 
137
 * Gets the PCI information for the device.
 
138
 *
 
139
 * @see pcidriver_pci_info
 
140
 *
 
141
 */
 
142
static int ioctl_pci_info(pcidriver_privdata_t *privdata, unsigned long arg)
 
143
{
 
144
        int ret;
 
145
        int bar;
 
146
        READ_FROM_USER(pci_board_info, pci_info);
 
147
 
 
148
        pci_info.vendor_id = privdata->pdev->vendor;
 
149
        pci_info.device_id = privdata->pdev->device;
 
150
        pci_info.bus = privdata->pdev->bus->number;
 
151
        pci_info.slot = PCI_SLOT(privdata->pdev->devfn);
 
152
        pci_info.devfn = privdata->pdev->devfn;
 
153
 
 
154
        if ((ret = pci_read_config_byte(privdata->pdev, PCI_INTERRUPT_PIN, &(pci_info.interrupt_pin))) != 0)
 
155
                return ret;
 
156
 
 
157
        if ((ret = pci_read_config_byte(privdata->pdev, PCI_INTERRUPT_LINE, &(pci_info.interrupt_line))) != 0)
 
158
                return ret;
 
159
 
 
160
        for (bar = 0; bar < 6; bar++) {
 
161
                pci_info.bar_start[bar] = pci_resource_start(privdata->pdev, bar);
 
162
                pci_info.bar_length[bar] = pci_resource_len(privdata->pdev, bar);
 
163
        }
 
164
 
 
165
        WRITE_TO_USER(pci_board_info, pci_info);
 
166
 
 
167
        return 0;
 
168
}
 
169
 
 
170
/**
 
171
 *
 
172
 * Allocates kernel memory.
 
173
 *
 
174
 * @see pcidriver_kmem_alloc
 
175
 *
 
176
 */
 
177
static int ioctl_kmem_alloc(pcidriver_privdata_t *privdata, unsigned long arg)
 
178
{
 
179
        int ret;
 
180
        READ_FROM_USER(kmem_handle_t, khandle);
 
181
 
 
182
        if ((ret = pcidriver_kmem_alloc(privdata, &khandle)) != 0)
 
183
                return ret;
 
184
 
 
185
        WRITE_TO_USER(kmem_handle_t, khandle);
 
186
 
 
187
        return 0;
 
188
}
 
189
 
 
190
/**
 
191
 *
 
192
 * Frees kernel memory.
 
193
 *
 
194
 * @see pcidriver_kmem_free
 
195
 *
 
196
 */
 
197
static int ioctl_kmem_free(pcidriver_privdata_t *privdata, unsigned long arg)
 
198
{
 
199
        int ret;
 
200
        READ_FROM_USER(kmem_handle_t, khandle);
 
201
 
 
202
        if ((ret = pcidriver_kmem_free(privdata, &khandle)) != 0)
 
203
                return ret;
 
204
 
 
205
        return 0;
 
206
}
 
207
 
 
208
/**
 
209
 *
 
210
 * Syncs kernel memory.
 
211
 *
 
212
 * @see pcidriver_kmem_sync
 
213
 *
 
214
 */
 
215
static int ioctl_kmem_sync(pcidriver_privdata_t *privdata, unsigned long arg)
 
216
{
 
217
        int ret;
 
218
        READ_FROM_USER(kmem_sync_t, ksync);
 
219
 
 
220
        return pcidriver_kmem_sync(privdata, &ksync);
 
221
}
 
222
 
 
223
/*
 
224
 *
 
225
 * Maps the given scatter/gather list from memory to PCI bus addresses.
 
226
 *
 
227
 * @see pcidriver_umem_sgmap
 
228
 *
 
229
 */
 
230
static int ioctl_umem_sgmap(pcidriver_privdata_t *privdata, unsigned long arg)
 
231
{
 
232
        int ret;
 
233
        READ_FROM_USER(umem_handle_t, uhandle);
 
234
 
 
235
        if ((ret = pcidriver_umem_sgmap(privdata, &uhandle)) != 0)
 
236
                return ret;
 
237
 
 
238
        WRITE_TO_USER(umem_handle_t, uhandle);
 
239
 
 
240
        return 0;
 
241
}
 
242
 
 
243
/**
 
244
 *
 
245
 * Unmaps the given scatter/gather list.
 
246
 *
 
247
 * @see pcidriver_umem_sgunmap
 
248
 *
 
249
 */
 
250
static int ioctl_umem_sgunmap(pcidriver_privdata_t *privdata, unsigned long arg)
 
251
{
 
252
        int ret;
 
253
        pcidriver_umem_entry_t *umem_entry;
 
254
        READ_FROM_USER(umem_handle_t, uhandle);
 
255
 
 
256
        /* Find the associated umem_entry for this buffer,
 
257
         * return -EINVAL if the specified handle id is invalid */
 
258
        if ((umem_entry = pcidriver_umem_find_entry_id(privdata, uhandle.handle_id)) == NULL)
 
259
                return -EINVAL;
 
260
 
 
261
        if ((ret = pcidriver_umem_sgunmap(privdata, umem_entry)) != 0)
 
262
                return ret;
 
263
 
 
264
        return 0;
 
265
}
 
266
 
 
267
/**
 
268
 *
 
269
 * Copies the scatter/gather list from kernelspace to userspace.
 
270
 *
 
271
 * @see pcidriver_umem_sgget
 
272
 *
 
273
 */
 
274
static int ioctl_umem_sgget(pcidriver_privdata_t *privdata, unsigned long arg)
 
275
{
 
276
        int ret;
 
277
        READ_FROM_USER(umem_sglist_t, usglist);
 
278
 
 
279
        /* The umem_sglist_t has a pointer to the scatter/gather list itself which
 
280
         * needs to be copied separately. The number of elements is stored in ->nents.
 
281
         * As the list can get very big, we need to use vmalloc. */
 
282
        if ((usglist.sg = vmalloc(usglist.nents * sizeof(umem_sgentry_t))) == NULL)
 
283
                return -ENOMEM;
 
284
 
 
285
        /* copy array to kernel structure */
 
286
        ret = copy_from_user(usglist.sg, ((umem_sglist_t *)arg)->sg, (usglist.nents)*sizeof(umem_sgentry_t));
 
287
        if (ret) return -EFAULT;
 
288
 
 
289
        if ((ret = pcidriver_umem_sgget(privdata, &usglist)) != 0)
 
290
                return ret;
 
291
 
 
292
        /* write data to user space */
 
293
        ret = copy_to_user(((umem_sglist_t *)arg)->sg, usglist.sg, (usglist.nents)*sizeof(umem_sgentry_t));
 
294
        if (ret) return -EFAULT;
 
295
 
 
296
        /* free array memory */
 
297
        vfree(usglist.sg);
 
298
 
 
299
        /* restore sg pointer to vma address in user space before copying */
 
300
        usglist.sg = ((umem_sglist_t *)arg)->sg;
 
301
 
 
302
        WRITE_TO_USER(umem_sglist_t, usglist);
 
303
 
 
304
        return 0;
 
305
}
 
306
 
 
307
/**
 
308
 *
 
309
 * Syncs user memory.
 
310
 *
 
311
 * @see pcidriver_umem_sync
 
312
 *
 
313
 */
 
314
static int ioctl_umem_sync(pcidriver_privdata_t *privdata, unsigned long arg)
 
315
{
 
316
        int ret;
 
317
        READ_FROM_USER(umem_handle_t, uhandle);
 
318
 
 
319
        return pcidriver_umem_sync( privdata, &uhandle );
 
320
}
 
321
 
 
322
/**
 
323
 *
 
324
 * Waits for an interrupt
 
325
 *
 
326
 * @param arg Not a pointer, but the irq source to wait for (unsigned int)
 
327
 *
 
328
 */
 
329
static int ioctl_wait_interrupt(pcidriver_privdata_t *privdata, unsigned long arg)
 
330
{
 
331
#ifdef ENABLE_IRQ
 
332
        unsigned int irq_source;
 
333
        int temp;
 
334
 
 
335
        if (arg >= PCIDRIVER_INT_MAXSOURCES)
 
336
                return -EFAULT;                                         /* User tried to overrun the IRQ_SOURCES array */
 
337
 
 
338
        irq_source = arg;
 
339
 
 
340
        /* Thanks to Joern for the correction and tips! */
 
341
        /* done this way to avoid wrong behaviour (endless loop) of the compiler in AMD platforms */
 
342
        temp=1;
 
343
        while (temp) {
 
344
                /* We wait here with an interruptible timeout. This will be interrupted
 
345
                 * by int.c:check_acknowledge_channel() as soon as in interrupt for
 
346
                 * the specified source arrives. */
 
347
                wait_event_interruptible_timeout( (privdata->irq_queues[irq_source]), (atomic_read(&(privdata->irq_outstanding[irq_source])) > 0), (10*HZ/1000) );
 
348
 
 
349
                if (atomic_add_negative( -1, &(privdata->irq_outstanding[irq_source])) )
 
350
                        atomic_inc( &(privdata->irq_outstanding[irq_source]) );
 
351
                else
 
352
                        temp =0;
 
353
        }
 
354
 
 
355
        return 0;
 
356
#else
 
357
        mod_info("Asked to wait for interrupt but interrupts are not enabled in the driver\n");
 
358
        return -EFAULT;
 
359
#endif
 
360
}
 
361
 
 
362
/**
 
363
 *
 
364
 * Clears the interrupt wait queue.
 
365
 *
 
366
 * @param arg Not a pointer, but the irq source (unsigned int)
 
367
 * @returns -EFAULT if the user specified an irq source out of range
 
368
 *
 
369
 */
 
370
static int ioctl_clear_ioq(pcidriver_privdata_t *privdata, unsigned long arg)
 
371
{
 
372
#ifdef ENABLE_IRQ
 
373
        unsigned int irq_source;
 
374
 
 
375
        if (arg >= PCIDRIVER_INT_MAXSOURCES)
 
376
                return -EFAULT;
 
377
 
 
378
        irq_source = arg;
 
379
        atomic_set(&(privdata->irq_outstanding[irq_source]), 0);
 
380
 
 
381
        return 0;
 
382
#else
 
383
        mod_info("Asked to wait for interrupt but interrupts are not enabled in the driver\n");
 
384
        return -EFAULT;
 
385
#endif
 
386
}
 
387
 
 
388
/**
 
389
 *
 
390
 * This function handles all ioctl file operations.
 
391
 * Generally, the data of the ioctl is copied from userspace to kernelspace, a separate
 
392
 * function is called to handle the ioctl itself, then the data is copied back to userspace.
 
393
 *
 
394
 * @returns -EFAULT when an invalid memory pointer is passed
 
395
 *
 
396
 */
 
397
int pcidriver_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
 
398
{
 
399
        pcidriver_privdata_t *privdata = filp->private_data;
 
400
 
 
401
        /* Select the appropiate command */
 
402
        switch (cmd) {
 
403
                case PCIDRIVER_IOC_MMAP_MODE:
 
404
                        return ioctl_mmap_mode(privdata, arg);
 
405
 
 
406
                case PCIDRIVER_IOC_MMAP_AREA:
 
407
                        return ioctl_mmap_area(privdata, arg);
 
408
 
 
409
                case PCIDRIVER_IOC_PCI_CFG_RD:
 
410
                case PCIDRIVER_IOC_PCI_CFG_WR:
 
411
                        return ioctl_pci_config_read_write(privdata, cmd, arg);
 
412
 
 
413
                case PCIDRIVER_IOC_PCI_INFO:
 
414
                        return ioctl_pci_info(privdata, arg);
 
415
 
 
416
                case PCIDRIVER_IOC_KMEM_ALLOC:
 
417
                        return ioctl_kmem_alloc(privdata, arg);
 
418
 
 
419
                case PCIDRIVER_IOC_KMEM_FREE:
 
420
                        return ioctl_kmem_free(privdata, arg);
 
421
 
 
422
                case PCIDRIVER_IOC_KMEM_SYNC:
 
423
                        return ioctl_kmem_sync(privdata, arg);
 
424
 
 
425
                case PCIDRIVER_IOC_UMEM_SGMAP:
 
426
                        return ioctl_umem_sgmap(privdata, arg);
 
427
 
 
428
                case PCIDRIVER_IOC_UMEM_SGUNMAP:
 
429
                        return ioctl_umem_sgunmap(privdata, arg);
 
430
 
 
431
                case PCIDRIVER_IOC_UMEM_SGGET:
 
432
                        return ioctl_umem_sgget(privdata, arg);
 
433
 
 
434
                case PCIDRIVER_IOC_UMEM_SYNC:
 
435
                        return ioctl_umem_sync(privdata, arg);
 
436
 
 
437
                case PCIDRIVER_IOC_WAITI:
 
438
                        return ioctl_wait_interrupt(privdata, arg);
 
439
 
 
440
                case PCIDRIVER_IOC_CLEAR_IOQ:
 
441
                        return ioctl_clear_ioq(privdata, arg);
 
442
 
 
443
                default:
 
444
                        return -EINVAL;
 
445
        }
 
446
}