Blame view
block/bsg.c
11.1 KB
8c16567d8
|
1 |
// SPDX-License-Identifier: GPL-2.0 |
3d6392cfb
|
2 |
/* |
0c6a89ba6
|
3 |
* bsg.c - block layer implementation of the sg v4 interface |
3d6392cfb
|
4 |
*/ |
3d6392cfb
|
5 6 7 8 |
#include <linux/module.h> #include <linux/init.h> #include <linux/file.h> #include <linux/blkdev.h> |
3d6392cfb
|
9 |
#include <linux/cdev.h> |
ad5ebd2fa
|
10 |
#include <linux/jiffies.h> |
3d6392cfb
|
11 |
#include <linux/percpu.h> |
598443a21
|
12 |
#include <linux/idr.h> |
3d6392cfb
|
13 |
#include <linux/bsg.h> |
5a0e3ad6a
|
14 |
#include <linux/slab.h> |
3d6392cfb
|
15 16 17 18 |
#include <scsi/scsi.h> #include <scsi/scsi_ioctl.h> #include <scsi/scsi_cmnd.h> |
4e2872d6b
|
19 20 |
#include <scsi/scsi_device.h> #include <scsi/scsi_driver.h> |
3d6392cfb
|
21 |
#include <scsi/sg.h> |
0ed081ce2
|
22 23 |
#define BSG_DESCRIPTION "Block layer SCSI generic (bsg) driver" #define BSG_VERSION "0.4" |
3d6392cfb
|
24 |
|
3124b65da
|
25 26 |
#define bsg_dbg(bd, fmt, ...) \ pr_debug("%s: " fmt, (bd)->name, ##__VA_ARGS__) |
3d6392cfb
|
27 |
struct bsg_device { |
165125e1e
|
28 |
struct request_queue *queue; |
3d6392cfb
|
29 |
spinlock_t lock; |
3d6392cfb
|
30 |
struct hlist_node dev_list; |
db193954e
|
31 |
refcount_t ref_count; |
3ada8b7e9
|
32 |
char name[20]; |
3d6392cfb
|
33 |
int max_queue; |
3d6392cfb
|
34 |
}; |
5309cb38d
|
35 |
#define BSG_DEFAULT_CMDS 64 |
292b7f271
|
36 |
#define BSG_MAX_DEVS 32768 |
3d6392cfb
|
37 |
|
3d6392cfb
|
38 |
static DEFINE_MUTEX(bsg_mutex); |
598443a21
|
39 |
static DEFINE_IDR(bsg_minor_idr); |
3d6392cfb
|
40 |
|
25fd16430
|
41 |
#define BSG_LIST_ARRAY_SIZE 8 |
25fd16430
|
42 |
static struct hlist_head bsg_device_list[BSG_LIST_ARRAY_SIZE]; |
3d6392cfb
|
43 44 |
static struct class *bsg_class; |
46f6ef4af
|
45 |
static int bsg_major; |
3d6392cfb
|
46 |
|
1c1133e1f
|
47 |
static inline struct hlist_head *bsg_dev_idx_hash(int index) |
3d6392cfb
|
48 |
{ |
1c1133e1f
|
49 |
return &bsg_device_list[index & (BSG_LIST_ARRAY_SIZE - 1)]; |
3d6392cfb
|
50 |
} |
17cb960f2
|
51 52 53 54 55 56 57 58 59 60 61 62 |
#define uptr64(val) ((void __user *)(uintptr_t)(val)) static int bsg_scsi_check_proto(struct sg_io_v4 *hdr) { if (hdr->protocol != BSG_PROTOCOL_SCSI || hdr->subprotocol != BSG_SUB_PROTOCOL_SCSI_CMD) return -EINVAL; return 0; } static int bsg_scsi_fill_hdr(struct request *rq, struct sg_io_v4 *hdr, fmode_t mode) |
70e36ecea
|
63 |
{ |
17cb960f2
|
64 |
struct scsi_request *sreq = scsi_req(rq); |
82ed4db49
|
65 |
|
972248e91
|
66 67 68 69 70 |
if (hdr->dout_xfer_len && hdr->din_xfer_len) { pr_warn_once("BIDI support in bsg has been removed. "); return -EOPNOTSUPP; } |
17cb960f2
|
71 72 73 74 |
sreq->cmd_len = hdr->request_len; if (sreq->cmd_len > BLK_MAX_CDB) { sreq->cmd = kzalloc(sreq->cmd_len, GFP_KERNEL); if (!sreq->cmd) |
9f5de6b10
|
75 76 |
return -ENOMEM; } |
70e36ecea
|
77 |
|
17cb960f2
|
78 |
if (copy_from_user(sreq->cmd, uptr64(hdr->request), sreq->cmd_len)) |
70e36ecea
|
79 |
return -EFAULT; |
17cb960f2
|
80 |
if (blk_verify_command(sreq->cmd, mode)) |
70e36ecea
|
81 |
return -EPERM; |
70e36ecea
|
82 83 |
return 0; } |
17cb960f2
|
84 |
static int bsg_scsi_complete_rq(struct request *rq, struct sg_io_v4 *hdr) |
3d6392cfb
|
85 |
{ |
17cb960f2
|
86 |
struct scsi_request *sreq = scsi_req(rq); |
15d10b611
|
87 |
int ret = 0; |
17cb960f2
|
88 89 90 91 92 93 94 95 96 97 |
/* * fill in all the output members */ hdr->device_status = sreq->result & 0xff; hdr->transport_status = host_byte(sreq->result); hdr->driver_status = driver_byte(sreq->result); hdr->info = 0; if (hdr->device_status || hdr->transport_status || hdr->driver_status) hdr->info |= SG_INFO_CHECK; hdr->response_len = 0; |
3d6392cfb
|
98 |
|
17cb960f2
|
99 100 101 102 103 104 105 106 107 |
if (sreq->sense_len && hdr->response) { int len = min_t(unsigned int, hdr->max_response_len, sreq->sense_len); if (copy_to_user(uptr64(hdr->response), sreq->sense, len)) ret = -EFAULT; else hdr->response_len = len; } |
972248e91
|
108 |
if (rq_data_dir(rq) == READ) |
17cb960f2
|
109 |
hdr->din_resid = sreq->resid_len; |
972248e91
|
110 |
else |
17cb960f2
|
111 |
hdr->dout_resid = sreq->resid_len; |
70e36ecea
|
112 |
|
15d10b611
|
113 |
return ret; |
3d6392cfb
|
114 |
} |
17cb960f2
|
115 116 117 118 119 120 121 122 123 124 125 |
static void bsg_scsi_free_rq(struct request *rq) { scsi_req_free_cmd(scsi_req(rq)); } static const struct bsg_ops bsg_scsi_ops = { .check_proto = bsg_scsi_check_proto, .fill_hdr = bsg_scsi_fill_hdr, .complete_rq = bsg_scsi_complete_rq, .free_rq = bsg_scsi_free_rq, }; |
ccf3209f0
|
126 |
static int bsg_sg_io(struct request_queue *q, fmode_t mode, void __user *uarg) |
3d6392cfb
|
127 |
{ |
972248e91
|
128 129 |
struct request *rq; struct bio *bio; |
ccf3209f0
|
130 |
struct sg_io_v4 hdr; |
aebf526b5
|
131 |
int ret; |
c7a841f3a
|
132 |
|
ccf3209f0
|
133 134 |
if (copy_from_user(&hdr, uarg, sizeof(hdr))) return -EFAULT; |
3d6392cfb
|
135 |
|
ccf3209f0
|
136 137 |
if (!q->bsg_dev.class_dev) return -ENXIO; |
3d6392cfb
|
138 |
|
ccf3209f0
|
139 140 141 |
if (hdr.guard != 'Q') return -EINVAL; ret = q->bsg_dev.ops->check_proto(&hdr); |
3d6392cfb
|
142 |
if (ret) |
ccf3209f0
|
143 |
return ret; |
3d6392cfb
|
144 |
|
ccf3209f0
|
145 |
rq = blk_get_request(q, hdr.dout_xfer_len ? |
ff005a066
|
146 |
REQ_OP_SCSI_OUT : REQ_OP_SCSI_IN, 0); |
a492f0754
|
147 |
if (IS_ERR(rq)) |
ccf3209f0
|
148 |
return PTR_ERR(rq); |
f27b087b8
|
149 |
|
ccf3209f0
|
150 |
ret = q->bsg_dev.ops->fill_hdr(rq, &hdr, mode); |
2c9ecdf40
|
151 |
if (ret) |
972248e91
|
152 |
return ret; |
2c9ecdf40
|
153 |
|
ccf3209f0
|
154 |
rq->timeout = msecs_to_jiffies(hdr.timeout); |
17cb960f2
|
155 156 157 158 159 160 |
if (!rq->timeout) rq->timeout = q->sg_timeout; if (!rq->timeout) rq->timeout = BLK_DEFAULT_SG_TIMEOUT; if (rq->timeout < BLK_MIN_SG_TIMEOUT) rq->timeout = BLK_MIN_SG_TIMEOUT; |
ccf3209f0
|
161 162 163 164 165 166 |
if (hdr.dout_xfer_len) { ret = blk_rq_map_user(q, rq, NULL, uptr64(hdr.dout_xferp), hdr.dout_xfer_len, GFP_KERNEL); } else if (hdr.din_xfer_len) { ret = blk_rq_map_user(q, rq, NULL, uptr64(hdr.din_xferp), hdr.din_xfer_len, GFP_KERNEL); |
3d6392cfb
|
167 |
} |
c1c201200
|
168 |
|
17cb960f2
|
169 |
if (ret) |
972248e91
|
170 |
goto out_free_rq; |
17cb960f2
|
171 |
|
ccf3209f0
|
172 |
bio = rq->bio; |
70e36ecea
|
173 |
|
ccf3209f0
|
174 175 |
blk_execute_rq(q, NULL, rq, !(hdr.flags & BSG_FLAG_Q_AT_TAIL)); ret = rq->q->bsg_dev.ops->complete_rq(rq, &hdr); |
70e36ecea
|
176 |
blk_rq_unmap_user(bio); |
972248e91
|
177 178 |
out_free_rq: |
17cb960f2
|
179 |
rq->q->bsg_dev.ops->free_rq(rq); |
70e36ecea
|
180 |
blk_put_request(rq); |
972248e91
|
181 |
if (!ret && copy_to_user(uarg, &hdr, sizeof(hdr))) |
ccf3209f0
|
182 183 |
return -EFAULT; return ret; |
70e36ecea
|
184 |
} |
3d6392cfb
|
185 186 |
static struct bsg_device *bsg_alloc_device(void) { |
3d6392cfb
|
187 |
struct bsg_device *bd; |
3d6392cfb
|
188 189 190 191 192 193 |
bd = kzalloc(sizeof(struct bsg_device), GFP_KERNEL); if (unlikely(!bd)) return NULL; spin_lock_init(&bd->lock); |
5309cb38d
|
194 |
bd->max_queue = BSG_DEFAULT_CMDS; |
3d6392cfb
|
195 |
INIT_HLIST_NODE(&bd->dev_list); |
3d6392cfb
|
196 |
return bd; |
3d6392cfb
|
197 198 199 200 |
} static int bsg_put_device(struct bsg_device *bd) { |
97f46ae45
|
201 |
struct request_queue *q = bd->queue; |
3d6392cfb
|
202 203 |
mutex_lock(&bsg_mutex); |
db193954e
|
204 |
if (!refcount_dec_and_test(&bd->ref_count)) { |
3f27e3ed1
|
205 |
mutex_unlock(&bsg_mutex); |
28519c891
|
206 |
return 0; |
3f27e3ed1
|
207 208 209 210 |
} hlist_del(&bd->dev_list); mutex_unlock(&bsg_mutex); |
3d6392cfb
|
211 |
|
3124b65da
|
212 213 |
bsg_dbg(bd, "tearing down "); |
3d6392cfb
|
214 215 216 217 |
/* * close can always block */ |
5309cb38d
|
218 |
kfree(bd); |
28519c891
|
219 220 |
blk_put_queue(q); return 0; |
3d6392cfb
|
221 222 223 |
} static struct bsg_device *bsg_add_device(struct inode *inode, |
d351af01b
|
224 |
struct request_queue *rq, |
3d6392cfb
|
225 226 |
struct file *file) { |
25fd16430
|
227 |
struct bsg_device *bd; |
3d6392cfb
|
228 |
unsigned char buf[32]; |
d9f972644
|
229 |
|
d6c73964f
|
230 |
lockdep_assert_held(&bsg_mutex); |
09ac46c42
|
231 |
if (!blk_get_queue(rq)) |
c3ff1b90d
|
232 |
return ERR_PTR(-ENXIO); |
3d6392cfb
|
233 234 |
bd = bsg_alloc_device(); |
c3ff1b90d
|
235 236 |
if (!bd) { blk_put_queue(rq); |
3d6392cfb
|
237 |
return ERR_PTR(-ENOMEM); |
c3ff1b90d
|
238 |
} |
3d6392cfb
|
239 |
|
d351af01b
|
240 |
bd->queue = rq; |
0b07de85a
|
241 |
|
db193954e
|
242 |
refcount_set(&bd->ref_count, 1); |
842ea771c
|
243 |
hlist_add_head(&bd->dev_list, bsg_dev_idx_hash(iminor(inode))); |
3d6392cfb
|
244 |
|
3ada8b7e9
|
245 |
strncpy(bd->name, dev_name(rq->bsg_dev.class_dev), sizeof(bd->name) - 1); |
3124b65da
|
246 247 |
bsg_dbg(bd, "bound to <%s>, max queue %d ", |
9e69fbb53
|
248 |
format_dev_t(buf, inode->i_rdev), bd->max_queue); |
3d6392cfb
|
249 |
|
3d6392cfb
|
250 251 |
return bd; } |
842ea771c
|
252 |
static struct bsg_device *__bsg_get_device(int minor, struct request_queue *q) |
3d6392cfb
|
253 |
{ |
43ac9e62c
|
254 |
struct bsg_device *bd; |
3d6392cfb
|
255 |
|
d6c73964f
|
256 |
lockdep_assert_held(&bsg_mutex); |
3d6392cfb
|
257 |
|
b67bfe0d4
|
258 |
hlist_for_each_entry(bd, bsg_dev_idx_hash(minor), dev_list) { |
842ea771c
|
259 |
if (bd->queue == q) { |
db193954e
|
260 |
refcount_inc(&bd->ref_count); |
43ac9e62c
|
261 |
goto found; |
3d6392cfb
|
262 |
} |
3d6392cfb
|
263 |
} |
43ac9e62c
|
264 265 |
bd = NULL; found: |
3d6392cfb
|
266 267 268 269 270 |
return bd; } static struct bsg_device *bsg_get_device(struct inode *inode, struct file *file) { |
598443a21
|
271 272 |
struct bsg_device *bd; struct bsg_class_device *bcd; |
3d6392cfb
|
273 |
|
3d6392cfb
|
274 275 276 |
/* * find the class device */ |
3d6392cfb
|
277 |
mutex_lock(&bsg_mutex); |
598443a21
|
278 |
bcd = idr_find(&bsg_minor_idr, iminor(inode)); |
3d6392cfb
|
279 |
|
d6c73964f
|
280 281 282 283 |
if (!bcd) { bd = ERR_PTR(-ENODEV); goto out_unlock; } |
3d6392cfb
|
284 |
|
842ea771c
|
285 |
bd = __bsg_get_device(iminor(inode), bcd->queue); |
d6c73964f
|
286 287 |
if (!bd) bd = bsg_add_device(inode, bcd->queue, file); |
d45ac4fa8
|
288 |
|
d6c73964f
|
289 290 |
out_unlock: mutex_unlock(&bsg_mutex); |
d45ac4fa8
|
291 |
return bd; |
3d6392cfb
|
292 293 294 295 |
} static int bsg_open(struct inode *inode, struct file *file) { |
75bd2ef14
|
296 |
struct bsg_device *bd; |
75bd2ef14
|
297 |
bd = bsg_get_device(inode, file); |
3d6392cfb
|
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
if (IS_ERR(bd)) return PTR_ERR(bd); file->private_data = bd; return 0; } static int bsg_release(struct inode *inode, struct file *file) { struct bsg_device *bd = file->private_data; file->private_data = NULL; return bsg_put_device(bd); } |
ccf3209f0
|
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
static int bsg_get_command_q(struct bsg_device *bd, int __user *uarg) { return put_user(bd->max_queue, uarg); } static int bsg_set_command_q(struct bsg_device *bd, int __user *uarg) { int queue; if (get_user(queue, uarg)) return -EFAULT; if (queue < 1) return -EINVAL; spin_lock_irq(&bd->lock); bd->max_queue = queue; spin_unlock_irq(&bd->lock); return 0; } |
25fd16430
|
332 |
static long bsg_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
3d6392cfb
|
333 334 |
{ struct bsg_device *bd = file->private_data; |
ccf3209f0
|
335 |
void __user *uarg = (void __user *) arg; |
3d6392cfb
|
336 |
|
3d6392cfb
|
337 |
switch (cmd) { |
ccf3209f0
|
338 339 340 |
/* * Our own ioctls */ |
3d6392cfb
|
341 |
case SG_GET_COMMAND_Q: |
ccf3209f0
|
342 343 344 |
return bsg_get_command_q(bd, uarg); case SG_SET_COMMAND_Q: return bsg_set_command_q(bd, uarg); |
3d6392cfb
|
345 346 347 348 349 350 351 352 353 354 355 356 |
/* * SCSI/sg ioctls */ case SG_GET_VERSION_NUM: case SCSI_IOCTL_GET_IDLUN: case SCSI_IOCTL_GET_BUS_NUMBER: case SG_SET_TIMEOUT: case SG_GET_TIMEOUT: case SG_GET_RESERVED_SIZE: case SG_SET_RESERVED_SIZE: case SG_EMULATED_HOST: |
ccf3209f0
|
357 |
case SCSI_IOCTL_SEND_COMMAND: |
74f3c8aff
|
358 |
return scsi_cmd_ioctl(bd->queue, NULL, file->f_mode, cmd, uarg); |
ccf3209f0
|
359 360 |
case SG_IO: return bsg_sg_io(bd->queue, file->f_mode, uarg); |
3d6392cfb
|
361 |
default: |
3d6392cfb
|
362 |
return -ENOTTY; |
3d6392cfb
|
363 364 |
} } |
7344be053
|
365 |
static const struct file_operations bsg_fops = { |
3d6392cfb
|
366 367 |
.open = bsg_open, .release = bsg_release, |
25fd16430
|
368 |
.unlocked_ioctl = bsg_ioctl, |
3d6392cfb
|
369 |
.owner = THIS_MODULE, |
6038f373a
|
370 |
.llseek = default_llseek, |
3d6392cfb
|
371 |
}; |
d351af01b
|
372 |
void bsg_unregister_queue(struct request_queue *q) |
3d6392cfb
|
373 |
{ |
d351af01b
|
374 |
struct bsg_class_device *bcd = &q->bsg_dev; |
3d6392cfb
|
375 |
|
df468820b
|
376 377 |
if (!bcd->class_dev) return; |
3d6392cfb
|
378 379 |
mutex_lock(&bsg_mutex); |
598443a21
|
380 |
idr_remove(&bsg_minor_idr, bcd->minor); |
37b40adf2
|
381 382 |
if (q->kobj.sd) sysfs_remove_link(&q->kobj, "bsg"); |
ee959b00c
|
383 |
device_unregister(bcd->class_dev); |
3d6392cfb
|
384 |
bcd->class_dev = NULL; |
3d6392cfb
|
385 386 |
mutex_unlock(&bsg_mutex); } |
4cf0723ac
|
387 |
EXPORT_SYMBOL_GPL(bsg_unregister_queue); |
3d6392cfb
|
388 |
|
97f46ae45
|
389 |
int bsg_register_queue(struct request_queue *q, struct device *parent, |
5de815a7e
|
390 |
const char *name, const struct bsg_ops *ops) |
3d6392cfb
|
391 |
{ |
598443a21
|
392 |
struct bsg_class_device *bcd; |
3d6392cfb
|
393 |
dev_t dev; |
bab998d62
|
394 |
int ret; |
ee959b00c
|
395 |
struct device *class_dev = NULL; |
3d6392cfb
|
396 397 398 399 |
/* * we need a proper transport to send commands, not a stacked device */ |
344e9ffcb
|
400 |
if (!queue_is_mq(q)) |
3d6392cfb
|
401 |
return 0; |
d351af01b
|
402 |
bcd = &q->bsg_dev; |
3d6392cfb
|
403 |
memset(bcd, 0, sizeof(*bcd)); |
3d6392cfb
|
404 405 |
mutex_lock(&bsg_mutex); |
292b7f271
|
406 |
|
bab998d62
|
407 408 409 410 411 412 413 |
ret = idr_alloc(&bsg_minor_idr, bcd, 0, BSG_MAX_DEVS, GFP_KERNEL); if (ret < 0) { if (ret == -ENOSPC) { printk(KERN_ERR "bsg: too many bsg devices "); ret = -EINVAL; } |
598443a21
|
414 |
goto unlock; |
598443a21
|
415 |
} |
bab998d62
|
416 |
bcd->minor = ret; |
d351af01b
|
417 |
bcd->queue = q; |
17cb960f2
|
418 |
bcd->ops = ops; |
46f6ef4af
|
419 |
dev = MKDEV(bsg_major, bcd->minor); |
5de815a7e
|
420 |
class_dev = device_create(bsg_class, parent, dev, NULL, "%s", name); |
4e2872d6b
|
421 422 |
if (IS_ERR(class_dev)) { ret = PTR_ERR(class_dev); |
5de815a7e
|
423 |
goto idr_remove; |
4e2872d6b
|
424 425 |
} bcd->class_dev = class_dev; |
abce891a1
|
426 |
if (q->kobj.sd) { |
4e2872d6b
|
427 428 |
ret = sysfs_create_link(&q->kobj, &bcd->class_dev->kobj, "bsg"); if (ret) |
598443a21
|
429 |
goto unregister_class_dev; |
4e2872d6b
|
430 |
} |
3d6392cfb
|
431 432 |
mutex_unlock(&bsg_mutex); return 0; |
6826ee4fd
|
433 |
|
598443a21
|
434 |
unregister_class_dev: |
ee959b00c
|
435 |
device_unregister(class_dev); |
5de815a7e
|
436 |
idr_remove: |
bab998d62
|
437 |
idr_remove(&bsg_minor_idr, bcd->minor); |
598443a21
|
438 |
unlock: |
264a04721
|
439 |
mutex_unlock(&bsg_mutex); |
4e2872d6b
|
440 441 |
return ret; } |
17cb960f2
|
442 443 444 445 446 447 448 449 |
int bsg_scsi_register_queue(struct request_queue *q, struct device *parent) { if (!blk_queue_scsi_passthrough(q)) { WARN_ONCE(true, "Attempt to register a non-SCSI queue "); return -EINVAL; } |
5de815a7e
|
450 |
return bsg_register_queue(q, parent, dev_name(parent), &bsg_scsi_ops); |
17cb960f2
|
451 452 |
} EXPORT_SYMBOL_GPL(bsg_scsi_register_queue); |
4e2872d6b
|
453 |
|
7e7654a92
|
454 |
static struct cdev bsg_cdev; |
292b7f271
|
455 |
|
2c9ede55e
|
456 |
static char *bsg_devnode(struct device *dev, umode_t *mode) |
2bdf91491
|
457 458 459 |
{ return kasprintf(GFP_KERNEL, "bsg/%s", dev_name(dev)); } |
3d6392cfb
|
460 461 462 |
static int __init bsg_init(void) { int ret, i; |
46f6ef4af
|
463 |
dev_t devid; |
3d6392cfb
|
464 |
|
25fd16430
|
465 |
for (i = 0; i < BSG_LIST_ARRAY_SIZE; i++) |
3d6392cfb
|
466 467 468 |
INIT_HLIST_HEAD(&bsg_device_list[i]); bsg_class = class_create(THIS_MODULE, "bsg"); |
28519c891
|
469 470 |
if (IS_ERR(bsg_class)) return PTR_ERR(bsg_class); |
e454cea20
|
471 |
bsg_class->devnode = bsg_devnode; |
3d6392cfb
|
472 |
|
46f6ef4af
|
473 |
ret = alloc_chrdev_region(&devid, 0, BSG_MAX_DEVS, "bsg"); |
9b9f770ce
|
474 475 |
if (ret) goto destroy_bsg_class; |
292b7f271
|
476 |
|
46f6ef4af
|
477 |
bsg_major = MAJOR(devid); |
292b7f271
|
478 |
cdev_init(&bsg_cdev, &bsg_fops); |
46f6ef4af
|
479 |
ret = cdev_add(&bsg_cdev, MKDEV(bsg_major, 0), BSG_MAX_DEVS); |
9b9f770ce
|
480 481 |
if (ret) goto unregister_chrdev; |
3d6392cfb
|
482 |
|
5d3a8cd34
|
483 |
printk(KERN_INFO BSG_DESCRIPTION " version " BSG_VERSION |
0ed081ce2
|
484 485 |
" loaded (major %d) ", bsg_major); |
3d6392cfb
|
486 |
return 0; |
9b9f770ce
|
487 488 489 490 |
unregister_chrdev: unregister_chrdev_region(MKDEV(bsg_major, 0), BSG_MAX_DEVS); destroy_bsg_class: class_destroy(bsg_class); |
9b9f770ce
|
491 |
return ret; |
3d6392cfb
|
492 493 494 |
} MODULE_AUTHOR("Jens Axboe"); |
0ed081ce2
|
495 |
MODULE_DESCRIPTION(BSG_DESCRIPTION); |
3d6392cfb
|
496 |
MODULE_LICENSE("GPL"); |
4e2872d6b
|
497 |
device_initcall(bsg_init); |