]>
Commit | Line | Data |
---|---|---|
4debfe40 AR |
1 | /* |
2 | * Copyright (C) 2012 CERN (www.cern.ch) | |
3 | * Author: Alessandro Rubini <[email protected]> | |
4 | * | |
5 | * Released according to the GNU GPL, version 2 or any later version. | |
6 | * | |
7 | * This work is part of the White Rabbit project, a research effort led | |
8 | * by CERN, the European Institute for Nuclear Research. | |
9 | */ | |
10 | #include <linux/module.h> | |
11 | #include <linux/init.h> | |
12 | #include <linux/list.h> | |
13 | #include <linux/slab.h> | |
14 | #include <linux/fs.h> | |
15 | #include <linux/miscdevice.h> | |
16 | #include <linux/spinlock.h> | |
17 | #include <linux/fmc.h> | |
18 | #include <linux/uaccess.h> | |
19 | ||
20 | static LIST_HEAD(fc_devices); | |
21 | static DEFINE_SPINLOCK(fc_lock); | |
22 | ||
23 | struct fc_instance { | |
24 | struct list_head list; | |
25 | struct fmc_device *fmc; | |
26 | struct miscdevice misc; | |
27 | }; | |
28 | ||
29 | /* at open time, we must identify our device */ | |
30 | static int fc_open(struct inode *ino, struct file *f) | |
31 | { | |
32 | struct fmc_device *fmc; | |
33 | struct fc_instance *fc; | |
34 | int minor = iminor(ino); | |
35 | ||
36 | list_for_each_entry(fc, &fc_devices, list) | |
37 | if (fc->misc.minor == minor) | |
38 | break; | |
39 | if (fc->misc.minor != minor) | |
40 | return -ENODEV; | |
41 | fmc = fc->fmc; | |
42 | if (try_module_get(fmc->owner) == 0) | |
43 | return -ENODEV; | |
44 | ||
45 | f->private_data = fmc; | |
46 | return 0; | |
47 | } | |
48 | ||
49 | static int fc_release(struct inode *ino, struct file *f) | |
50 | { | |
51 | struct fmc_device *fmc = f->private_data; | |
52 | module_put(fmc->owner); | |
53 | return 0; | |
54 | } | |
55 | ||
56 | /* read and write are simple after the default llseek has been used */ | |
57 | static ssize_t fc_read(struct file *f, char __user *buf, size_t count, | |
58 | loff_t *offp) | |
59 | { | |
60 | struct fmc_device *fmc = f->private_data; | |
61 | unsigned long addr; | |
62 | uint32_t val; | |
63 | ||
64 | if (count < sizeof(val)) | |
65 | return -EINVAL; | |
66 | count = sizeof(val); | |
67 | ||
68 | addr = *offp; | |
69 | if (addr > fmc->memlen) | |
70 | return -ESPIPE; /* Illegal seek */ | |
71 | val = fmc_readl(fmc, addr); | |
72 | if (copy_to_user(buf, &val, count)) | |
73 | return -EFAULT; | |
74 | *offp += count; | |
75 | return count; | |
76 | } | |
77 | ||
78 | static ssize_t fc_write(struct file *f, const char __user *buf, size_t count, | |
79 | loff_t *offp) | |
80 | { | |
81 | struct fmc_device *fmc = f->private_data; | |
82 | unsigned long addr; | |
83 | uint32_t val; | |
84 | ||
85 | if (count < sizeof(val)) | |
86 | return -EINVAL; | |
87 | count = sizeof(val); | |
88 | ||
89 | addr = *offp; | |
90 | if (addr > fmc->memlen) | |
91 | return -ESPIPE; /* Illegal seek */ | |
92 | if (copy_from_user(&val, buf, count)) | |
93 | return -EFAULT; | |
94 | fmc_writel(fmc, val, addr); | |
95 | *offp += count; | |
96 | return count; | |
97 | } | |
98 | ||
99 | static const struct file_operations fc_fops = { | |
100 | .owner = THIS_MODULE, | |
101 | .open = fc_open, | |
102 | .release = fc_release, | |
103 | .llseek = generic_file_llseek, | |
104 | .read = fc_read, | |
105 | .write = fc_write, | |
106 | }; | |
107 | ||
108 | ||
109 | /* Device part .. */ | |
110 | static int fc_probe(struct fmc_device *fmc); | |
111 | static int fc_remove(struct fmc_device *fmc); | |
112 | ||
113 | static struct fmc_driver fc_drv = { | |
114 | .version = FMC_VERSION, | |
115 | .driver.name = KBUILD_MODNAME, | |
116 | .probe = fc_probe, | |
117 | .remove = fc_remove, | |
118 | /* no table: we want to match everything */ | |
119 | }; | |
120 | ||
121 | /* We accept the generic busid parameter */ | |
122 | FMC_PARAM_BUSID(fc_drv); | |
123 | ||
124 | /* probe and remove must allocate and release a misc device */ | |
125 | static int fc_probe(struct fmc_device *fmc) | |
126 | { | |
127 | int ret; | |
128 | int index = 0; | |
129 | ||
130 | struct fc_instance *fc; | |
131 | ||
132 | if (fmc->op->validate) | |
133 | index = fmc->op->validate(fmc, &fc_drv); | |
134 | if (index < 0) | |
135 | return -EINVAL; /* not our device: invalid */ | |
136 | ||
137 | /* Create a char device: we want to create it anew */ | |
138 | fc = kzalloc(sizeof(*fc), GFP_KERNEL); | |
4640a3f2 DC |
139 | if (!fc) |
140 | return -ENOMEM; | |
4debfe40 AR |
141 | fc->fmc = fmc; |
142 | fc->misc.minor = MISC_DYNAMIC_MINOR; | |
143 | fc->misc.fops = &fc_fops; | |
144 | fc->misc.name = kstrdup(dev_name(&fmc->dev), GFP_KERNEL); | |
145 | ||
4debfe40 | 146 | ret = misc_register(&fc->misc); |
4640a3f2 | 147 | if (ret < 0) |
783c2fb1 AR |
148 | goto out; |
149 | spin_lock(&fc_lock); | |
4640a3f2 | 150 | list_add(&fc->list, &fc_devices); |
4debfe40 AR |
151 | spin_unlock(&fc_lock); |
152 | dev_info(&fc->fmc->dev, "Created misc device \"%s\"\n", | |
153 | fc->misc.name); | |
4640a3f2 DC |
154 | return 0; |
155 | ||
783c2fb1 | 156 | out: |
4640a3f2 DC |
157 | kfree(fc->misc.name); |
158 | kfree(fc); | |
4debfe40 AR |
159 | return ret; |
160 | } | |
161 | ||
162 | static int fc_remove(struct fmc_device *fmc) | |
163 | { | |
164 | struct fc_instance *fc; | |
165 | ||
166 | list_for_each_entry(fc, &fc_devices, list) | |
167 | if (fc->fmc == fmc) | |
168 | break; | |
169 | if (fc->fmc != fmc) { | |
170 | dev_err(&fmc->dev, "remove called but not found\n"); | |
171 | return -ENODEV; | |
172 | } | |
173 | ||
174 | spin_lock(&fc_lock); | |
175 | list_del(&fc->list); | |
783c2fb1 | 176 | spin_unlock(&fc_lock); |
4debfe40 AR |
177 | misc_deregister(&fc->misc); |
178 | kfree(fc->misc.name); | |
179 | kfree(fc); | |
4debfe40 AR |
180 | |
181 | return 0; | |
182 | } | |
183 | ||
184 | ||
185 | static int fc_init(void) | |
186 | { | |
187 | int ret; | |
188 | ||
189 | ret = fmc_driver_register(&fc_drv); | |
190 | return ret; | |
191 | } | |
192 | ||
193 | static void fc_exit(void) | |
194 | { | |
195 | fmc_driver_unregister(&fc_drv); | |
196 | } | |
197 | ||
198 | module_init(fc_init); | |
199 | module_exit(fc_exit); | |
200 | ||
201 | MODULE_LICENSE("GPL"); |