1/*
2 * Copyright (C) 2012 CERN (www.cern.ch)
3 * Author: Alessandro Rubini <rubini@gnudd.com>
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
20static LIST_HEAD(fc_devices);
21static DEFINE_SPINLOCK(fc_lock);
22
23struct 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 */
30static 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
49static 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 */
57static 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
78static 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
99static 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 .. */
110static int fc_probe(struct fmc_device *fmc);
111static int fc_remove(struct fmc_device *fmc);
112
113static 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 */
122FMC_PARAM_BUSID(fc_drv);
123
124/* probe and remove must allocate and release a misc device */
125static 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);
139	if (!fc)
140		return -ENOMEM;
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
146	ret = misc_register(&fc->misc);
147	if (ret < 0)
148		goto out;
149	spin_lock(&fc_lock);
150	list_add(&fc->list, &fc_devices);
151	spin_unlock(&fc_lock);
152	dev_info(&fc->fmc->dev, "Created misc device \"%s\"\n",
153		 fc->misc.name);
154	return 0;
155
156out:
157	kfree(fc->misc.name);
158	kfree(fc);
159	return ret;
160}
161
162static 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);
176	spin_unlock(&fc_lock);
177	misc_deregister(&fc->misc);
178	kfree(fc->misc.name);
179	kfree(fc);
180
181	return 0;
182}
183
184
185static int fc_init(void)
186{
187	int ret;
188
189	ret = fmc_driver_register(&fc_drv);
190	return ret;
191}
192
193static void fc_exit(void)
194{
195	fmc_driver_unregister(&fc_drv);
196}
197
198module_init(fc_init);
199module_exit(fc_exit);
200
201MODULE_LICENSE("GPL");
202