raw.c 6.91 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
 * linux/drivers/char/raw.c
 *
 * Front-end raw character devices.  These can be bound to any block
 * devices to provide genuine Unix raw character device semantics.
 *
 * We reserve minor number 0 for a control interface.  ioctl()s on this
 * device are used to bind the other minor numbers to block devices.
 */

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/major.h>
#include <linux/blkdev.h>
#include <linux/module.h>
#include <linux/raw.h>
#include <linux/capability.h>
#include <linux/uio.h>
#include <linux/cdev.h>
#include <linux/device.h>
21
#include <linux/mutex.h>
Linus Torvalds's avatar
Linus Torvalds committed
22
23
24
25
26
27
28
29

#include <asm/uaccess.h>

struct raw_device_data {
	struct block_device *binding;
	int inuse;
};

30
static struct class *raw_class;
Linus Torvalds's avatar
Linus Torvalds committed
31
static struct raw_device_data raw_devices[MAX_RAW_MINORS];
32
static DEFINE_MUTEX(raw_mutex);
33
static const struct file_operations raw_ctl_fops; /* forward declaration */
Linus Torvalds's avatar
Linus Torvalds committed
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

/*
 * Open/close code for raw IO.
 *
 * We just rewrite the i_mapping for the /dev/raw/rawN file descriptor to
 * point at the blockdev's address_space and set the file handle to use
 * O_DIRECT.
 *
 * Set the device's soft blocksize to the minimum possible.  This gives the
 * finest possible alignment and has no adverse impact on performance.
 */
static int raw_open(struct inode *inode, struct file *filp)
{
	const int minor = iminor(inode);
	struct block_device *bdev;
	int err;

	if (minor == 0) {	/* It is the control device */
		filp->f_op = &raw_ctl_fops;
		return 0;
	}

56
	mutex_lock(&raw_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

	/*
	 * All we need to do on open is check that the device is bound.
	 */
	bdev = raw_devices[minor].binding;
	err = -ENODEV;
	if (!bdev)
		goto out;
	igrab(bdev->bd_inode);
	err = blkdev_get(bdev, filp->f_mode, 0);
	if (err)
		goto out;
	err = bd_claim(bdev, raw_open);
	if (err)
		goto out1;
	err = set_blocksize(bdev, bdev_hardsect_size(bdev));
	if (err)
		goto out2;
	filp->f_flags |= O_DIRECT;
	filp->f_mapping = bdev->bd_inode->i_mapping;
	if (++raw_devices[minor].inuse == 1)
78
		filp->f_path.dentry->d_inode->i_mapping =
Linus Torvalds's avatar
Linus Torvalds committed
79
80
			bdev->bd_inode->i_mapping;
	filp->private_data = bdev;
81
	mutex_unlock(&raw_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
82
83
84
85
86
87
88
	return 0;

out2:
	bd_release(bdev);
out1:
	blkdev_put(bdev);
out:
89
	mutex_unlock(&raw_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
90
91
92
93
94
95
96
97
98
99
100
101
	return err;
}

/*
 * When the final fd which refers to this character-special node is closed, we
 * make its ->mapping point back at its own i_data.
 */
static int raw_release(struct inode *inode, struct file *filp)
{
	const int minor= iminor(inode);
	struct block_device *bdev;

102
	mutex_lock(&raw_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
103
104
105
106
107
108
	bdev = raw_devices[minor].binding;
	if (--raw_devices[minor].inuse == 0) {
		/* Here  inode->i_mapping == bdev->bd_inode->i_mapping  */
		inode->i_mapping = &inode->i_data;
		inode->i_mapping->backing_dev_info = &default_backing_dev_info;
	}
109
	mutex_unlock(&raw_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

	bd_release(bdev);
	blkdev_put(bdev);
	return 0;
}

/*
 * Forward ioctls to the underlying block device.
 */
static int
raw_ioctl(struct inode *inode, struct file *filp,
		  unsigned int command, unsigned long arg)
{
	struct block_device *bdev = filp->private_data;

125
	return blkdev_ioctl(bdev->bd_inode, NULL, command, arg);
Linus Torvalds's avatar
Linus Torvalds committed
126
127
128
129
}

static void bind_device(struct raw_config_request *rq)
{
130
131
132
	device_destroy(raw_class, MKDEV(RAW_MAJOR, rq->raw_minor));
	device_create(raw_class, NULL, MKDEV(RAW_MAJOR, rq->raw_minor),
		      "raw%d", rq->raw_minor);
Linus Torvalds's avatar
Linus Torvalds committed
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
}

/*
 * Deal with ioctls against the raw-device control interface, to bind
 * and unbind other raw devices.
 */
static int raw_ctl_ioctl(struct inode *inode, struct file *filp,
			unsigned int command, unsigned long arg)
{
	struct raw_config_request rq;
	struct raw_device_data *rawdev;
	int err = 0;

	switch (command) {
	case RAW_SETBIND:
	case RAW_GETBIND:

		/* First, find out which raw minor we want */

		if (copy_from_user(&rq, (void __user *) arg, sizeof(rq))) {
			err = -EFAULT;
			goto out;
		}

157
		if (rq.raw_minor <= 0 || rq.raw_minor >= MAX_RAW_MINORS) {
Linus Torvalds's avatar
Linus Torvalds committed
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
			err = -EINVAL;
			goto out;
		}
		rawdev = &raw_devices[rq.raw_minor];

		if (command == RAW_SETBIND) {
			dev_t dev;

			/*
			 * This is like making block devices, so demand the
			 * same capability
			 */
			if (!capable(CAP_SYS_ADMIN)) {
				err = -EPERM;
				goto out;
			}

			/*
			 * For now, we don't need to check that the underlying
			 * block device is present or not: we can do that when
			 * the raw device is opened.  Just check that the
			 * major/minor numbers make sense.
			 */

			dev = MKDEV(rq.block_major, rq.block_minor);
			if ((rq.block_major == 0 && rq.block_minor != 0) ||
					MAJOR(dev) != rq.block_major ||
					MINOR(dev) != rq.block_minor) {
				err = -EINVAL;
				goto out;
			}

190
			mutex_lock(&raw_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
191
			if (rawdev->inuse) {
192
				mutex_unlock(&raw_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
193
194
195
196
197
198
199
200
201
202
				err = -EBUSY;
				goto out;
			}
			if (rawdev->binding) {
				bdput(rawdev->binding);
				module_put(THIS_MODULE);
			}
			if (rq.block_major == 0 && rq.block_minor == 0) {
				/* unbind */
				rawdev->binding = NULL;
203
				device_destroy(raw_class,
204
						MKDEV(RAW_MAJOR, rq.raw_minor));
Linus Torvalds's avatar
Linus Torvalds committed
205
206
207
208
209
210
211
212
213
			} else {
				rawdev->binding = bdget(dev);
				if (rawdev->binding == NULL)
					err = -ENOMEM;
				else {
					__module_get(THIS_MODULE);
					bind_device(&rq);
				}
			}
214
			mutex_unlock(&raw_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
215
216
217
		} else {
			struct block_device *bdev;

218
			mutex_lock(&raw_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
219
220
221
222
223
224
225
			bdev = rawdev->binding;
			if (bdev) {
				rq.block_major = MAJOR(bdev->bd_dev);
				rq.block_minor = MINOR(bdev->bd_dev);
			} else {
				rq.block_major = rq.block_minor = 0;
			}
226
			mutex_unlock(&raw_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
227
228
229
230
231
232
233
234
235
236
237
238
239
240
			if (copy_to_user((void __user *)arg, &rq, sizeof(rq))) {
				err = -EFAULT;
				goto out;
			}
		}
		break;
	default:
		err = -EINVAL;
		break;
	}
out:
	return err;
}

241
static const struct file_operations raw_fops = {
242
	.read	=	do_sync_read,
Linus Torvalds's avatar
Linus Torvalds committed
243
	.aio_read = 	generic_file_aio_read,
244
	.write	=	do_sync_write,
245
	.aio_write = 	generic_file_aio_write_nolock,
Linus Torvalds's avatar
Linus Torvalds committed
246
247
248
249
250
251
	.open	=	raw_open,
	.release=	raw_release,
	.ioctl	=	raw_ioctl,
	.owner	=	THIS_MODULE,
};

252
static const struct file_operations raw_ctl_fops = {
Linus Torvalds's avatar
Linus Torvalds committed
253
254
255
256
257
258
259
260
261
262
263
264
265
	.ioctl	=	raw_ctl_ioctl,
	.open	=	raw_open,
	.owner	=	THIS_MODULE,
};

static struct cdev raw_cdev = {
	.kobj	=	{.name = "raw", },
	.owner	=	THIS_MODULE,
};

static int __init raw_init(void)
{
	dev_t dev = MKDEV(RAW_MAJOR, 0);
266
	int ret;
Linus Torvalds's avatar
Linus Torvalds committed
267

268
269
	ret = register_chrdev_region(dev, MAX_RAW_MINORS, "raw");
	if (ret)
Linus Torvalds's avatar
Linus Torvalds committed
270
271
272
		goto error;

	cdev_init(&raw_cdev, &raw_fops);
273
274
	ret = cdev_add(&raw_cdev, dev, MAX_RAW_MINORS);
	if (ret) {
Linus Torvalds's avatar
Linus Torvalds committed
275
		kobject_put(&raw_cdev.kobj);
276
		goto error_region;
Linus Torvalds's avatar
Linus Torvalds committed
277
278
	}

279
	raw_class = class_create(THIS_MODULE, "raw");
Linus Torvalds's avatar
Linus Torvalds committed
280
281
282
	if (IS_ERR(raw_class)) {
		printk(KERN_ERR "Error creating raw class.\n");
		cdev_del(&raw_cdev);
283
284
		ret = PTR_ERR(raw_class);
		goto error_region;
Linus Torvalds's avatar
Linus Torvalds committed
285
	}
286
	device_create(raw_class, NULL, MKDEV(RAW_MAJOR, 0), "rawctl");
Linus Torvalds's avatar
Linus Torvalds committed
287
288
289

	return 0;

290
291
error_region:
	unregister_chrdev_region(dev, MAX_RAW_MINORS);
Linus Torvalds's avatar
Linus Torvalds committed
292
error:
293
	return ret;
Linus Torvalds's avatar
Linus Torvalds committed
294
295
296
297
}

static void __exit raw_exit(void)
{
298
	device_destroy(raw_class, MKDEV(RAW_MAJOR, 0));
299
	class_destroy(raw_class);
Linus Torvalds's avatar
Linus Torvalds committed
300
301
302
303
304
305
306
	cdev_del(&raw_cdev);
	unregister_chrdev_region(MKDEV(RAW_MAJOR, 0), MAX_RAW_MINORS);
}

module_init(raw_init);
module_exit(raw_exit);
MODULE_LICENSE("GPL");