diff options
-rw-r--r-- | include/litmus/ftdev.h | 49 | ||||
-rw-r--r-- | litmus/ftdev.c | 352 |
2 files changed, 401 insertions, 0 deletions
diff --git a/include/litmus/ftdev.h b/include/litmus/ftdev.h new file mode 100644 index 000000000000..7697b4616699 --- /dev/null +++ b/include/litmus/ftdev.h | |||
@@ -0,0 +1,49 @@ | |||
1 | #ifndef _LITMUS_FTDEV_H_ | ||
2 | #define _LITMUS_FTDEV_H_ | ||
3 | |||
4 | #include <litmus/feather_trace.h> | ||
5 | #include <litmus/feather_buffer.h> | ||
6 | #include <linux/mutex.h> | ||
7 | #include <linux/cdev.h> | ||
8 | |||
9 | #define MAX_FTDEV_MINORS NR_CPUS | ||
10 | |||
11 | #define FTDEV_ENABLE_CMD 0 | ||
12 | #define FTDEV_DISABLE_CMD 1 | ||
13 | |||
14 | struct ftdev; | ||
15 | |||
16 | /* return 0 if buffer can be opened, otherwise -$REASON */ | ||
17 | typedef int (*ftdev_can_open_t)(struct ftdev* dev, unsigned int buf_no); | ||
18 | /* return 0 on success, otherwise -$REASON */ | ||
19 | typedef int (*ftdev_alloc_t)(struct ftdev* dev, unsigned int buf_no); | ||
20 | typedef void (*ftdev_free_t)(struct ftdev* dev, unsigned int buf_no); | ||
21 | |||
22 | |||
23 | struct ftdev_event; | ||
24 | |||
25 | struct ftdev_minor { | ||
26 | struct ft_buffer* buf; | ||
27 | unsigned int readers; | ||
28 | struct mutex lock; | ||
29 | /* FIXME: filter for authorized events */ | ||
30 | struct ftdev_event* events; | ||
31 | }; | ||
32 | |||
33 | struct ftdev { | ||
34 | struct cdev cdev; | ||
35 | /* FIXME: don't waste memory, allocate dynamically */ | ||
36 | struct ftdev_minor minor[MAX_FTDEV_MINORS]; | ||
37 | unsigned int minor_cnt; | ||
38 | ftdev_alloc_t alloc; | ||
39 | ftdev_free_t free; | ||
40 | ftdev_can_open_t can_open; | ||
41 | }; | ||
42 | |||
43 | struct ft_buffer* alloc_ft_buffer(unsigned int count, size_t size); | ||
44 | void free_ft_buffer(struct ft_buffer* buf); | ||
45 | |||
46 | void ftdev_init(struct ftdev* ftdev, struct module* owner); | ||
47 | int register_ftdev(struct ftdev* ftdev, const char* name, int major); | ||
48 | |||
49 | #endif | ||
diff --git a/litmus/ftdev.c b/litmus/ftdev.c new file mode 100644 index 000000000000..1c1c241a0a69 --- /dev/null +++ b/litmus/ftdev.c | |||
@@ -0,0 +1,352 @@ | |||
1 | #include <linux/sched.h> | ||
2 | #include <linux/fs.h> | ||
3 | #include <linux/cdev.h> | ||
4 | #include <asm/uaccess.h> | ||
5 | #include <linux/module.h> | ||
6 | |||
7 | #include <litmus/litmus.h> | ||
8 | #include <litmus/feather_trace.h> | ||
9 | #include <litmus/ftdev.h> | ||
10 | |||
11 | struct ft_buffer* alloc_ft_buffer(unsigned int count, size_t size) | ||
12 | { | ||
13 | struct ft_buffer* buf; | ||
14 | size_t total = (size + 1) * count; | ||
15 | char* mem; | ||
16 | int order = 0, pages = 1; | ||
17 | |||
18 | buf = kmalloc(sizeof(*buf), GFP_KERNEL); | ||
19 | if (!buf) | ||
20 | return NULL; | ||
21 | |||
22 | total = (total / PAGE_SIZE) + (total % PAGE_SIZE != 0); | ||
23 | while (pages < total) { | ||
24 | order++; | ||
25 | pages *= 2; | ||
26 | } | ||
27 | |||
28 | mem = (char*) __get_free_pages(GFP_KERNEL, order); | ||
29 | if (!mem) { | ||
30 | kfree(buf); | ||
31 | return NULL; | ||
32 | } | ||
33 | |||
34 | if (!init_ft_buffer(buf, count, size, | ||
35 | mem + (count * size), /* markers at the end */ | ||
36 | mem)) { /* buffer objects */ | ||
37 | free_pages((unsigned long) mem, order); | ||
38 | kfree(buf); | ||
39 | return NULL; | ||
40 | } | ||
41 | return buf; | ||
42 | } | ||
43 | |||
44 | void free_ft_buffer(struct ft_buffer* buf) | ||
45 | { | ||
46 | int order = 0, pages = 1; | ||
47 | size_t total; | ||
48 | |||
49 | if (buf) { | ||
50 | total = (buf->slot_size + 1) * buf->slot_count; | ||
51 | total = (total / PAGE_SIZE) + (total % PAGE_SIZE != 0); | ||
52 | while (pages < total) { | ||
53 | order++; | ||
54 | pages *= 2; | ||
55 | } | ||
56 | free_pages((unsigned long) buf->buffer_mem, order); | ||
57 | kfree(buf); | ||
58 | } | ||
59 | } | ||
60 | |||
61 | struct ftdev_event { | ||
62 | int id; | ||
63 | struct ftdev_event* next; | ||
64 | }; | ||
65 | |||
66 | static int activate(struct ftdev_event** chain, int id) | ||
67 | { | ||
68 | struct ftdev_event* ev = kmalloc(sizeof(*ev), GFP_KERNEL); | ||
69 | if (ev) { | ||
70 | printk(KERN_INFO | ||
71 | "Enabling feather-trace event %d.\n", (int) id); | ||
72 | ft_enable_event(id); | ||
73 | ev->id = id; | ||
74 | ev->next = *chain; | ||
75 | *chain = ev; | ||
76 | } | ||
77 | return ev ? 0 : -ENOMEM; | ||
78 | } | ||
79 | |||
80 | static void deactivate(struct ftdev_event** chain, int id) | ||
81 | { | ||
82 | struct ftdev_event **cur = chain; | ||
83 | struct ftdev_event *nxt; | ||
84 | while (*cur) { | ||
85 | if ((*cur)->id == id) { | ||
86 | nxt = (*cur)->next; | ||
87 | kfree(*cur); | ||
88 | *cur = nxt; | ||
89 | printk(KERN_INFO | ||
90 | "Disabling feather-trace event %d.\n", (int) id); | ||
91 | ft_disable_event(id); | ||
92 | break; | ||
93 | } | ||
94 | cur = &(*cur)->next; | ||
95 | } | ||
96 | } | ||
97 | |||
98 | static int ftdev_open(struct inode *in, struct file *filp) | ||
99 | { | ||
100 | struct ftdev* ftdev; | ||
101 | struct ftdev_minor* ftdm; | ||
102 | unsigned int buf_idx = iminor(in); | ||
103 | int err = 0; | ||
104 | |||
105 | ftdev = container_of(in->i_cdev, struct ftdev, cdev); | ||
106 | |||
107 | if (buf_idx >= ftdev->minor_cnt) { | ||
108 | err = -ENODEV; | ||
109 | goto out; | ||
110 | } | ||
111 | if (ftdev->can_open && (err = ftdev->can_open(ftdev, buf_idx))) | ||
112 | goto out; | ||
113 | |||
114 | ftdm = ftdev->minor + buf_idx; | ||
115 | filp->private_data = ftdm; | ||
116 | |||
117 | if (mutex_lock_interruptible(&ftdm->lock)) { | ||
118 | err = -ERESTARTSYS; | ||
119 | goto out; | ||
120 | } | ||
121 | |||
122 | if (!ftdm->readers && ftdev->alloc) | ||
123 | err = ftdev->alloc(ftdev, buf_idx); | ||
124 | if (0 == err) | ||
125 | ftdm->readers++; | ||
126 | |||
127 | mutex_unlock(&ftdm->lock); | ||
128 | out: | ||
129 | return err; | ||
130 | } | ||
131 | |||
132 | static int ftdev_release(struct inode *in, struct file *filp) | ||
133 | { | ||
134 | struct ftdev* ftdev; | ||
135 | struct ftdev_minor* ftdm; | ||
136 | unsigned int buf_idx = iminor(in); | ||
137 | int err = 0; | ||
138 | |||
139 | ftdev = container_of(in->i_cdev, struct ftdev, cdev); | ||
140 | |||
141 | if (buf_idx >= ftdev->minor_cnt) { | ||
142 | err = -ENODEV; | ||
143 | goto out; | ||
144 | } | ||
145 | ftdm = ftdev->minor + buf_idx; | ||
146 | |||
147 | if (mutex_lock_interruptible(&ftdm->lock)) { | ||
148 | err = -ERESTARTSYS; | ||
149 | goto out; | ||
150 | } | ||
151 | |||
152 | if (ftdm->readers == 1) { | ||
153 | while (ftdm->events) | ||
154 | deactivate(&ftdm->events, ftdm->events->id); | ||
155 | |||
156 | /* wait for any pending events to complete */ | ||
157 | set_current_state(TASK_UNINTERRUPTIBLE); | ||
158 | schedule_timeout(HZ); | ||
159 | |||
160 | printk(KERN_ALERT "Failed trace writes: %u\n", | ||
161 | ftdm->buf->failed_writes); | ||
162 | |||
163 | if (ftdev->free) | ||
164 | ftdev->free(ftdev, buf_idx); | ||
165 | } | ||
166 | |||
167 | ftdm->readers--; | ||
168 | mutex_unlock(&ftdm->lock); | ||
169 | out: | ||
170 | return err; | ||
171 | } | ||
172 | |||
173 | /* based on ft_buffer_read | ||
174 | * @returns < 0 : page fault | ||
175 | * = 0 : no data available | ||
176 | * = 1 : one slot copied | ||
177 | */ | ||
178 | static int ft_buffer_copy_to_user(struct ft_buffer* buf, char __user *dest) | ||
179 | { | ||
180 | unsigned int idx; | ||
181 | int err = 0; | ||
182 | if (buf->free_count != buf->slot_count) { | ||
183 | /* data available */ | ||
184 | idx = buf->read_idx % buf->slot_count; | ||
185 | if (buf->slots[idx] == SLOT_READY) { | ||
186 | err = copy_to_user(dest, ((char*) buf->buffer_mem) + | ||
187 | idx * buf->slot_size, | ||
188 | buf->slot_size); | ||
189 | if (err == 0) { | ||
190 | /* copy ok */ | ||
191 | buf->slots[idx] = SLOT_FREE; | ||
192 | buf->read_idx++; | ||
193 | fetch_and_inc(&buf->free_count); | ||
194 | err = 1; | ||
195 | } | ||
196 | } | ||
197 | } | ||
198 | return err; | ||
199 | } | ||
200 | |||
201 | static ssize_t ftdev_read(struct file *filp, | ||
202 | char __user *to, size_t len, loff_t *f_pos) | ||
203 | { | ||
204 | /* we ignore f_pos, this is strictly sequential */ | ||
205 | |||
206 | ssize_t err = 0; | ||
207 | size_t chunk; | ||
208 | int copied; | ||
209 | struct ftdev_minor* ftdm = filp->private_data; | ||
210 | |||
211 | if (mutex_lock_interruptible(&ftdm->lock)) { | ||
212 | err = -ERESTARTSYS; | ||
213 | goto out; | ||
214 | } | ||
215 | |||
216 | |||
217 | chunk = ftdm->buf->slot_size; | ||
218 | while (len >= chunk) { | ||
219 | copied = ft_buffer_copy_to_user(ftdm->buf, to); | ||
220 | if (copied == 1) { | ||
221 | len -= chunk; | ||
222 | to += chunk; | ||
223 | err += chunk; | ||
224 | } else if (err == 0 && copied == 0 && ftdm->events) { | ||
225 | /* Only wait if there are any events enabled and only | ||
226 | * if we haven't copied some data yet. We cannot wait | ||
227 | * here with copied data because that data would get | ||
228 | * lost if the task is interrupted (e.g., killed). | ||
229 | */ | ||
230 | set_current_state(TASK_INTERRUPTIBLE); | ||
231 | schedule_timeout(50); | ||
232 | if (signal_pending(current)) { | ||
233 | if (err == 0) | ||
234 | /* nothing read yet, signal problem */ | ||
235 | err = -ERESTARTSYS; | ||
236 | break; | ||
237 | } | ||
238 | } else if (copied < 0) { | ||
239 | /* page fault */ | ||
240 | err = copied; | ||
241 | break; | ||
242 | } else | ||
243 | /* nothing left to get, return to user space */ | ||
244 | break; | ||
245 | } | ||
246 | mutex_unlock(&ftdm->lock); | ||
247 | out: | ||
248 | return err; | ||
249 | } | ||
250 | |||
251 | typedef uint32_t cmd_t; | ||
252 | |||
253 | static ssize_t ftdev_write(struct file *filp, const char __user *from, | ||
254 | size_t len, loff_t *f_pos) | ||
255 | { | ||
256 | struct ftdev_minor* ftdm = filp->private_data; | ||
257 | ssize_t err = -EINVAL; | ||
258 | cmd_t cmd; | ||
259 | cmd_t id; | ||
260 | |||
261 | if (len % sizeof(cmd) || len < 2 * sizeof(cmd)) | ||
262 | goto out; | ||
263 | |||
264 | if (copy_from_user(&cmd, from, sizeof(cmd))) { | ||
265 | err = -EFAULT; | ||
266 | goto out; | ||
267 | } | ||
268 | len -= sizeof(cmd); | ||
269 | from += sizeof(cmd); | ||
270 | |||
271 | if (cmd != FTDEV_ENABLE_CMD && cmd != FTDEV_DISABLE_CMD) | ||
272 | goto out; | ||
273 | |||
274 | if (mutex_lock_interruptible(&ftdm->lock)) { | ||
275 | err = -ERESTARTSYS; | ||
276 | goto out; | ||
277 | } | ||
278 | |||
279 | err = sizeof(cmd); | ||
280 | while (len) { | ||
281 | if (copy_from_user(&id, from, sizeof(cmd))) { | ||
282 | err = -EFAULT; | ||
283 | goto out_unlock; | ||
284 | } | ||
285 | /* FIXME: check id against list of acceptable events */ | ||
286 | len -= sizeof(cmd); | ||
287 | from += sizeof(cmd); | ||
288 | if (cmd == FTDEV_DISABLE_CMD) | ||
289 | deactivate(&ftdm->events, id); | ||
290 | else if (activate(&ftdm->events, id) != 0) { | ||
291 | err = -ENOMEM; | ||
292 | goto out_unlock; | ||
293 | } | ||
294 | err += sizeof(cmd); | ||
295 | } | ||
296 | |||
297 | out_unlock: | ||
298 | mutex_unlock(&ftdm->lock); | ||
299 | out: | ||
300 | return err; | ||
301 | } | ||
302 | |||
303 | struct file_operations ftdev_fops = { | ||
304 | .owner = THIS_MODULE, | ||
305 | .open = ftdev_open, | ||
306 | .release = ftdev_release, | ||
307 | .write = ftdev_write, | ||
308 | .read = ftdev_read, | ||
309 | }; | ||
310 | |||
311 | |||
312 | void ftdev_init(struct ftdev* ftdev, struct module* owner) | ||
313 | { | ||
314 | int i; | ||
315 | cdev_init(&ftdev->cdev, &ftdev_fops); | ||
316 | ftdev->cdev.owner = owner; | ||
317 | ftdev->cdev.ops = &ftdev_fops; | ||
318 | ftdev->minor_cnt = 0; | ||
319 | for (i = 0; i < MAX_FTDEV_MINORS; i++) { | ||
320 | mutex_init(&ftdev->minor[i].lock); | ||
321 | ftdev->minor[i].readers = 0; | ||
322 | ftdev->minor[i].buf = NULL; | ||
323 | ftdev->minor[i].events = NULL; | ||
324 | } | ||
325 | ftdev->alloc = NULL; | ||
326 | ftdev->free = NULL; | ||
327 | ftdev->can_open = NULL; | ||
328 | } | ||
329 | |||
330 | int register_ftdev(struct ftdev* ftdev, const char* name, int major) | ||
331 | { | ||
332 | dev_t trace_dev; | ||
333 | int error = 0; | ||
334 | |||
335 | trace_dev = MKDEV(major, 0); | ||
336 | error = register_chrdev_region(trace_dev, ftdev->minor_cnt, name); | ||
337 | if (error) | ||
338 | { | ||
339 | printk(KERN_WARNING "ftdev(%s): " | ||
340 | "Could not register major/minor number %d/%u\n", | ||
341 | name, major, ftdev->minor_cnt); | ||
342 | return error; | ||
343 | } | ||
344 | error = cdev_add(&ftdev->cdev, trace_dev, ftdev->minor_cnt); | ||
345 | if (error) { | ||
346 | printk(KERN_WARNING "ftdev(%s): " | ||
347 | "Could not add cdev for major/minor = %d/%u.\n", | ||
348 | name, major, ftdev->minor_cnt); | ||
349 | return error; | ||
350 | } | ||
351 | return error; | ||
352 | } | ||