char_device_user.c 14.6 KB
Newer Older
1
2
3
//
// Copyright (C) 2014 David de la Chevallerie, TU Darmstadt
//
4
// This file is part of Tapasco (TPC).
5
//
6
// Tapasco is free software: you can redistribute it and/or modify
7
8
9
10
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
11
// Tapasco is distributed in the hope that it will be useful,
12
13
14
15
16
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
17
// along with Tapasco.  If not, see <http://www.gnu.org/licenses/>.
18
19
//
/**
20
 * @file char_device_user.c
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 * @brief Implementation of char-device calls for user-specific calls
	the char-device allows access to the user-registers over pcie
	besides it handles blocking execution of hw-function
	irqs are managed here as well, this code expects a Xilinx IRQ IP-Core as the source the irq
	each minor node handles one IRQ-Core, multiple minor node will be available
 * */

/******************************************************************************/

#include "char_device_user.h"

/******************************************************************************/
/* global struct and variable declarations */

/* file operations for sw-calls as a char device */
static struct file_operations user_fops = {
	.owner          = THIS_MODULE,
	.open           = user_open,
	.release        = user_close,
	.unlocked_ioctl = user_ioctl,
	.read           = user_read,
	.write          = user_write,
43
	.mmap		= user_mmap
44
45
46
};

/* struct array to hold data over multiple fops-calls */
47
static struct priv_data_struct priv_data;
48
49

/* char device structure basically for dev_t and fops */
50
static struct cdev char_user_cdev;
51
52
53
54
55
/* char device number for f3_char_driver major and minor number */
static dev_t char_user_dev_t;
/* device class entry for sysfs */
struct class *char_user_class;

56
static int device_opened = 0;
57

58
static DEFINE_SPINLOCK(device_open_close_mutex);
59
60
61
62
63
64
65
66
67
68
69
70
71
72

/******************************************************************************/
/* functions for user-space interaction */

/**
 * @brief When minor node is opened, mutexes and waiting queues will be initialized,
	performs setup code to activate interrupt controller
	caution: when irq-core is not present, this will cause a deadlock
 * @param inode Representation of node in /dev, used to get major-/minor-number
 * @param filp Mostly used to allocate private data for consecutive calls
 * @return Zero, if char-device could be opened, error code otherwise
 * */
static int user_open(struct inode *inode, struct file *filp)
{
73
	spin_lock(&device_open_close_mutex);
74

75
	fflink_notice("Already %d files in use.\n", device_opened);
76

77
78
79
80
	++device_opened;
	/* set filp for further sys calls to this minor number */
	filp->private_data = &priv_data;
	spin_unlock(&device_open_close_mutex);
81
82
83
84
85
86
87
88
89
90
91
	return 0;
}

/**
 * @brief Tidy up device, nothing to do here currently
 * @param inode Representation of node in /dev, used to get major-/minor-number
 * @param filp Mostly used to allocate private data for consecutive calls
 * @return Zero, if char-device could be opened, error code otherwise
 * */
static int user_close(struct inode *inode, struct file *filp)
{
92
93
94
95
	spin_lock(&device_open_close_mutex);
	--device_opened;
	fflink_notice("Still %d files in use.\n", device_opened);
	spin_unlock(&device_open_close_mutex);
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
	return 0;
}

/******************************************************************************/
/* functions for user-space interaction */

/**
 * @brief Read out consecutive register of user-specific function over pcie
	workaroung: using specific struct (user_ioctl_calls) to pass needed parameters
 * @param filp Needed to identify device node and get access to corresponding buffers
 * @param buf Pointer to user space buffer
 * @param count Bytes to be transferred
 * @param f_pos Offset in file, currently not supported
 * @return Zero, if transfer was successful, error code otherwise
 * */
static ssize_t user_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	struct user_rw_params params;
Jaco Hofmann's avatar
Jaco Hofmann committed
114
115
	uint32_t i, err = 0;
	uint32_t static_buffer[STATIC_BUFFER_SIZE], *copy_buffer;
116
117
118
119
120
	bool use_dynamic = false;
	fflink_notice("Called for device minor %d\n", ((struct priv_data_struct *) filp->private_data)->minor);

	copy_buffer = static_buffer;

Jaco Hofmann's avatar
Jaco Hofmann committed
121
	if (count != sizeof(struct user_rw_params)) {
122
123
124
		fflink_warn("Wrong size to parse parameters accordingly %ld vs %ld\n", count, sizeof(struct user_rw_params));
		return -EACCES;
	}
Jaco Hofmann's avatar
Jaco Hofmann committed
125
	if (copy_from_user(&params, buf, count)) {
126
127
128
129
		fflink_warn("Couldn't copy all bytes from user-space to parse parameters\n");
		return -EACCES;
	}

Jaco Hofmann's avatar
Jaco Hofmann committed
130
131
	if (params.btt > STATIC_BUFFER_SIZE * REGISTER_BYTE_SIZE) {
		fflink_info("Allocating %d bytes dynamically - only %d available statically\n", params.btt, STATIC_BUFFER_SIZE * REGISTER_BYTE_SIZE);
132
133

		copy_buffer = kmalloc(params.btt, GFP_KERNEL);
Jaco Hofmann's avatar
Jaco Hofmann committed
134
		if (!copy_buffer) {
135
136
137
138
139
140
141
142
143
			fflink_warn("Couldn't allocate dynamic buffer for transfer\n");
			return -EACCES;
		}

		use_dynamic = true;
	}

	fflink_info("Copy %d bytes from address %llX to address %llX\n", params.btt, params.fpga_addr, params.host_addr);

Jaco Hofmann's avatar
Jaco Hofmann committed
144
145
146
147
148
149
150
151
152
153
154
	if (params.btt == 4) {
		copy_buffer[0] = pcie_readl((void*) params.fpga_addr);
	}
	else if (params.btt == 8) {
		((uint64_t*)copy_buffer)[0] = pcie_readq((void*) params.fpga_addr);
	} else {
		for (i = 0; i < params.btt / 4; i++)
			copy_buffer[i] = pcie_readl((void*) (params.fpga_addr + i * 4));
	}

	if (copy_to_user((void *)params.host_addr, copy_buffer, params.btt)) {
155
156
157
158
		fflink_warn("Couldn't copy all bytes to user-space\n");
		err = -EACCES;
	}

Jaco Hofmann's avatar
Jaco Hofmann committed
159
	if (unlikely(use_dynamic)) {
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
		fflink_info("Freeing dynamic buffer\n");
		kfree(copy_buffer);
	}

	return err;
}

/**
 * @brief Write to consecutive register of user-specific function over pcie
	workaroung: using specific struct (user_ioctl_calls) to pass needed parameters
 * @param filp Needed to identify device node and get access to corresponding buffers
 * @param buf Pointer to user space buffer
 * @param count Bytes to be transferred
 * @param f_pos Offset in file, currently not supported
 * @return Zero, if transfer was successful, error code otherwise
 * */
static ssize_t user_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	struct user_rw_params params;
	uint32_t i, err = 0, static_buffer[STATIC_BUFFER_SIZE], *copy_buffer;
	bool use_dynamic = false;
	fflink_notice("Called for device minor %d\n", ((struct priv_data_struct *) filp->private_data)->minor);

	copy_buffer = static_buffer;

Jaco Hofmann's avatar
Jaco Hofmann committed
185
	if (count != sizeof(struct user_rw_params)) {
186
187
188
		fflink_warn("Wrong size to parse parameters accordingly %ld vs %ld\n", count, sizeof(struct user_rw_params));
		return -EACCES;
	}
Jaco Hofmann's avatar
Jaco Hofmann committed
189
	if (copy_from_user(&params, buf, count)) {
190
191
192
193
		fflink_warn("Couldn't copy all bytes from user-space to parse parameters\n");
		return -EACCES;
	}

Jaco Hofmann's avatar
Jaco Hofmann committed
194
195
	if (params.btt > STATIC_BUFFER_SIZE * REGISTER_BYTE_SIZE) {
		fflink_info("Allocating %d bytes dynamically - only %d available statically\n", params.btt, STATIC_BUFFER_SIZE * REGISTER_BYTE_SIZE);
196
197

		copy_buffer = kmalloc(params.btt, GFP_KERNEL);
Jaco Hofmann's avatar
Jaco Hofmann committed
198
		if (!copy_buffer) {
199
200
201
202
203
204
205
			fflink_warn("Couldn't allocate dynamic buffer for transfer\n");
			return -EACCES;
		}

		use_dynamic = true;
	}

Jaco Hofmann's avatar
Jaco Hofmann committed
206
	if (copy_from_user(copy_buffer, (void*)params.host_addr, params.btt)) {
207
208
209
210
211
212
		fflink_warn("Couldn't copy all bytes from user-space\n");
		err = -EACCES;
		goto USER_WRITE_CLEANUP;
	}

	fflink_info("Copy %d bytes to address %llX from address %llX\n", params.btt, params.fpga_addr, params.host_addr);
Jaco Hofmann's avatar
Jaco Hofmann committed
213
214
215
216
217
218
219
220
221
	if (params.btt == 4) {
		pcie_writel(copy_buffer[0], (void*) params.fpga_addr);
	}
	else if (params.btt == 8) {
		pcie_writeq(((uint64_t*)copy_buffer)[0], (void*) params.fpga_addr);
	} else {
		for (i = 0; i < params.btt / 4; i++)
			pcie_writel(copy_buffer[i], (void*) (params.fpga_addr + i * 4));
	}
222
223

USER_WRITE_CLEANUP:
Jaco Hofmann's avatar
Jaco Hofmann committed
224
	if (unlikely(use_dynamic)) {
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
		fflink_info("Freeing dynamic buffer\n");
		kfree(copy_buffer);
	}

	return err;
}


/******************************************************************************/
/* function for user-space interaction */

/**
 * @brief User space communication to trigger hw-function to run and wait for its interrupt
	identification of function is done with event field of the struct user_ioctl_params
 * @param filp Needed to identify device node and get access to corresponding buffers
 * @param ioctl_num magic number from ioctl_calls header
 * @param ioctl_param pointer to arguments from user corresponding to provided commands
 * @return Zero, if command could be executed successfully, otherwise error code
 * */
static long user_ioctl(struct file *filp, unsigned int ioctl_num, unsigned long ioctl_param)
{
246
	int irq_counter;
247
248
249
250
	struct priv_data_struct * p = (struct priv_data_struct *) filp->private_data;
	struct user_ioctl_params params;
	fflink_notice("Called for device minor %d\n", p->minor);

Jaco Hofmann's avatar
Jaco Hofmann committed
251
	if (_IOC_SIZE(ioctl_num) != sizeof(struct user_ioctl_params)) {
252
253
254
		fflink_warn("Wrong size to read out registers %d vs %ld\n", _IOC_SIZE(ioctl_num), sizeof(struct user_ioctl_params));
		return -EACCES;
	}
Jaco Hofmann's avatar
Jaco Hofmann committed
255
	if (copy_from_user(&params, (void *)ioctl_param, _IOC_SIZE(ioctl_num))) {
256
257
258
259
		fflink_warn("Couldn't copy all bytes\n");
		return -EACCES;
	}

Jaco Hofmann's avatar
Jaco Hofmann committed
260
261
262
263
	switch (ioctl_num) {
	case IOCTL_CMD_USER_WAIT_EVENT:
		fflink_info("IOCTL_CMD_USER_WAIT_EVENT with Param-Size: %d byte\n", _IOC_SIZE(ioctl_num));
		fflink_info("Want to write %X to address %llX with event %d\n", params.data, params.fpga_addr, params.event);
264

Jaco Hofmann's avatar
Jaco Hofmann committed
265
		irq_counter = priv_data.user_condition[params.event];
266

Jaco Hofmann's avatar
Jaco Hofmann committed
267
		pcie_writel(params.data, (void*) params.fpga_addr);
268

Jaco Hofmann's avatar
Jaco Hofmann committed
269
270
		if (wait_event_interruptible(p->user_wait_queue[params.event], (irq_counter != p->user_condition[params.event]))) {
			fflink_warn("got killed while hanging in waiting queue\n");
271
			break;
Jaco Hofmann's avatar
Jaco Hofmann committed
272
273
274
275
276
277
		}

		break;
	default:
		fflink_warn("default case - nothing to do here\n");
		break;
278
279
280
281
282
283
284
285
286
287
288
289
290
	}

	return 0;
}

/******************************************************************************/
/* function for user-space interaction */

/**
 * @brief not implemented yet - could map register to user-space directly here
	but not recommended, cause address space will not be filled completely rather than sparse
	performing a transaction on the 'wholes' will cause deadlocks immediatly
 * @param filp Needed to identify device node and get access to corresponding buffers
291
 * @param vma Struct of virtual memory representation, will be modified to allow user-space access
292
293
294
 * @return Zero, if memory could be mapped, error code otherwise
 * */
static int user_mmap(struct file *filp, struct vm_area_struct *vma)
295
{
296
297
298
299
300
301
302
303
304
	fflink_notice("Called for device minor %d\n", ((struct priv_data_struct *) filp->private_data)->minor);

	return 0;
}

/******************************************************************************/
/* functions for irq-handling */

/**
305
 * @brief Interrupt handler for thread pools
306
307
308
309
310
311
	wakes up corresponding process, which waits for the function to finish
	only work, when function was activated over ioctl
 * @param irq Interrupt number of calling line
 * @param dev_id magic number for interrupt sharing (not needed)
 * @return Tells OS, that irq is handled properly
 * */
312
irqreturn_t intr_handler_user(int irq, void * dev_id)
313
{
314
	int irq_translated = pcie_translate_irq_number(irq) - 4;
Jaco Hofmann's avatar
Jaco Hofmann committed
315
	if (irq_translated != -1) {
316
317
318
319
320
		fflink_info("User interrupt for IRQ %d called with irq %d\n", irq_translated, irq);
		priv_data.user_condition[irq_translated] += 1;
		wake_up_interruptible_sync(&priv_data.user_wait_queue[irq_translated]);
	} else {
		fflink_info("Illegal interrupt %d\n", irq);
321
322
323
324
325
326
327
328
329
330
331
332
333
334
	}
	return IRQ_HANDLED;
}

/******************************************************************************/
/* helper functions externally called e.g. to (un/)load this char device */

/**
 * @brief Registers char device with multiple minor nodes in /dev
 * @param none
 * @return Returns error code or zero if success
 * */
int char_user_register(void)
{
335
	int err = 0, hw_id = 0, i;
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
	struct device *device = NULL;

	fflink_info("Try to add char_device to /dev\n");

	/* create device class to register under sysfs */
	err = alloc_chrdev_region(&char_user_dev_t, 0, FFLINK_USER_NODES, FFLINK_USER_NAME);
	if (err < 0 || MINOR(char_user_dev_t) != 0) {
		fflink_warn("failed to allocate chrdev with %d minors\n", FFLINK_USER_NODES);
		goto error_no_device;
	}

	/* create device class to register under udev/sysfs */
	if (IS_ERR((char_user_class = class_create(THIS_MODULE, FFLINK_USER_NAME)))) {
		fflink_warn("failed to create class\n");
		goto error_class_invalid;
	}

	/* initialize char dev with fops to prepare for adding */
	cdev_init(&char_user_cdev, &user_fops);
	char_user_cdev.owner = THIS_MODULE;

	/* try to add char dev */
	err = cdev_add(&char_user_cdev, char_user_dev_t, FFLINK_USER_NODES);
	if (err) {
		fflink_warn("failed to add char dev\n");
		goto error_add_to_system;
	}

Jaco Hofmann's avatar
Jaco Hofmann committed
364
	for (i = 0; i < FFLINK_USER_NODES; i++) {
365
		/* create device file via udev */
Jaco Hofmann's avatar
Jaco Hofmann committed
366
		device = device_create(char_user_class, NULL, MKDEV(MAJOR(char_user_dev_t), MINOR(char_user_dev_t) + i), NULL, FFLINK_USER_NAME "_%d", MINOR(char_user_dev_t) + i);
367
368
369
370
371
372
373
		if (IS_ERR(device)) {
			err = PTR_ERR(device);
			fflink_warn("failed while device create %d\n", MINOR(char_user_dev_t));
			goto error_device_create;
		}
	}

374
375
376
377
	fflink_notice("Called for device (<%d,%d>)\n", imajor(inode), iminor(inode));

	/* check if ID core is readable */
	hw_id = pcie_readl((void*) HW_ID_ADDR);
Jaco Hofmann's avatar
Jaco Hofmann committed
378
	if (hw_id != HW_ID_MAGIC) {
379
		fflink_warn("ID Core not found (was %X - should: %X)\n", hw_id, HW_ID_MAGIC);
Jaco Hofmann's avatar
Jaco Hofmann committed
380
		goto error_device_create;
381
382
383
	}

	/* init control structures for synchron sys-calls */
Jaco Hofmann's avatar
Jaco Hofmann committed
384
	for (i = 0; i < PE_IRQS; ++i) {
385
386
387
388
		init_waitqueue_head(&priv_data.user_wait_queue[i]);
		priv_data.user_condition[i] = 0;
	}

389
390
391
392
	return 0;

	/* tidy up for everything successfully allocated */
error_device_create:
Jaco Hofmann's avatar
Jaco Hofmann committed
393
394
	for (i = i - 1; i >= 0; i--) {
		device_destroy(char_user_class, MKDEV(MAJOR(char_user_dev_t), MINOR(char_user_dev_t) + i));
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
	}
	cdev_del(&char_user_cdev);
error_add_to_system:
	class_destroy(char_user_class);
error_class_invalid:
	unregister_chrdev_region(char_user_dev_t, FFLINK_USER_NODES);
error_no_device:
	return -ENODEV;
}

/**
 * @brief Unregisters char device, which was initialized with user_register before
 * @param none
 * @return none
 * */
void char_user_unregister(void)
{
	int i;

	fflink_info("Tidy up\n");

Jaco Hofmann's avatar
Jaco Hofmann committed
416
417
	for (i = 0; i < FFLINK_USER_NODES; i++) {
		device_destroy(char_user_class, MKDEV(MAJOR(char_user_dev_t), MINOR(char_user_dev_t) + i));
418
419
420
421
422
423
424
425
426
427
	}

	cdev_del(&char_user_cdev);

	class_destroy(char_user_class);

	unregister_chrdev_region(char_user_dev_t, FFLINK_USER_NODES);
}

/******************************************************************************/