-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtimer_driver.c
More file actions
363 lines (276 loc) · 9.44 KB
/
timer_driver.c
File metadata and controls
363 lines (276 loc) · 9.44 KB
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
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <linux/dma-mapping.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/list.h>
#include <linux/wait.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
#include <linux/interrupt.h>
#include <asm/irq.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include "timer_ioctl.h"
/* Standard module information, edit as appropriate */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Eric Matthews");
MODULE_DESCRIPTION ("zedboard axi timer driver");
#define DRIVER_NAME "timer"
//structures
static struct file_operations timer_fops;
struct timer_local *timer;
static struct fasync_struct *async_queue;
//functions
static irq_handler_t timer_interrupt(int irq, void *dev_id, struct pt_regs *regs);
static int timer_fasync(int fd, struct file *filp, int mode);
/*
* Main driver data structure
*/
struct timer_local {
int major;
int minor;
unsigned int irq;
unsigned long mem_start;
unsigned long mem_end;
void __iomem *base_addr;
struct cdev *cdev;
};
//IRQ handler function//
static irq_handler_t timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
//function is called when interrupt is recieved
printk(KERN_ALERT "Hardware Interrupt!\n");
//clear interrupt, reads control reg and writes this value
iowrite32(ioread32(timer->base_addr + CONTROL_REG), timer->base_addr + CONTROL_REG);
if (async_queue) {
printk ("signal async_queue");
// signal the interested processes when data arrives
kill_fasync(&async_queue, SIGIO, POLL_IN);
}
return IRQ_HANDLED;
}
static int timer_fasync(int fd, struct file *filp, int mode) { //whenever interrupt is generated, notifies user space, by sending SIGIO signal; fasync is used to notify user space process
return fasync_helper(fd, filp, mode, &async_queue);
}
/*
* Open function, called when userspace program calls open()
*/
static int timer_open(struct inode *inode, struct file *file)
{
return 0;
}
/*
* Close function, called when userspace program calls close()
*/
static int timer_release(struct inode *inode, struct file *filp)
{
// Remove the file from the list of asynchronously notified filp's
timer_fasync(-1, filp, 0);
return 0;
}
/*
* ioctl function, called when userspace program calls ioctl()
*/
//IOCTL function is a device specific system call; use this for reading and writing to timer; in order to communicate to device we need iowrite and ioread//driver is in kernel space
static long timer_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
struct timer_ioctl_data timer_data;
/* printf (along with other C library functions) is not available in
* kernel code, but printk is almost identical */
//printk(KERN_ALERT "Starting IOCTL... \n");
switch (cmd) { //switch funtion does whatever is true first
case TIMER_READ_REG: //reading from timer register
if (copy_from_user(&timer_data, (void *)arg, sizeof(timer_data))) {
//we need to copy from user space to access it// copying from user space the data in address of arg //to the address of timer_data (value in timer) in kernel
printk(KERN_ALERT "***Unsuccessful transfer of ioctl argument...\n");
return -EFAULT;
}
timer_data.data = ioread32(timer->base_addr+timer_data.offset);
//user loads timer, and we need to read this value; access device using ioread reading the timer value from memory and storing; this value is found in the base_addr+timer_data.offset; offset is defined by user??// in order to refer to this value, store it in data element of timer_data.data
if (copy_to_user((void *)arg, &timer_data, sizeof(timer_data))) { //copying to user space the data in timer_data (value in timer)
printk(KERN_ALERT "***Unsuccessful transfer of ioctl argument...\n"); //from the address of arg in kernel
return -EFAULT;
}
break;
case TIMER_WRITE_REG: //writing to timer register
if (copy_from_user(&timer_data, (void *)arg, sizeof(timer_data))) { //copying from user space the data in address of arg
printk(KERN_ALERT "***Unsuccessful transfer of ioctl argument...\n"); //to the address of timer_data (value in timer) in kernel
return -EFAULT;
}
iowrite32(timer_data.data, timer->base_addr+timer_data.offset); //iowrite adds new information, updates curent value in timer
break;
default:
printk(KERN_ALERT "***Invalid ioctl command...\n");
return -EINVAL;
}
return 0;
}
/*
* File operations struct
* - informs kernel which functions should be called when userspace prorgams
* call different functions (ie. open, close, ioctl etc.)
*/
static struct file_operations timer_fops = {
.open = timer_open,
.release = timer_release,
.fasync = timer_fasync,
.unlocked_ioctl = timer_ioctl,
};
/*
* Probe function (part of platform driver API)
*/
static int __devinit timer_probe(struct platform_device *pdev)
{
struct resource *r_mem; /* IO mem resources */
struct device *dev = &pdev->dev;
int rc = 0;
dev_t devno;
int err;
/* dev_info is a logging function part of the platform driver API */
dev_info(dev, "Device Tree Probing\n");
/* Get iospace for the device */
r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r_mem) {
dev_err(dev, "invalid address\n");
return -ENODEV;
}
/* Allocate space for driver data structure
* note the use of kmalloc - malloc (and all other C library functions) is
* unavailable in kernel code */
timer = (struct timer_local *) kmalloc(sizeof(struct timer_local), GFP_KERNEL);
if (!timer) {
dev_err(dev, "Cound not allocate timer device\n");
return -ENOMEM;
}
dev_set_drvdata(dev, timer);
timer->mem_start = r_mem->start;
timer->mem_end = r_mem->end;
if (!request_mem_region(timer->mem_start,
timer->mem_end - timer->mem_start + 1,
DRIVER_NAME)) {
dev_err(dev, "Couldn't lock memory region at %p\n",
(void *)timer->mem_start);
rc = -EBUSY;
goto error1;
}
/* Allocate I/O memory */
timer->base_addr = ioremap(timer->mem_start, timer->mem_end - timer->mem_start + 1);
if (!timer->base_addr) {
dev_err(dev, "timer: Could not allocate iomem\n");
rc = -EIO;
goto error2;
}
// Request IRQ
timer->irq = platform_get_irq(pdev, 0);
rc = request_irq(timer->irq, (void *)timer_interrupt, 0, "timer_driver", dev);
if (rc) {
dev_err(dev, "can't get assigned irq %i\n", timer->irq);
goto error3;
} else {
dev_info(dev, "assigned irq number %i\n", timer->irq);
}
dev_info(dev, "Registering character device\n");
if ((alloc_chrdev_region(&devno, 0, 1, "timer")) < 0) {
goto error4; //change it to error4
}
/* Fill in driver data structure */
timer->major = MAJOR(devno);
timer->minor = MINOR(devno);
dev_info(dev, "Initializing character device\n");
timer->cdev = cdev_alloc();
timer->cdev->owner = THIS_MODULE;
timer->cdev->ops = &timer_fops;
err = cdev_add (timer->cdev, devno, 1);
/* Print driver info (addresses, major and minor num) */
dev_info(dev,"timer at 0x%08x mapped to 0x%08x\nMajor: %d, Minor %d\n",
(unsigned int __force)timer->mem_start,
(unsigned int __force)timer->base_addr,
timer->major, timer->minor);
return 0;
/* Error handling for probe function
* - this is one of very few cases where goto statements are a good idea
* - when an error happens that prevents the driver from continuing to
* register/allocate resources, we need to undo any previous allocations
* that succeeded (in the reverse order)
*/
error4: // Undo 'request_irq()'
free_irq(timer->irq, dev);
error3: // Undo 'ioremap()'
iounmap((void *)(timer->base_addr));
error2:
release_mem_region(timer->mem_start, timer->mem_end - timer->mem_start + 1);
error1:
kfree(timer);
dev_set_drvdata(dev, NULL);
return rc;
}
/*
* Remove function (part of platform driver API)
*/
static int __devexit timer_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct timer_local *timer = dev_get_drvdata(dev);
free_irq(timer->irq, dev);//freeing irq
release_mem_region(timer->mem_start, timer->mem_end - timer->mem_start + 1);
cdev_del(timer->cdev);
unregister_chrdev_region(MKDEV(timer->major, timer->minor), 1);
kfree(timer);
dev_set_drvdata(dev, NULL);
return 0;
}
/*
* Compatiblity string for matching driver to hardware
*/
#ifdef CONFIG_OF
static struct of_device_id timer_of_match[] __devinitdata = {
/* This must match the compatible string in device tree source */
{ .compatible = "ensc351-timer", },
{ /* end of list */ },
};
MODULE_DEVICE_TABLE(of, timer_of_match);
#else
# define timer_of_match
#endif
/*
* Platform driver struct
* - used by platform driver API for device tree probing
*/
static struct platform_driver timer_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = timer_of_match,
},
.probe = timer_probe,
.remove = __devexit_p(timer_remove),
};
/*
* Driver initialization function
*/
static int __init timer_init(void)
{
printk("<1>Hello module world.\n");
return platform_driver_register(&timer_driver);
}
/*
* Driver exit function
*/
static void __exit timer_exit(void)
{
platform_driver_unregister(&timer_driver);
printk(KERN_ALERT "Goodbye module world.\n");
}
/*
* Register init and exit functions
*/
module_init(timer_init);
module_exit(timer_exit);