Commit | Line | Data |
---|---|---|
b6058d0f MN |
1 | /* |
2 | * storage_common.c -- Common definitions for mass storage functionality | |
3 | * | |
4 | * Copyright (C) 2003-2008 Alan Stern | |
5 | * Copyeight (C) 2009 Samsung Electronics | |
54b8360f | 6 | * Author: Michal Nazarewicz (mina86@mina86.com) |
d6181702 MN |
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; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
d6181702 MN |
12 | */ |
13 | ||
d6181702 MN |
14 | /* |
15 | * This file requires the following identifiers used in USB strings to | |
16 | * be defined (each of type pointer to char): | |
d6181702 | 17 | * - fsg_string_interface -- name of the interface |
93bcf12e MN |
18 | */ |
19 | ||
6532c7fd PF |
20 | /* |
21 | * When USB_GADGET_DEBUG_FILES is defined the module param num_buffers | |
22 | * sets the number of pipeline buffers (length of the fsg_buffhd array). | |
23 | * The valid range of num_buffers is: num >= 2 && num <= 4. | |
24 | */ | |
25 | ||
6fdc5dd2 AP |
26 | #include <linux/module.h> |
27 | #include <linux/blkdev.h> | |
28 | #include <linux/file.h> | |
29 | #include <linux/fs.h> | |
30 | #include <linux/usb/composite.h> | |
b6058d0f | 31 | |
6fdc5dd2 | 32 | #include "storage_common.h" |
b6058d0f | 33 | |
fea20dbc | 34 | static inline struct fsg_lun *fsg_lun_from_dev(struct device *dev) |
b6058d0f | 35 | { |
d6181702 | 36 | return container_of(dev, struct fsg_lun, dev); |
b6058d0f MN |
37 | } |
38 | ||
b6058d0f MN |
39 | /* There is only one interface. */ |
40 | ||
6fdc5dd2 | 41 | struct usb_interface_descriptor fsg_intf_desc = { |
d6181702 | 42 | .bLength = sizeof fsg_intf_desc, |
b6058d0f MN |
43 | .bDescriptorType = USB_DT_INTERFACE, |
44 | ||
d26a6aa0 | 45 | .bNumEndpoints = 2, /* Adjusted during fsg_bind() */ |
b6058d0f | 46 | .bInterfaceClass = USB_CLASS_MASS_STORAGE, |
d26a6aa0 MN |
47 | .bInterfaceSubClass = USB_SC_SCSI, /* Adjusted during fsg_bind() */ |
48 | .bInterfaceProtocol = USB_PR_BULK, /* Adjusted during fsg_bind() */ | |
d6181702 | 49 | .iInterface = FSG_STRING_INTERFACE, |
b6058d0f | 50 | }; |
6fdc5dd2 | 51 | EXPORT_SYMBOL(fsg_intf_desc); |
b6058d0f | 52 | |
d0893264 MN |
53 | /* |
54 | * Three full-speed endpoint descriptors: bulk-in, bulk-out, and | |
55 | * interrupt-in. | |
56 | */ | |
b6058d0f | 57 | |
6fdc5dd2 | 58 | struct usb_endpoint_descriptor fsg_fs_bulk_in_desc = { |
b6058d0f MN |
59 | .bLength = USB_DT_ENDPOINT_SIZE, |
60 | .bDescriptorType = USB_DT_ENDPOINT, | |
61 | ||
62 | .bEndpointAddress = USB_DIR_IN, | |
63 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | |
64 | /* wMaxPacketSize set by autoconfiguration */ | |
65 | }; | |
6fdc5dd2 | 66 | EXPORT_SYMBOL(fsg_fs_bulk_in_desc); |
b6058d0f | 67 | |
6fdc5dd2 | 68 | struct usb_endpoint_descriptor fsg_fs_bulk_out_desc = { |
b6058d0f MN |
69 | .bLength = USB_DT_ENDPOINT_SIZE, |
70 | .bDescriptorType = USB_DT_ENDPOINT, | |
71 | ||
72 | .bEndpointAddress = USB_DIR_OUT, | |
73 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | |
74 | /* wMaxPacketSize set by autoconfiguration */ | |
75 | }; | |
6fdc5dd2 | 76 | EXPORT_SYMBOL(fsg_fs_bulk_out_desc); |
b6058d0f | 77 | |
6fdc5dd2 | 78 | struct usb_descriptor_header *fsg_fs_function[] = { |
d6181702 MN |
79 | (struct usb_descriptor_header *) &fsg_intf_desc, |
80 | (struct usb_descriptor_header *) &fsg_fs_bulk_in_desc, | |
81 | (struct usb_descriptor_header *) &fsg_fs_bulk_out_desc, | |
b6058d0f MN |
82 | NULL, |
83 | }; | |
6fdc5dd2 | 84 | EXPORT_SYMBOL(fsg_fs_function); |
b6058d0f MN |
85 | |
86 | ||
87 | /* | |
88 | * USB 2.0 devices need to expose both high speed and full speed | |
89 | * descriptors, unless they only run at full speed. | |
90 | * | |
91 | * That means alternate endpoint descriptors (bigger packets) | |
92 | * and a "device qualifier" ... plus more construction options | |
d0893264 | 93 | * for the configuration descriptor. |
b6058d0f | 94 | */ |
6fdc5dd2 | 95 | struct usb_endpoint_descriptor fsg_hs_bulk_in_desc = { |
b6058d0f MN |
96 | .bLength = USB_DT_ENDPOINT_SIZE, |
97 | .bDescriptorType = USB_DT_ENDPOINT, | |
98 | ||
99 | /* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */ | |
100 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | |
101 | .wMaxPacketSize = cpu_to_le16(512), | |
102 | }; | |
6fdc5dd2 | 103 | EXPORT_SYMBOL(fsg_hs_bulk_in_desc); |
b6058d0f | 104 | |
6fdc5dd2 | 105 | struct usb_endpoint_descriptor fsg_hs_bulk_out_desc = { |
b6058d0f MN |
106 | .bLength = USB_DT_ENDPOINT_SIZE, |
107 | .bDescriptorType = USB_DT_ENDPOINT, | |
108 | ||
109 | /* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */ | |
110 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | |
111 | .wMaxPacketSize = cpu_to_le16(512), | |
d26a6aa0 | 112 | .bInterval = 1, /* NAK every 1 uframe */ |
b6058d0f | 113 | }; |
6fdc5dd2 | 114 | EXPORT_SYMBOL(fsg_hs_bulk_out_desc); |
b6058d0f | 115 | |
93bcf12e | 116 | |
6fdc5dd2 | 117 | struct usb_descriptor_header *fsg_hs_function[] = { |
d6181702 MN |
118 | (struct usb_descriptor_header *) &fsg_intf_desc, |
119 | (struct usb_descriptor_header *) &fsg_hs_bulk_in_desc, | |
120 | (struct usb_descriptor_header *) &fsg_hs_bulk_out_desc, | |
b6058d0f MN |
121 | NULL, |
122 | }; | |
6fdc5dd2 | 123 | EXPORT_SYMBOL(fsg_hs_function); |
b6058d0f | 124 | |
6fdc5dd2 | 125 | struct usb_endpoint_descriptor fsg_ss_bulk_in_desc = { |
4bb99b7c FB |
126 | .bLength = USB_DT_ENDPOINT_SIZE, |
127 | .bDescriptorType = USB_DT_ENDPOINT, | |
128 | ||
129 | /* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */ | |
130 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | |
131 | .wMaxPacketSize = cpu_to_le16(1024), | |
132 | }; | |
6fdc5dd2 | 133 | EXPORT_SYMBOL(fsg_ss_bulk_in_desc); |
4bb99b7c | 134 | |
6fdc5dd2 | 135 | struct usb_ss_ep_comp_descriptor fsg_ss_bulk_in_comp_desc = { |
4bb99b7c FB |
136 | .bLength = sizeof(fsg_ss_bulk_in_comp_desc), |
137 | .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, | |
138 | ||
139 | /*.bMaxBurst = DYNAMIC, */ | |
140 | }; | |
6fdc5dd2 | 141 | EXPORT_SYMBOL(fsg_ss_bulk_in_comp_desc); |
4bb99b7c | 142 | |
6fdc5dd2 | 143 | struct usb_endpoint_descriptor fsg_ss_bulk_out_desc = { |
4bb99b7c FB |
144 | .bLength = USB_DT_ENDPOINT_SIZE, |
145 | .bDescriptorType = USB_DT_ENDPOINT, | |
146 | ||
147 | /* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */ | |
148 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | |
149 | .wMaxPacketSize = cpu_to_le16(1024), | |
150 | }; | |
6fdc5dd2 | 151 | EXPORT_SYMBOL(fsg_ss_bulk_out_desc); |
4bb99b7c | 152 | |
6fdc5dd2 | 153 | struct usb_ss_ep_comp_descriptor fsg_ss_bulk_out_comp_desc = { |
4bb99b7c FB |
154 | .bLength = sizeof(fsg_ss_bulk_in_comp_desc), |
155 | .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, | |
156 | ||
157 | /*.bMaxBurst = DYNAMIC, */ | |
158 | }; | |
6fdc5dd2 | 159 | EXPORT_SYMBOL(fsg_ss_bulk_out_comp_desc); |
4bb99b7c | 160 | |
6fdc5dd2 | 161 | struct usb_descriptor_header *fsg_ss_function[] = { |
4bb99b7c FB |
162 | (struct usb_descriptor_header *) &fsg_intf_desc, |
163 | (struct usb_descriptor_header *) &fsg_ss_bulk_in_desc, | |
164 | (struct usb_descriptor_header *) &fsg_ss_bulk_in_comp_desc, | |
165 | (struct usb_descriptor_header *) &fsg_ss_bulk_out_desc, | |
166 | (struct usb_descriptor_header *) &fsg_ss_bulk_out_comp_desc, | |
4bb99b7c FB |
167 | NULL, |
168 | }; | |
6fdc5dd2 | 169 | EXPORT_SYMBOL(fsg_ss_function); |
b6058d0f MN |
170 | |
171 | ||
172 | /*-------------------------------------------------------------------------*/ | |
173 | ||
d0893264 MN |
174 | /* |
175 | * If the next two routines are called while the gadget is registered, | |
176 | * the caller must own fsg->filesem for writing. | |
177 | */ | |
b6058d0f | 178 | |
6fdc5dd2 | 179 | void fsg_lun_close(struct fsg_lun *curlun) |
d6e16a89 MN |
180 | { |
181 | if (curlun->filp) { | |
182 | LDBG(curlun, "close backing file\n"); | |
183 | fput(curlun->filp); | |
184 | curlun->filp = NULL; | |
185 | } | |
186 | } | |
6fdc5dd2 | 187 | EXPORT_SYMBOL(fsg_lun_close); |
d6e16a89 | 188 | |
6fdc5dd2 | 189 | int fsg_lun_open(struct fsg_lun *curlun, const char *filename) |
b6058d0f MN |
190 | { |
191 | int ro; | |
192 | struct file *filp = NULL; | |
193 | int rc = -EINVAL; | |
194 | struct inode *inode = NULL; | |
195 | loff_t size; | |
196 | loff_t num_sectors; | |
197 | loff_t min_sectors; | |
d6e16a89 MN |
198 | unsigned int blkbits; |
199 | unsigned int blksize; | |
b6058d0f MN |
200 | |
201 | /* R/W if we can, R/O if we must */ | |
e909ef5d | 202 | ro = curlun->initially_ro; |
b6058d0f MN |
203 | if (!ro) { |
204 | filp = filp_open(filename, O_RDWR | O_LARGEFILE, 0); | |
75f1dc0d | 205 | if (PTR_ERR(filp) == -EROFS || PTR_ERR(filp) == -EACCES) |
b6058d0f MN |
206 | ro = 1; |
207 | } | |
208 | if (ro) | |
209 | filp = filp_open(filename, O_RDONLY | O_LARGEFILE, 0); | |
210 | if (IS_ERR(filp)) { | |
211 | LINFO(curlun, "unable to open backing file: %s\n", filename); | |
212 | return PTR_ERR(filp); | |
213 | } | |
214 | ||
215 | if (!(filp->f_mode & FMODE_WRITE)) | |
216 | ro = 1; | |
217 | ||
496ad9aa | 218 | inode = file_inode(filp); |
20818a0c | 219 | if ((!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))) { |
b6058d0f MN |
220 | LINFO(curlun, "invalid file type: %s\n", filename); |
221 | goto out; | |
222 | } | |
223 | ||
d0893264 MN |
224 | /* |
225 | * If we can't read the file, it's no good. | |
226 | * If we can't write the file, use it read-only. | |
227 | */ | |
20818a0c | 228 | if (!(filp->f_op->read || filp->f_op->aio_read)) { |
b6058d0f MN |
229 | LINFO(curlun, "file not readable: %s\n", filename); |
230 | goto out; | |
231 | } | |
232 | if (!(filp->f_op->write || filp->f_op->aio_write)) | |
233 | ro = 1; | |
234 | ||
235 | size = i_size_read(inode->i_mapping->host); | |
236 | if (size < 0) { | |
237 | LINFO(curlun, "unable to find file size: %s\n", filename); | |
238 | rc = (int) size; | |
239 | goto out; | |
240 | } | |
3f565a36 PL |
241 | |
242 | if (curlun->cdrom) { | |
d6e16a89 MN |
243 | blksize = 2048; |
244 | blkbits = 11; | |
3f565a36 | 245 | } else if (inode->i_bdev) { |
d6e16a89 MN |
246 | blksize = bdev_logical_block_size(inode->i_bdev); |
247 | blkbits = blksize_bits(blksize); | |
3f565a36 | 248 | } else { |
d6e16a89 MN |
249 | blksize = 512; |
250 | blkbits = 9; | |
3f565a36 PL |
251 | } |
252 | ||
d6e16a89 | 253 | num_sectors = size >> blkbits; /* File size in logic-block-size blocks */ |
b6058d0f | 254 | min_sectors = 1; |
e909ef5d | 255 | if (curlun->cdrom) { |
3f565a36 PL |
256 | min_sectors = 300; /* Smallest track is 300 frames */ |
257 | if (num_sectors >= 256*60*75) { | |
258 | num_sectors = 256*60*75 - 1; | |
b6058d0f MN |
259 | LINFO(curlun, "file too big: %s\n", filename); |
260 | LINFO(curlun, "using only first %d blocks\n", | |
261 | (int) num_sectors); | |
262 | } | |
263 | } | |
264 | if (num_sectors < min_sectors) { | |
265 | LINFO(curlun, "file too small: %s\n", filename); | |
266 | rc = -ETOOSMALL; | |
267 | goto out; | |
268 | } | |
269 | ||
d6e16a89 MN |
270 | if (fsg_lun_is_open(curlun)) |
271 | fsg_lun_close(curlun); | |
272 | ||
d6e16a89 MN |
273 | curlun->blksize = blksize; |
274 | curlun->blkbits = blkbits; | |
b6058d0f MN |
275 | curlun->ro = ro; |
276 | curlun->filp = filp; | |
277 | curlun->file_length = size; | |
278 | curlun->num_sectors = num_sectors; | |
279 | LDBG(curlun, "open backing file: %s\n", filename); | |
20818a0c | 280 | return 0; |
b6058d0f MN |
281 | |
282 | out: | |
20818a0c | 283 | fput(filp); |
b6058d0f MN |
284 | return rc; |
285 | } | |
6fdc5dd2 | 286 | EXPORT_SYMBOL(fsg_lun_open); |
b6058d0f MN |
287 | |
288 | ||
b6058d0f MN |
289 | /*-------------------------------------------------------------------------*/ |
290 | ||
d0893264 MN |
291 | /* |
292 | * Sync the file data, don't bother with the metadata. | |
293 | * This code was copied from fs/buffer.c:sys_fdatasync(). | |
294 | */ | |
6fdc5dd2 | 295 | int fsg_lun_fsync_sub(struct fsg_lun *curlun) |
b6058d0f MN |
296 | { |
297 | struct file *filp = curlun->filp; | |
298 | ||
299 | if (curlun->ro || !filp) | |
300 | return 0; | |
8018ab05 | 301 | return vfs_fsync(filp, 1); |
b6058d0f | 302 | } |
6fdc5dd2 | 303 | EXPORT_SYMBOL(fsg_lun_fsync_sub); |
b6058d0f | 304 | |
6fdc5dd2 | 305 | void store_cdrom_address(u8 *dest, int msf, u32 addr) |
b6058d0f MN |
306 | { |
307 | if (msf) { | |
308 | /* Convert to Minutes-Seconds-Frames */ | |
309 | addr >>= 2; /* Convert to 2048-byte frames */ | |
310 | addr += 2*75; /* Lead-in occupies 2 seconds */ | |
311 | dest[3] = addr % 75; /* Frames */ | |
312 | addr /= 75; | |
313 | dest[2] = addr % 60; /* Seconds */ | |
314 | addr /= 60; | |
315 | dest[1] = addr; /* Minutes */ | |
316 | dest[0] = 0; /* Reserved */ | |
317 | } else { | |
318 | /* Absolute sector */ | |
319 | put_unaligned_be32(addr, dest); | |
320 | } | |
321 | } | |
6fdc5dd2 | 322 | EXPORT_SYMBOL(store_cdrom_address); |
93f93740 MN |
323 | |
324 | /*-------------------------------------------------------------------------*/ | |
325 | ||
326 | ||
6fdc5dd2 AP |
327 | ssize_t fsg_show_ro(struct device *dev, struct device_attribute *attr, |
328 | char *buf) | |
93f93740 MN |
329 | { |
330 | struct fsg_lun *curlun = fsg_lun_from_dev(dev); | |
331 | ||
332 | return sprintf(buf, "%d\n", fsg_lun_is_open(curlun) | |
333 | ? curlun->ro | |
334 | : curlun->initially_ro); | |
335 | } | |
6fdc5dd2 | 336 | EXPORT_SYMBOL(fsg_show_ro); |
93f93740 | 337 | |
6fdc5dd2 AP |
338 | ssize_t fsg_show_nofua(struct device *dev, struct device_attribute *attr, |
339 | char *buf) | |
a93917d3 AS |
340 | { |
341 | struct fsg_lun *curlun = fsg_lun_from_dev(dev); | |
342 | ||
343 | return sprintf(buf, "%u\n", curlun->nofua); | |
344 | } | |
6fdc5dd2 | 345 | EXPORT_SYMBOL(fsg_show_nofua); |
a93917d3 | 346 | |
6fdc5dd2 AP |
347 | ssize_t fsg_show_file(struct device *dev, struct device_attribute *attr, |
348 | char *buf) | |
93f93740 MN |
349 | { |
350 | struct fsg_lun *curlun = fsg_lun_from_dev(dev); | |
351 | struct rw_semaphore *filesem = dev_get_drvdata(dev); | |
352 | char *p; | |
353 | ssize_t rc; | |
354 | ||
355 | down_read(filesem); | |
d26a6aa0 | 356 | if (fsg_lun_is_open(curlun)) { /* Get the complete pathname */ |
93f93740 MN |
357 | p = d_path(&curlun->filp->f_path, buf, PAGE_SIZE - 1); |
358 | if (IS_ERR(p)) | |
359 | rc = PTR_ERR(p); | |
360 | else { | |
361 | rc = strlen(p); | |
362 | memmove(buf, p, rc); | |
d26a6aa0 | 363 | buf[rc] = '\n'; /* Add a newline */ |
93f93740 MN |
364 | buf[++rc] = 0; |
365 | } | |
d26a6aa0 | 366 | } else { /* No file, return 0 bytes */ |
93f93740 MN |
367 | *buf = 0; |
368 | rc = 0; | |
369 | } | |
370 | up_read(filesem); | |
371 | return rc; | |
372 | } | |
6fdc5dd2 | 373 | EXPORT_SYMBOL(fsg_show_file); |
93f93740 MN |
374 | |
375 | ||
6fdc5dd2 AP |
376 | ssize_t fsg_store_ro(struct device *dev, struct device_attribute *attr, |
377 | const char *buf, size_t count) | |
93f93740 | 378 | { |
fd4477b0 | 379 | ssize_t rc; |
93f93740 MN |
380 | struct fsg_lun *curlun = fsg_lun_from_dev(dev); |
381 | struct rw_semaphore *filesem = dev_get_drvdata(dev); | |
db8fa285 | 382 | unsigned ro; |
93f93740 | 383 | |
db8fa285 MN |
384 | rc = kstrtouint(buf, 2, &ro); |
385 | if (rc) | |
386 | return rc; | |
93f93740 | 387 | |
d0893264 MN |
388 | /* |
389 | * Allow the write-enable status to change only while the | |
390 | * backing file is closed. | |
391 | */ | |
93f93740 MN |
392 | down_read(filesem); |
393 | if (fsg_lun_is_open(curlun)) { | |
394 | LDBG(curlun, "read-only status change prevented\n"); | |
395 | rc = -EBUSY; | |
396 | } else { | |
8156d158 AS |
397 | curlun->ro = ro; |
398 | curlun->initially_ro = ro; | |
93f93740 | 399 | LDBG(curlun, "read-only status set to %d\n", curlun->ro); |
fd4477b0 | 400 | rc = count; |
93f93740 MN |
401 | } |
402 | up_read(filesem); | |
403 | return rc; | |
404 | } | |
6fdc5dd2 | 405 | EXPORT_SYMBOL(fsg_store_ro); |
93f93740 | 406 | |
6fdc5dd2 AP |
407 | ssize_t fsg_store_nofua(struct device *dev, struct device_attribute *attr, |
408 | const char *buf, size_t count) | |
a93917d3 AS |
409 | { |
410 | struct fsg_lun *curlun = fsg_lun_from_dev(dev); | |
db8fa285 MN |
411 | unsigned nofua; |
412 | int ret; | |
a93917d3 | 413 | |
db8fa285 MN |
414 | ret = kstrtouint(buf, 2, &nofua); |
415 | if (ret) | |
416 | return ret; | |
a93917d3 AS |
417 | |
418 | /* Sync data when switching from async mode to sync */ | |
419 | if (!nofua && curlun->nofua) | |
420 | fsg_lun_fsync_sub(curlun); | |
421 | ||
422 | curlun->nofua = nofua; | |
423 | ||
424 | return count; | |
425 | } | |
6fdc5dd2 | 426 | EXPORT_SYMBOL(fsg_store_nofua); |
a93917d3 | 427 | |
6fdc5dd2 AP |
428 | ssize_t fsg_store_file(struct device *dev, struct device_attribute *attr, |
429 | const char *buf, size_t count) | |
93f93740 MN |
430 | { |
431 | struct fsg_lun *curlun = fsg_lun_from_dev(dev); | |
432 | struct rw_semaphore *filesem = dev_get_drvdata(dev); | |
433 | int rc = 0; | |
434 | ||
435 | if (curlun->prevent_medium_removal && fsg_lun_is_open(curlun)) { | |
436 | LDBG(curlun, "eject attempt prevented\n"); | |
d26a6aa0 | 437 | return -EBUSY; /* "Door is locked" */ |
93f93740 MN |
438 | } |
439 | ||
440 | /* Remove a trailing newline */ | |
441 | if (count > 0 && buf[count-1] == '\n') | |
d26a6aa0 | 442 | ((char *) buf)[count-1] = 0; /* Ugh! */ |
93f93740 | 443 | |
93f93740 | 444 | /* Load new medium */ |
d6e16a89 | 445 | down_write(filesem); |
93f93740 | 446 | if (count > 0 && buf[0]) { |
d6e16a89 | 447 | /* fsg_lun_open() will close existing file if any. */ |
93f93740 MN |
448 | rc = fsg_lun_open(curlun, buf); |
449 | if (rc == 0) | |
450 | curlun->unit_attention_data = | |
451 | SS_NOT_READY_TO_READY_TRANSITION; | |
d6e16a89 MN |
452 | } else if (fsg_lun_is_open(curlun)) { |
453 | fsg_lun_close(curlun); | |
454 | curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT; | |
93f93740 MN |
455 | } |
456 | up_write(filesem); | |
457 | return (rc < 0 ? rc : count); | |
458 | } | |
6fdc5dd2 AP |
459 | EXPORT_SYMBOL(fsg_store_file); |
460 | ||
461 | MODULE_LICENSE("GPL"); |