Commit | Line | Data |
---|---|---|
2a13877c JG |
1 | |
2 | /* | |
3 | osdblk.c -- Export a single SCSI OSD object as a Linux block device | |
4 | ||
5 | ||
6 | Copyright 2009 Red Hat, Inc. | |
7 | ||
8 | This program is free software; you can redistribute it and/or modify | |
9 | it under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation. | |
11 | ||
12 | This program is distributed in the hope that it will be useful, | |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | GNU General Public License for more details. | |
16 | ||
17 | You should have received a copy of the GNU General Public License | |
18 | along with this program; see the file COPYING. If not, write to | |
19 | the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
20 | ||
21 | ||
22 | Instructions for use | |
23 | -------------------- | |
24 | ||
25 | 1) Map a Linux block device to an existing OSD object. | |
26 | ||
27 | In this example, we will use partition id 1234, object id 5678, | |
28 | OSD device /dev/osd1. | |
29 | ||
30 | $ echo "1234 5678 /dev/osd1" > /sys/class/osdblk/add | |
31 | ||
32 | ||
33 | 2) List all active blkdev<->object mappings. | |
34 | ||
35 | In this example, we have performed step #1 twice, creating two blkdevs, | |
36 | mapped to two separate OSD objects. | |
37 | ||
38 | $ cat /sys/class/osdblk/list | |
39 | 0 174 1234 5678 /dev/osd1 | |
40 | 1 179 1994 897123 /dev/osd0 | |
41 | ||
42 | The columns, in order, are: | |
43 | - blkdev unique id | |
44 | - blkdev assigned major | |
45 | - OSD object partition id | |
46 | - OSD object id | |
47 | - OSD device | |
48 | ||
49 | ||
50 | 3) Remove an active blkdev<->object mapping. | |
51 | ||
52 | In this example, we remove the mapping with blkdev unique id 1. | |
53 | ||
54 | $ echo 1 > /sys/class/osdblk/remove | |
55 | ||
56 | ||
57 | NOTE: The actual creation and deletion of OSD objects is outside the scope | |
58 | of this driver. | |
59 | ||
60 | */ | |
61 | ||
62 | #include <linux/kernel.h> | |
63 | #include <linux/device.h> | |
64 | #include <linux/module.h> | |
65 | #include <linux/fs.h> | |
5a0e3ad6 | 66 | #include <linux/slab.h> |
2a13877c JG |
67 | #include <scsi/osd_initiator.h> |
68 | #include <scsi/osd_attributes.h> | |
69 | #include <scsi/osd_sec.h> | |
bc47df0f | 70 | #include <scsi/scsi_device.h> |
2a13877c JG |
71 | |
72 | #define DRV_NAME "osdblk" | |
73 | #define PFX DRV_NAME ": " | |
74 | ||
75 | /* #define _OSDBLK_DEBUG */ | |
76 | #ifdef _OSDBLK_DEBUG | |
77 | #define OSDBLK_DEBUG(fmt, a...) \ | |
78 | printk(KERN_NOTICE "osdblk @%s:%d: " fmt, __func__, __LINE__, ##a) | |
79 | #else | |
80 | #define OSDBLK_DEBUG(fmt, a...) \ | |
81 | do { if (0) printk(fmt, ##a); } while (0) | |
82 | #endif | |
83 | ||
84 | MODULE_AUTHOR("Jeff Garzik <jeff@garzik.org>"); | |
85 | MODULE_DESCRIPTION("block device inside an OSD object osdblk.ko"); | |
86 | MODULE_LICENSE("GPL"); | |
87 | ||
88 | struct osdblk_device; | |
89 | ||
90 | enum { | |
91 | OSDBLK_MINORS_PER_MAJOR = 256, /* max minors per blkdev */ | |
92 | OSDBLK_MAX_REQ = 32, /* max parallel requests */ | |
93 | OSDBLK_OP_TIMEOUT = 4 * 60, /* sync OSD req timeout */ | |
94 | }; | |
95 | ||
96 | struct osdblk_request { | |
97 | struct request *rq; /* blk layer request */ | |
98 | struct bio *bio; /* cloned bio */ | |
99 | struct osdblk_device *osdev; /* associated blkdev */ | |
100 | }; | |
101 | ||
102 | struct osdblk_device { | |
103 | int id; /* blkdev unique id */ | |
104 | ||
105 | int major; /* blkdev assigned major */ | |
106 | struct gendisk *disk; /* blkdev's gendisk and rq */ | |
107 | struct request_queue *q; | |
108 | ||
109 | struct osd_dev *osd; /* associated OSD */ | |
110 | ||
111 | char name[32]; /* blkdev name, e.g. osdblk34 */ | |
112 | ||
113 | spinlock_t lock; /* queue lock */ | |
114 | ||
115 | struct osd_obj_id obj; /* OSD partition, obj id */ | |
116 | uint8_t obj_cred[OSD_CAP_LEN]; /* OSD cred */ | |
117 | ||
118 | struct osdblk_request req[OSDBLK_MAX_REQ]; /* request table */ | |
119 | ||
120 | struct list_head node; | |
121 | ||
122 | char osd_path[0]; /* OSD device path */ | |
123 | }; | |
124 | ||
125 | static struct class *class_osdblk; /* /sys/class/osdblk */ | |
126 | static DEFINE_MUTEX(ctl_mutex); /* Serialize open/close/setup/teardown */ | |
127 | static LIST_HEAD(osdblkdev_list); | |
128 | ||
83d5cde4 | 129 | static const struct block_device_operations osdblk_bd_ops = { |
2a13877c JG |
130 | .owner = THIS_MODULE, |
131 | }; | |
132 | ||
133 | static const struct osd_attr g_attr_logical_length = ATTR_DEF( | |
134 | OSD_APAGE_OBJECT_INFORMATION, OSD_ATTR_OI_LOGICAL_LENGTH, 8); | |
135 | ||
136 | static void osdblk_make_credential(u8 cred_a[OSD_CAP_LEN], | |
137 | const struct osd_obj_id *obj) | |
138 | { | |
139 | osd_sec_init_nosec_doall_caps(cred_a, obj, false, true); | |
140 | } | |
141 | ||
142 | /* copied from exofs; move to libosd? */ | |
143 | /* | |
144 | * Perform a synchronous OSD operation. copied from exofs; move to libosd? | |
145 | */ | |
146 | static int osd_sync_op(struct osd_request *or, int timeout, uint8_t *credential) | |
147 | { | |
148 | int ret; | |
149 | ||
150 | or->timeout = timeout; | |
151 | ret = osd_finalize_request(or, 0, credential, NULL); | |
152 | if (ret) | |
153 | return ret; | |
154 | ||
155 | ret = osd_execute_request(or); | |
156 | ||
157 | /* osd_req_decode_sense(or, ret); */ | |
158 | return ret; | |
159 | } | |
160 | ||
161 | /* | |
162 | * Perform an asynchronous OSD operation. copied from exofs; move to libosd? | |
163 | */ | |
164 | static int osd_async_op(struct osd_request *or, osd_req_done_fn *async_done, | |
165 | void *caller_context, u8 *cred) | |
166 | { | |
167 | int ret; | |
168 | ||
169 | ret = osd_finalize_request(or, 0, cred, NULL); | |
170 | if (ret) | |
171 | return ret; | |
172 | ||
173 | ret = osd_execute_request_async(or, async_done, caller_context); | |
174 | ||
175 | return ret; | |
176 | } | |
177 | ||
178 | /* copied from exofs; move to libosd? */ | |
179 | static int extract_attr_from_req(struct osd_request *or, struct osd_attr *attr) | |
180 | { | |
181 | struct osd_attr cur_attr = {.attr_page = 0}; /* start with zeros */ | |
182 | void *iter = NULL; | |
183 | int nelem; | |
184 | ||
185 | do { | |
186 | nelem = 1; | |
187 | osd_req_decode_get_attr_list(or, &cur_attr, &nelem, &iter); | |
188 | if ((cur_attr.attr_page == attr->attr_page) && | |
189 | (cur_attr.attr_id == attr->attr_id)) { | |
190 | attr->len = cur_attr.len; | |
191 | attr->val_ptr = cur_attr.val_ptr; | |
192 | return 0; | |
193 | } | |
194 | } while (iter); | |
195 | ||
196 | return -EIO; | |
197 | } | |
198 | ||
199 | static int osdblk_get_obj_size(struct osdblk_device *osdev, u64 *size_out) | |
200 | { | |
201 | struct osd_request *or; | |
202 | struct osd_attr attr; | |
203 | int ret; | |
204 | ||
205 | /* start request */ | |
206 | or = osd_start_request(osdev->osd, GFP_KERNEL); | |
207 | if (!or) | |
208 | return -ENOMEM; | |
209 | ||
210 | /* create a get-attributes(length) request */ | |
211 | osd_req_get_attributes(or, &osdev->obj); | |
212 | ||
213 | osd_req_add_get_attr_list(or, &g_attr_logical_length, 1); | |
214 | ||
215 | /* execute op synchronously */ | |
216 | ret = osd_sync_op(or, OSDBLK_OP_TIMEOUT, osdev->obj_cred); | |
217 | if (ret) | |
218 | goto out; | |
219 | ||
220 | /* extract length from returned attribute info */ | |
221 | attr = g_attr_logical_length; | |
222 | ret = extract_attr_from_req(or, &attr); | |
223 | if (ret) | |
224 | goto out; | |
225 | ||
226 | *size_out = get_unaligned_be64(attr.val_ptr); | |
227 | ||
228 | out: | |
229 | osd_end_request(or); | |
230 | return ret; | |
231 | ||
232 | } | |
233 | ||
234 | static void osdblk_osd_complete(struct osd_request *or, void *private) | |
235 | { | |
236 | struct osdblk_request *orq = private; | |
237 | struct osd_sense_info osi; | |
238 | int ret = osd_req_decode_sense(or, &osi); | |
239 | ||
240 | if (ret) { | |
241 | ret = -EIO; | |
242 | OSDBLK_DEBUG("osdblk_osd_complete with err=%d\n", ret); | |
243 | } | |
244 | ||
245 | /* complete OSD request */ | |
246 | osd_end_request(or); | |
247 | ||
248 | /* complete request passed to osdblk by block layer */ | |
249 | __blk_end_request_all(orq->rq, ret); | |
250 | } | |
251 | ||
252 | static void bio_chain_put(struct bio *chain) | |
253 | { | |
254 | struct bio *tmp; | |
255 | ||
256 | while (chain) { | |
257 | tmp = chain; | |
258 | chain = chain->bi_next; | |
259 | ||
260 | bio_put(tmp); | |
261 | } | |
262 | } | |
263 | ||
264 | static struct bio *bio_chain_clone(struct bio *old_chain, gfp_t gfpmask) | |
265 | { | |
266 | struct bio *tmp, *new_chain = NULL, *tail = NULL; | |
267 | ||
268 | while (old_chain) { | |
269 | tmp = bio_kmalloc(gfpmask, old_chain->bi_max_vecs); | |
270 | if (!tmp) | |
271 | goto err_out; | |
272 | ||
273 | __bio_clone(tmp, old_chain); | |
274 | tmp->bi_bdev = NULL; | |
275 | gfpmask &= ~__GFP_WAIT; | |
276 | tmp->bi_next = NULL; | |
277 | ||
278 | if (!new_chain) | |
279 | new_chain = tail = tmp; | |
280 | else { | |
281 | tail->bi_next = tmp; | |
282 | tail = tmp; | |
283 | } | |
284 | ||
285 | old_chain = old_chain->bi_next; | |
286 | } | |
287 | ||
288 | return new_chain; | |
289 | ||
290 | err_out: | |
291 | OSDBLK_DEBUG("bio_chain_clone with err\n"); | |
292 | bio_chain_put(new_chain); | |
293 | return NULL; | |
294 | } | |
295 | ||
296 | static void osdblk_rq_fn(struct request_queue *q) | |
297 | { | |
298 | struct osdblk_device *osdev = q->queuedata; | |
299 | ||
300 | while (1) { | |
301 | struct request *rq; | |
302 | struct osdblk_request *orq; | |
303 | struct osd_request *or; | |
304 | struct bio *bio; | |
305 | bool do_write, do_flush; | |
306 | ||
307 | /* peek at request from block layer */ | |
308 | rq = blk_fetch_request(q); | |
309 | if (!rq) | |
310 | break; | |
311 | ||
312 | /* filter out block requests we don't understand */ | |
33659ebb CH |
313 | if (rq->cmd_type != REQ_TYPE_FS && |
314 | !(rq->cmd_flags & REQ_HARDBARRIER)) { | |
2a13877c JG |
315 | blk_end_request_all(rq, 0); |
316 | continue; | |
317 | } | |
318 | ||
319 | /* deduce our operation (read, write, flush) */ | |
320 | /* I wish the block layer simplified cmd_type/cmd_flags/cmd[] | |
321 | * into a clearly defined set of RPC commands: | |
322 | * read, write, flush, scsi command, power mgmt req, | |
323 | * driver-specific, etc. | |
324 | */ | |
325 | ||
326 | do_flush = (rq->special == (void *) 0xdeadbeefUL); | |
327 | do_write = (rq_data_dir(rq) == WRITE); | |
328 | ||
329 | if (!do_flush) { /* osd_flush does not use a bio */ | |
330 | /* a bio clone to be passed down to OSD request */ | |
331 | bio = bio_chain_clone(rq->bio, GFP_ATOMIC); | |
332 | if (!bio) | |
333 | break; | |
334 | } else | |
335 | bio = NULL; | |
336 | ||
337 | /* alloc internal OSD request, for OSD command execution */ | |
338 | or = osd_start_request(osdev->osd, GFP_ATOMIC); | |
339 | if (!or) { | |
340 | bio_chain_put(bio); | |
341 | OSDBLK_DEBUG("osd_start_request with err\n"); | |
342 | break; | |
343 | } | |
344 | ||
345 | orq = &osdev->req[rq->tag]; | |
346 | orq->rq = rq; | |
347 | orq->bio = bio; | |
348 | orq->osdev = osdev; | |
349 | ||
350 | /* init OSD command: flush, write or read */ | |
351 | if (do_flush) | |
352 | osd_req_flush_object(or, &osdev->obj, | |
353 | OSD_CDB_FLUSH_ALL, 0, 0); | |
354 | else if (do_write) | |
355 | osd_req_write(or, &osdev->obj, blk_rq_pos(rq) * 512ULL, | |
356 | bio, blk_rq_bytes(rq)); | |
357 | else | |
358 | osd_req_read(or, &osdev->obj, blk_rq_pos(rq) * 512ULL, | |
359 | bio, blk_rq_bytes(rq)); | |
360 | ||
361 | OSDBLK_DEBUG("%s 0x%x bytes at 0x%llx\n", | |
362 | do_flush ? "flush" : do_write ? | |
363 | "write" : "read", blk_rq_bytes(rq), | |
364 | blk_rq_pos(rq) * 512ULL); | |
365 | ||
366 | /* begin OSD command execution */ | |
367 | if (osd_async_op(or, osdblk_osd_complete, orq, | |
368 | osdev->obj_cred)) { | |
369 | osd_end_request(or); | |
370 | blk_requeue_request(q, rq); | |
371 | bio_chain_put(bio); | |
372 | OSDBLK_DEBUG("osd_execute_request_async with err\n"); | |
373 | break; | |
374 | } | |
375 | ||
376 | /* remove the special 'flush' marker, now that the command | |
377 | * is executing | |
378 | */ | |
379 | rq->special = NULL; | |
380 | } | |
381 | } | |
382 | ||
383 | static void osdblk_prepare_flush(struct request_queue *q, struct request *rq) | |
384 | { | |
385 | /* add driver-specific marker, to indicate that this request | |
386 | * is a flush command | |
387 | */ | |
388 | rq->special = (void *) 0xdeadbeefUL; | |
389 | } | |
390 | ||
391 | static void osdblk_free_disk(struct osdblk_device *osdev) | |
392 | { | |
393 | struct gendisk *disk = osdev->disk; | |
394 | ||
395 | if (!disk) | |
396 | return; | |
397 | ||
398 | if (disk->flags & GENHD_FL_UP) | |
399 | del_gendisk(disk); | |
400 | if (disk->queue) | |
401 | blk_cleanup_queue(disk->queue); | |
402 | put_disk(disk); | |
403 | } | |
404 | ||
405 | static int osdblk_init_disk(struct osdblk_device *osdev) | |
406 | { | |
407 | struct gendisk *disk; | |
408 | struct request_queue *q; | |
409 | int rc; | |
410 | u64 obj_size = 0; | |
411 | ||
412 | /* contact OSD, request size info about the object being mapped */ | |
413 | rc = osdblk_get_obj_size(osdev, &obj_size); | |
414 | if (rc) | |
415 | return rc; | |
416 | ||
417 | /* create gendisk info */ | |
418 | disk = alloc_disk(OSDBLK_MINORS_PER_MAJOR); | |
419 | if (!disk) | |
420 | return -ENOMEM; | |
421 | ||
422 | sprintf(disk->disk_name, DRV_NAME "%d", osdev->id); | |
423 | disk->major = osdev->major; | |
424 | disk->first_minor = 0; | |
425 | disk->fops = &osdblk_bd_ops; | |
426 | disk->private_data = osdev; | |
427 | ||
428 | /* init rq */ | |
429 | q = blk_init_queue(osdblk_rq_fn, &osdev->lock); | |
430 | if (!q) { | |
431 | put_disk(disk); | |
432 | return -ENOMEM; | |
433 | } | |
434 | ||
435 | /* switch queue to TCQ mode; allocate tag map */ | |
436 | rc = blk_queue_init_tags(q, OSDBLK_MAX_REQ, NULL); | |
437 | if (rc) { | |
438 | blk_cleanup_queue(q); | |
439 | put_disk(disk); | |
440 | return rc; | |
441 | } | |
442 | ||
bc47df0f BH |
443 | /* Set our limits to the lower device limits, because osdblk cannot |
444 | * sleep when allocating a lower-request and therefore cannot be | |
445 | * bouncing. | |
446 | */ | |
447 | blk_queue_stack_limits(q, osd_request_queue(osdev->osd)); | |
448 | ||
2a13877c JG |
449 | blk_queue_prep_rq(q, blk_queue_start_tag); |
450 | blk_queue_ordered(q, QUEUE_ORDERED_DRAIN_FLUSH, osdblk_prepare_flush); | |
451 | ||
452 | disk->queue = q; | |
453 | ||
454 | q->queuedata = osdev; | |
455 | ||
456 | osdev->disk = disk; | |
457 | osdev->q = q; | |
458 | ||
459 | /* finally, announce the disk to the world */ | |
460 | set_capacity(disk, obj_size / 512ULL); | |
461 | add_disk(disk); | |
462 | ||
463 | printk(KERN_INFO "%s: Added of size 0x%llx\n", | |
464 | disk->disk_name, (unsigned long long)obj_size); | |
465 | ||
466 | return 0; | |
467 | } | |
468 | ||
469 | /******************************************************************** | |
470 | * /sys/class/osdblk/ | |
471 | * add map OSD object to blkdev | |
472 | * remove unmap OSD object | |
473 | * list show mappings | |
474 | *******************************************************************/ | |
475 | ||
476 | static void class_osdblk_release(struct class *cls) | |
477 | { | |
478 | kfree(cls); | |
479 | } | |
480 | ||
28812fe1 AK |
481 | static ssize_t class_osdblk_list(struct class *c, |
482 | struct class_attribute *attr, | |
483 | char *data) | |
2a13877c JG |
484 | { |
485 | int n = 0; | |
486 | struct list_head *tmp; | |
487 | ||
488 | mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING); | |
489 | ||
490 | list_for_each(tmp, &osdblkdev_list) { | |
491 | struct osdblk_device *osdev; | |
492 | ||
493 | osdev = list_entry(tmp, struct osdblk_device, node); | |
494 | ||
495 | n += sprintf(data+n, "%d %d %llu %llu %s\n", | |
496 | osdev->id, | |
497 | osdev->major, | |
498 | osdev->obj.partition, | |
499 | osdev->obj.id, | |
500 | osdev->osd_path); | |
501 | } | |
502 | ||
503 | mutex_unlock(&ctl_mutex); | |
504 | return n; | |
505 | } | |
506 | ||
28812fe1 AK |
507 | static ssize_t class_osdblk_add(struct class *c, |
508 | struct class_attribute *attr, | |
509 | const char *buf, size_t count) | |
2a13877c JG |
510 | { |
511 | struct osdblk_device *osdev; | |
512 | ssize_t rc; | |
513 | int irc, new_id = 0; | |
514 | struct list_head *tmp; | |
515 | ||
516 | if (!try_module_get(THIS_MODULE)) | |
517 | return -ENODEV; | |
518 | ||
519 | /* new osdblk_device object */ | |
520 | osdev = kzalloc(sizeof(*osdev) + strlen(buf) + 1, GFP_KERNEL); | |
521 | if (!osdev) { | |
522 | rc = -ENOMEM; | |
523 | goto err_out_mod; | |
524 | } | |
525 | ||
526 | /* static osdblk_device initialization */ | |
527 | spin_lock_init(&osdev->lock); | |
528 | INIT_LIST_HEAD(&osdev->node); | |
529 | ||
530 | /* generate unique id: find highest unique id, add one */ | |
531 | ||
532 | mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING); | |
533 | ||
534 | list_for_each(tmp, &osdblkdev_list) { | |
535 | struct osdblk_device *osdev; | |
536 | ||
537 | osdev = list_entry(tmp, struct osdblk_device, node); | |
538 | if (osdev->id > new_id) | |
539 | new_id = osdev->id + 1; | |
540 | } | |
541 | ||
542 | osdev->id = new_id; | |
543 | ||
544 | /* add to global list */ | |
545 | list_add_tail(&osdev->node, &osdblkdev_list); | |
546 | ||
547 | mutex_unlock(&ctl_mutex); | |
548 | ||
549 | /* parse add command */ | |
550 | if (sscanf(buf, "%llu %llu %s", &osdev->obj.partition, &osdev->obj.id, | |
551 | osdev->osd_path) != 3) { | |
552 | rc = -EINVAL; | |
553 | goto err_out_slot; | |
554 | } | |
555 | ||
556 | /* initialize rest of new object */ | |
557 | sprintf(osdev->name, DRV_NAME "%d", osdev->id); | |
558 | ||
559 | /* contact requested OSD */ | |
560 | osdev->osd = osduld_path_lookup(osdev->osd_path); | |
561 | if (IS_ERR(osdev->osd)) { | |
562 | rc = PTR_ERR(osdev->osd); | |
563 | goto err_out_slot; | |
564 | } | |
565 | ||
566 | /* build OSD credential */ | |
567 | osdblk_make_credential(osdev->obj_cred, &osdev->obj); | |
568 | ||
569 | /* register our block device */ | |
570 | irc = register_blkdev(0, osdev->name); | |
571 | if (irc < 0) { | |
572 | rc = irc; | |
573 | goto err_out_osd; | |
574 | } | |
575 | ||
576 | osdev->major = irc; | |
577 | ||
578 | /* set up and announce blkdev mapping */ | |
579 | rc = osdblk_init_disk(osdev); | |
580 | if (rc) | |
581 | goto err_out_blkdev; | |
582 | ||
583 | return count; | |
584 | ||
585 | err_out_blkdev: | |
586 | unregister_blkdev(osdev->major, osdev->name); | |
587 | err_out_osd: | |
588 | osduld_put_device(osdev->osd); | |
589 | err_out_slot: | |
590 | mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING); | |
591 | list_del_init(&osdev->node); | |
592 | mutex_unlock(&ctl_mutex); | |
593 | ||
594 | kfree(osdev); | |
595 | err_out_mod: | |
596 | OSDBLK_DEBUG("Error adding device %s\n", buf); | |
597 | module_put(THIS_MODULE); | |
598 | return rc; | |
599 | } | |
600 | ||
28812fe1 AK |
601 | static ssize_t class_osdblk_remove(struct class *c, |
602 | struct class_attribute *attr, | |
603 | const char *buf, | |
2a13877c JG |
604 | size_t count) |
605 | { | |
606 | struct osdblk_device *osdev = NULL; | |
607 | int target_id, rc; | |
608 | unsigned long ul; | |
609 | struct list_head *tmp; | |
610 | ||
611 | rc = strict_strtoul(buf, 10, &ul); | |
612 | if (rc) | |
613 | return rc; | |
614 | ||
615 | /* convert to int; abort if we lost anything in the conversion */ | |
616 | target_id = (int) ul; | |
617 | if (target_id != ul) | |
618 | return -EINVAL; | |
619 | ||
620 | /* remove object from list immediately */ | |
621 | mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING); | |
622 | ||
623 | list_for_each(tmp, &osdblkdev_list) { | |
624 | osdev = list_entry(tmp, struct osdblk_device, node); | |
625 | if (osdev->id == target_id) { | |
626 | list_del_init(&osdev->node); | |
627 | break; | |
628 | } | |
629 | osdev = NULL; | |
630 | } | |
631 | ||
632 | mutex_unlock(&ctl_mutex); | |
633 | ||
634 | if (!osdev) | |
635 | return -ENOENT; | |
636 | ||
637 | /* clean up and free blkdev and associated OSD connection */ | |
638 | osdblk_free_disk(osdev); | |
639 | unregister_blkdev(osdev->major, osdev->name); | |
640 | osduld_put_device(osdev->osd); | |
641 | kfree(osdev); | |
642 | ||
643 | /* release module ref */ | |
644 | module_put(THIS_MODULE); | |
645 | ||
646 | return count; | |
647 | } | |
648 | ||
649 | static struct class_attribute class_osdblk_attrs[] = { | |
650 | __ATTR(add, 0200, NULL, class_osdblk_add), | |
651 | __ATTR(remove, 0200, NULL, class_osdblk_remove), | |
652 | __ATTR(list, 0444, class_osdblk_list, NULL), | |
653 | __ATTR_NULL | |
654 | }; | |
655 | ||
656 | static int osdblk_sysfs_init(void) | |
657 | { | |
658 | int ret = 0; | |
659 | ||
660 | /* | |
661 | * create control files in sysfs | |
662 | * /sys/class/osdblk/... | |
663 | */ | |
664 | class_osdblk = kzalloc(sizeof(*class_osdblk), GFP_KERNEL); | |
665 | if (!class_osdblk) | |
666 | return -ENOMEM; | |
667 | ||
668 | class_osdblk->name = DRV_NAME; | |
669 | class_osdblk->owner = THIS_MODULE; | |
670 | class_osdblk->class_release = class_osdblk_release; | |
671 | class_osdblk->class_attrs = class_osdblk_attrs; | |
672 | ||
673 | ret = class_register(class_osdblk); | |
674 | if (ret) { | |
675 | kfree(class_osdblk); | |
676 | class_osdblk = NULL; | |
677 | printk(PFX "failed to create class osdblk\n"); | |
678 | return ret; | |
679 | } | |
680 | ||
681 | return 0; | |
682 | } | |
683 | ||
684 | static void osdblk_sysfs_cleanup(void) | |
685 | { | |
686 | if (class_osdblk) | |
687 | class_destroy(class_osdblk); | |
688 | class_osdblk = NULL; | |
689 | } | |
690 | ||
691 | static int __init osdblk_init(void) | |
692 | { | |
693 | int rc; | |
694 | ||
695 | rc = osdblk_sysfs_init(); | |
696 | if (rc) | |
697 | return rc; | |
698 | ||
699 | return 0; | |
700 | } | |
701 | ||
702 | static void __exit osdblk_exit(void) | |
703 | { | |
704 | osdblk_sysfs_cleanup(); | |
705 | } | |
706 | ||
707 | module_init(osdblk_init); | |
708 | module_exit(osdblk_exit); | |
709 |