Commit | Line | Data |
---|---|---|
95b05a7d BH |
1 | /* |
2 | * osd_uld.c - OSD Upper Layer Driver | |
3 | * | |
4 | * A Linux driver module that registers as a SCSI ULD and probes | |
5 | * for OSD type SCSI devices. | |
6 | * It's main function is to export osd devices to in-kernel users like | |
7 | * osdfs and pNFS-objects-LD. It also provides one ioctl for running | |
8 | * in Kernel tests. | |
9 | * | |
10 | * Copyright (C) 2008 Panasas Inc. All rights reserved. | |
11 | * | |
12 | * Authors: | |
13 | * Boaz Harrosh <bharrosh@panasas.com> | |
14 | * Benny Halevy <bhalevy@panasas.com> | |
15 | * | |
16 | * This program is free software; you can redistribute it and/or modify | |
17 | * it under the terms of the GNU General Public License version 2 | |
18 | * | |
19 | * Redistribution and use in source and binary forms, with or without | |
20 | * modification, are permitted provided that the following conditions | |
21 | * are met: | |
22 | * | |
23 | * 1. Redistributions of source code must retain the above copyright | |
24 | * notice, this list of conditions and the following disclaimer. | |
25 | * 2. Redistributions in binary form must reproduce the above copyright | |
26 | * notice, this list of conditions and the following disclaimer in the | |
27 | * documentation and/or other materials provided with the distribution. | |
28 | * 3. Neither the name of the Panasas company nor the names of its | |
29 | * contributors may be used to endorse or promote products derived | |
30 | * from this software without specific prior written permission. | |
31 | * | |
32 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED | |
33 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
34 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
35 | * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
36 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
37 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
38 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR | |
39 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
40 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
41 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
42 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
43 | */ | |
44 | ||
45 | #include <linux/namei.h> | |
46 | #include <linux/cdev.h> | |
47 | #include <linux/fs.h> | |
48 | #include <linux/module.h> | |
49 | #include <linux/device.h> | |
50 | #include <linux/idr.h> | |
51 | #include <linux/major.h> | |
52 | ||
53 | #include <scsi/scsi.h> | |
54 | #include <scsi/scsi_driver.h> | |
55 | #include <scsi/scsi_device.h> | |
56 | #include <scsi/scsi_ioctl.h> | |
57 | ||
58 | #include <scsi/osd_initiator.h> | |
59 | #include <scsi/osd_sec.h> | |
60 | ||
61 | #include "osd_debug.h" | |
62 | ||
63 | #ifndef TYPE_OSD | |
64 | # define TYPE_OSD 0x11 | |
65 | #endif | |
66 | ||
67 | #ifndef SCSI_OSD_MAJOR | |
68 | # define SCSI_OSD_MAJOR 260 | |
69 | #endif | |
70 | #define SCSI_OSD_MAX_MINOR 64 | |
71 | ||
72 | static const char osd_name[] = "osd"; | |
73 | static const char *osd_version_string = "open-osd 0.1.0"; | |
74 | const char osd_symlink[] = "scsi_osd"; | |
75 | ||
76 | MODULE_AUTHOR("Boaz Harrosh <bharrosh@panasas.com>"); | |
77 | MODULE_DESCRIPTION("open-osd Upper-Layer-Driver osd.ko"); | |
78 | MODULE_LICENSE("GPL"); | |
79 | MODULE_ALIAS_CHARDEV_MAJOR(SCSI_OSD_MAJOR); | |
80 | MODULE_ALIAS_SCSI_DEVICE(TYPE_OSD); | |
81 | ||
82 | struct osd_uld_device { | |
83 | int minor; | |
84 | struct kref kref; | |
85 | struct cdev cdev; | |
86 | struct osd_dev od; | |
87 | struct gendisk *disk; | |
88 | struct device *class_member; | |
89 | }; | |
90 | ||
91 | static void __uld_get(struct osd_uld_device *oud); | |
92 | static void __uld_put(struct osd_uld_device *oud); | |
93 | ||
94 | /* | |
95 | * Char Device operations | |
96 | */ | |
97 | ||
98 | static int osd_uld_open(struct inode *inode, struct file *file) | |
99 | { | |
100 | struct osd_uld_device *oud = container_of(inode->i_cdev, | |
101 | struct osd_uld_device, cdev); | |
102 | ||
103 | __uld_get(oud); | |
104 | /* cache osd_uld_device on file handle */ | |
105 | file->private_data = oud; | |
106 | OSD_DEBUG("osd_uld_open %p\n", oud); | |
107 | return 0; | |
108 | } | |
109 | ||
110 | static int osd_uld_release(struct inode *inode, struct file *file) | |
111 | { | |
112 | struct osd_uld_device *oud = file->private_data; | |
113 | ||
114 | OSD_DEBUG("osd_uld_release %p\n", file->private_data); | |
115 | file->private_data = NULL; | |
116 | __uld_put(oud); | |
117 | return 0; | |
118 | } | |
119 | ||
120 | /* FIXME: Only one vector for now */ | |
121 | unsigned g_test_ioctl; | |
122 | do_test_fn *g_do_test; | |
123 | ||
124 | int osduld_register_test(unsigned ioctl, do_test_fn *do_test) | |
125 | { | |
126 | if (g_test_ioctl) | |
127 | return -EINVAL; | |
128 | ||
129 | g_test_ioctl = ioctl; | |
130 | g_do_test = do_test; | |
131 | return 0; | |
132 | } | |
133 | EXPORT_SYMBOL(osduld_register_test); | |
134 | ||
135 | void osduld_unregister_test(unsigned ioctl) | |
136 | { | |
137 | if (ioctl == g_test_ioctl) { | |
138 | g_test_ioctl = 0; | |
139 | g_do_test = NULL; | |
140 | } | |
141 | } | |
142 | EXPORT_SYMBOL(osduld_unregister_test); | |
143 | ||
144 | static do_test_fn *_find_ioctl(unsigned cmd) | |
145 | { | |
146 | if (g_test_ioctl == cmd) | |
147 | return g_do_test; | |
148 | else | |
149 | return NULL; | |
150 | } | |
151 | ||
152 | static long osd_uld_ioctl(struct file *file, unsigned int cmd, | |
153 | unsigned long arg) | |
154 | { | |
155 | struct osd_uld_device *oud = file->private_data; | |
156 | int ret; | |
157 | do_test_fn *do_test; | |
158 | ||
159 | do_test = _find_ioctl(cmd); | |
160 | if (do_test) | |
161 | ret = do_test(&oud->od, cmd, arg); | |
162 | else { | |
163 | OSD_ERR("Unknown ioctl %d: osd_uld_device=%p\n", cmd, oud); | |
164 | ret = -ENOIOCTLCMD; | |
165 | } | |
166 | return ret; | |
167 | } | |
168 | ||
169 | static const struct file_operations osd_fops = { | |
170 | .owner = THIS_MODULE, | |
171 | .open = osd_uld_open, | |
172 | .release = osd_uld_release, | |
173 | .unlocked_ioctl = osd_uld_ioctl, | |
174 | }; | |
175 | ||
e24977d4 | 176 | struct osd_dev *osduld_path_lookup(const char *name) |
b799bc7d | 177 | { |
e24977d4 | 178 | struct path path; |
b799bc7d BH |
179 | struct inode *inode; |
180 | struct cdev *cdev; | |
181 | struct osd_uld_device *uninitialized_var(oud); | |
182 | int error; | |
183 | ||
e24977d4 | 184 | if (!name || !*name) { |
b799bc7d BH |
185 | OSD_ERR("Mount with !path || !*path\n"); |
186 | return ERR_PTR(-EINVAL); | |
187 | } | |
188 | ||
e24977d4 | 189 | error = kern_path(name, LOOKUP_FOLLOW, &path); |
b799bc7d | 190 | if (error) { |
e24977d4 | 191 | OSD_ERR("path_lookup of %s failed=>%d\n", name, error); |
b799bc7d BH |
192 | return ERR_PTR(error); |
193 | } | |
194 | ||
e24977d4 | 195 | inode = path.dentry->d_inode; |
b799bc7d BH |
196 | error = -EINVAL; /* Not the right device e.g osd_uld_device */ |
197 | if (!S_ISCHR(inode->i_mode)) { | |
198 | OSD_DEBUG("!S_ISCHR()\n"); | |
199 | goto out; | |
200 | } | |
201 | ||
202 | cdev = inode->i_cdev; | |
203 | if (!cdev) { | |
204 | OSD_ERR("Before mounting an OSD Based filesystem\n"); | |
e24977d4 AV |
205 | OSD_ERR(" user-mode must open+close the %s device\n", name); |
206 | OSD_ERR(" Example: bash: echo < %s\n", name); | |
b799bc7d BH |
207 | goto out; |
208 | } | |
209 | ||
210 | /* The Magic wand. Is it our char-dev */ | |
211 | /* TODO: Support sg devices */ | |
212 | if (cdev->owner != THIS_MODULE) { | |
e24977d4 | 213 | OSD_ERR("Error mounting %s - is not an OSD device\n", name); |
b799bc7d BH |
214 | goto out; |
215 | } | |
216 | ||
217 | oud = container_of(cdev, struct osd_uld_device, cdev); | |
218 | ||
219 | __uld_get(oud); | |
220 | error = 0; | |
221 | ||
222 | out: | |
e24977d4 | 223 | path_put(&path); |
b799bc7d BH |
224 | return error ? ERR_PTR(error) : &oud->od; |
225 | } | |
226 | EXPORT_SYMBOL(osduld_path_lookup); | |
227 | ||
228 | void osduld_put_device(struct osd_dev *od) | |
229 | { | |
230 | if (od) { | |
231 | struct osd_uld_device *oud = container_of(od, | |
232 | struct osd_uld_device, od); | |
233 | ||
234 | __uld_put(oud); | |
235 | } | |
236 | } | |
237 | EXPORT_SYMBOL(osduld_put_device); | |
238 | ||
95b05a7d BH |
239 | /* |
240 | * Scsi Device operations | |
241 | */ | |
242 | ||
243 | static int __detect_osd(struct osd_uld_device *oud) | |
244 | { | |
245 | struct scsi_device *scsi_device = oud->od.scsi_device; | |
1b9dce94 | 246 | char caps[OSD_CAP_LEN]; |
95b05a7d BH |
247 | int error; |
248 | ||
249 | /* sending a test_unit_ready as first command seems to be needed | |
250 | * by some targets | |
251 | */ | |
252 | OSD_DEBUG("start scsi_test_unit_ready %p %p %p\n", | |
253 | oud, scsi_device, scsi_device->request_queue); | |
254 | error = scsi_test_unit_ready(scsi_device, 10*HZ, 5, NULL); | |
255 | if (error) | |
256 | OSD_ERR("warning: scsi_test_unit_ready failed\n"); | |
257 | ||
1b9dce94 BH |
258 | osd_sec_init_nosec_doall_caps(caps, &osd_root_object, false, true); |
259 | if (osd_auto_detect_ver(&oud->od, caps)) | |
260 | return -ENODEV; | |
261 | ||
95b05a7d BH |
262 | return 0; |
263 | } | |
264 | ||
265 | static struct class *osd_sysfs_class; | |
266 | static DEFINE_IDA(osd_minor_ida); | |
267 | ||
268 | static int osd_probe(struct device *dev) | |
269 | { | |
270 | struct scsi_device *scsi_device = to_scsi_device(dev); | |
271 | struct gendisk *disk; | |
272 | struct osd_uld_device *oud; | |
273 | int minor; | |
274 | int error; | |
275 | ||
276 | if (scsi_device->type != TYPE_OSD) | |
277 | return -ENODEV; | |
278 | ||
279 | do { | |
280 | if (!ida_pre_get(&osd_minor_ida, GFP_KERNEL)) | |
281 | return -ENODEV; | |
282 | ||
283 | error = ida_get_new(&osd_minor_ida, &minor); | |
284 | } while (error == -EAGAIN); | |
285 | ||
286 | if (error) | |
287 | return error; | |
288 | if (minor >= SCSI_OSD_MAX_MINOR) { | |
289 | error = -EBUSY; | |
290 | goto err_retract_minor; | |
291 | } | |
292 | ||
293 | error = -ENOMEM; | |
294 | oud = kzalloc(sizeof(*oud), GFP_KERNEL); | |
295 | if (NULL == oud) | |
296 | goto err_retract_minor; | |
297 | ||
298 | kref_init(&oud->kref); | |
299 | dev_set_drvdata(dev, oud); | |
300 | oud->minor = minor; | |
301 | ||
302 | /* allocate a disk and set it up */ | |
303 | /* FIXME: do we need this since sg has already done that */ | |
304 | disk = alloc_disk(1); | |
305 | if (!disk) { | |
306 | OSD_ERR("alloc_disk failed\n"); | |
307 | goto err_free_osd; | |
308 | } | |
309 | disk->major = SCSI_OSD_MAJOR; | |
310 | disk->first_minor = oud->minor; | |
311 | sprintf(disk->disk_name, "osd%d", oud->minor); | |
312 | oud->disk = disk; | |
313 | ||
314 | /* hold one more reference to the scsi_device that will get released | |
315 | * in __release, in case a logout is happening while fs is mounted | |
316 | */ | |
317 | scsi_device_get(scsi_device); | |
318 | osd_dev_init(&oud->od, scsi_device); | |
319 | ||
320 | /* Detect the OSD Version */ | |
321 | error = __detect_osd(oud); | |
322 | if (error) { | |
323 | OSD_ERR("osd detection failed, non-compatible OSD device\n"); | |
324 | goto err_put_disk; | |
325 | } | |
326 | ||
327 | /* init the char-device for communication with user-mode */ | |
328 | cdev_init(&oud->cdev, &osd_fops); | |
329 | oud->cdev.owner = THIS_MODULE; | |
330 | error = cdev_add(&oud->cdev, | |
331 | MKDEV(SCSI_OSD_MAJOR, oud->minor), 1); | |
332 | if (error) { | |
333 | OSD_ERR("cdev_add failed\n"); | |
334 | goto err_put_disk; | |
335 | } | |
336 | kobject_get(&oud->cdev.kobj); /* 2nd ref see osd_remove() */ | |
337 | ||
338 | /* class_member */ | |
339 | oud->class_member = device_create(osd_sysfs_class, dev, | |
340 | MKDEV(SCSI_OSD_MAJOR, oud->minor), "%s", disk->disk_name); | |
341 | if (IS_ERR(oud->class_member)) { | |
342 | OSD_ERR("class_device_create failed\n"); | |
343 | error = PTR_ERR(oud->class_member); | |
344 | goto err_put_cdev; | |
345 | } | |
346 | ||
347 | dev_set_drvdata(oud->class_member, oud); | |
95b05a7d BH |
348 | |
349 | OSD_INFO("osd_probe %s\n", disk->disk_name); | |
350 | return 0; | |
351 | ||
352 | err_put_cdev: | |
353 | cdev_del(&oud->cdev); | |
354 | err_put_disk: | |
355 | scsi_device_put(scsi_device); | |
356 | put_disk(disk); | |
357 | err_free_osd: | |
358 | dev_set_drvdata(dev, NULL); | |
359 | kfree(oud); | |
360 | err_retract_minor: | |
361 | ida_remove(&osd_minor_ida, minor); | |
362 | return error; | |
363 | } | |
364 | ||
365 | static int osd_remove(struct device *dev) | |
366 | { | |
367 | struct scsi_device *scsi_device = to_scsi_device(dev); | |
368 | struct osd_uld_device *oud = dev_get_drvdata(dev); | |
369 | ||
370 | if (!oud || (oud->od.scsi_device != scsi_device)) { | |
371 | OSD_ERR("Half cooked osd-device %p,%p || %p!=%p", | |
372 | dev, oud, oud ? oud->od.scsi_device : NULL, | |
373 | scsi_device); | |
374 | } | |
375 | ||
95b05a7d BH |
376 | if (oud->class_member) |
377 | device_destroy(osd_sysfs_class, | |
378 | MKDEV(SCSI_OSD_MAJOR, oud->minor)); | |
379 | ||
380 | /* We have 2 references to the cdev. One is released here | |
381 | * and also takes down the /dev/osdX mapping. The second | |
382 | * Will be released in __remove() after all users have released | |
383 | * the osd_uld_device. | |
384 | */ | |
385 | if (oud->cdev.owner) | |
386 | cdev_del(&oud->cdev); | |
387 | ||
388 | __uld_put(oud); | |
389 | return 0; | |
390 | } | |
391 | ||
392 | static void __remove(struct kref *kref) | |
393 | { | |
394 | struct osd_uld_device *oud = container_of(kref, | |
395 | struct osd_uld_device, kref); | |
396 | struct scsi_device *scsi_device = oud->od.scsi_device; | |
397 | ||
398 | /* now let delete the char_dev */ | |
399 | kobject_put(&oud->cdev.kobj); | |
400 | ||
401 | osd_dev_fini(&oud->od); | |
402 | scsi_device_put(scsi_device); | |
403 | ||
404 | OSD_INFO("osd_remove %s\n", | |
405 | oud->disk ? oud->disk->disk_name : NULL); | |
406 | ||
407 | if (oud->disk) | |
408 | put_disk(oud->disk); | |
409 | ||
410 | ida_remove(&osd_minor_ida, oud->minor); | |
411 | kfree(oud); | |
412 | } | |
413 | ||
414 | static void __uld_get(struct osd_uld_device *oud) | |
415 | { | |
416 | kref_get(&oud->kref); | |
417 | } | |
418 | ||
419 | static void __uld_put(struct osd_uld_device *oud) | |
420 | { | |
421 | kref_put(&oud->kref, __remove); | |
422 | } | |
423 | ||
424 | /* | |
425 | * Global driver and scsi registration | |
426 | */ | |
427 | ||
428 | static struct scsi_driver osd_driver = { | |
429 | .owner = THIS_MODULE, | |
430 | .gendrv = { | |
431 | .name = osd_name, | |
432 | .probe = osd_probe, | |
433 | .remove = osd_remove, | |
434 | } | |
435 | }; | |
436 | ||
437 | static int __init osd_uld_init(void) | |
438 | { | |
439 | int err; | |
440 | ||
441 | osd_sysfs_class = class_create(THIS_MODULE, osd_symlink); | |
442 | if (IS_ERR(osd_sysfs_class)) { | |
443 | OSD_ERR("Unable to register sysfs class => %ld\n", | |
444 | PTR_ERR(osd_sysfs_class)); | |
445 | return PTR_ERR(osd_sysfs_class); | |
446 | } | |
447 | ||
448 | err = register_chrdev_region(MKDEV(SCSI_OSD_MAJOR, 0), | |
449 | SCSI_OSD_MAX_MINOR, osd_name); | |
450 | if (err) { | |
451 | OSD_ERR("Unable to register major %d for osd ULD => %d\n", | |
452 | SCSI_OSD_MAJOR, err); | |
453 | goto err_out; | |
454 | } | |
455 | ||
456 | err = scsi_register_driver(&osd_driver.gendrv); | |
457 | if (err) { | |
458 | OSD_ERR("scsi_register_driver failed => %d\n", err); | |
459 | goto err_out_chrdev; | |
460 | } | |
461 | ||
462 | OSD_INFO("LOADED %s\n", osd_version_string); | |
463 | return 0; | |
464 | ||
465 | err_out_chrdev: | |
466 | unregister_chrdev_region(MKDEV(SCSI_OSD_MAJOR, 0), SCSI_OSD_MAX_MINOR); | |
467 | err_out: | |
468 | class_destroy(osd_sysfs_class); | |
469 | return err; | |
470 | } | |
471 | ||
472 | static void __exit osd_uld_exit(void) | |
473 | { | |
474 | scsi_unregister_driver(&osd_driver.gendrv); | |
475 | unregister_chrdev_region(MKDEV(SCSI_OSD_MAJOR, 0), SCSI_OSD_MAX_MINOR); | |
476 | class_destroy(osd_sysfs_class); | |
477 | OSD_INFO("UNLOADED %s\n", osd_version_string); | |
478 | } | |
479 | ||
480 | module_init(osd_uld_init); | |
481 | module_exit(osd_uld_exit); |