Commit 08f64b97 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] s390: tape driver.

From: Martin Schwidefsky <schwidefsky@de.ibm.com>

 - Add module license gpl.
 - Add debug messages.
 - Make blocksize persistent after close. Limit blocksize to 64k.
 - Check tape state against TS_INIT/TS_UNUSED for special case of
   medium sense and assign.
 - Assign tape as soon as they are set online and unassign when set offline.
 - Correct implementation of MT_EOD.
 - Add backward compatible tape.agent hotplug support (to be removed as soon
   as a full blown tape class is implemented).
 - Add state to differentiate between character device and block device access.
 - Make tape block device usable.
 - Add 34xx seek speedup code.
 - Fix device reference counting.
 - Fix online-offline-online cycle.
 - Add timeout to standard assign function.
 - Correct calculation of device index in tape_get_device().
 - Check idal buffer for fixed block size reads and writes.
 - Adapt to notify api change in cio.
 - Add sysfs attributes for tape state, first minor, current operation and
   current blocksize.
parent 21df060f
...@@ -12,18 +12,32 @@ ...@@ -12,18 +12,32 @@
#ifndef _TAPE_H #ifndef _TAPE_H
#define _TAPE_H #define _TAPE_H
#include <asm/ccwdev.h>
#include <asm/debug.h>
#include <asm/idals.h>
#include <linux/config.h> #include <linux/config.h>
#include <linux/blkdev.h> #include <linux/blkdev.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/mtio.h> #include <linux/mtio.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <asm/ccwdev.h> #include <linux/workqueue.h>
#include <asm/debug.h>
#include <asm/idals.h>
struct gendisk; struct gendisk;
/*
* Define DBF_LIKE_HELL for lots of messages in the debug feature.
*/
#define DBF_LIKE_HELL
#ifdef DBF_LIKE_HELL
#define DBF_LH(level, str, ...) \
do { \
debug_sprintf_event(tape_dbf_area, level, str, ## __VA_ARGS__); \
} while (0)
#else
#define DBF_LH(level, str, ...) do {} while(0)
#endif
/* /*
* macros s390 debug feature (dbf) * macros s390 debug feature (dbf)
*/ */
...@@ -46,7 +60,11 @@ do { \ ...@@ -46,7 +60,11 @@ do { \
#define TAPEBLOCK_HSEC_S2B 2 #define TAPEBLOCK_HSEC_S2B 2
#define TAPEBLOCK_RETRIES 5 #define TAPEBLOCK_RETRIES 5
#define TAPE_BUSY(td) (td->treq != NULL) /* Event types for hotplug */
#define TAPE_HOTPLUG_CHAR_ADD 1
#define TAPE_HOTPLUG_BLOCK_ADD 2
#define TAPE_HOTPLUG_CHAR_REMOVE 3
#define TAPE_HOTPLUG_BLOCK_REMOVE 4
enum tape_medium_state { enum tape_medium_state {
MS_UNKNOWN, MS_UNKNOWN,
...@@ -58,6 +76,7 @@ enum tape_medium_state { ...@@ -58,6 +76,7 @@ enum tape_medium_state {
enum tape_state { enum tape_state {
TS_UNUSED=0, TS_UNUSED=0,
TS_IN_USE, TS_IN_USE,
TS_BLKUSE,
TS_INIT, TS_INIT,
TS_NOT_OPER, TS_NOT_OPER,
TS_SIZE TS_SIZE
...@@ -130,8 +149,6 @@ struct tape_discipline { ...@@ -130,8 +149,6 @@ struct tape_discipline {
struct module *owner; struct module *owner;
int (*setup_device)(struct tape_device *); int (*setup_device)(struct tape_device *);
void (*cleanup_device)(struct tape_device *); void (*cleanup_device)(struct tape_device *);
int (*assign)(struct tape_device *);
int (*unassign)(struct tape_device *);
int (*irq)(struct tape_device *, struct tape_request *, struct irb *); int (*irq)(struct tape_device *, struct tape_request *, struct irb *);
struct tape_request *(*read_block)(struct tape_device *, size_t); struct tape_request *(*read_block)(struct tape_device *, size_t);
struct tape_request *(*write_block)(struct tape_device *, size_t); struct tape_request *(*write_block)(struct tape_device *, size_t);
...@@ -168,48 +185,60 @@ struct tape_char_data { ...@@ -168,48 +185,60 @@ struct tape_char_data {
struct tape_blk_data struct tape_blk_data
{ {
/* Block device request queue. */ /* Block device request queue. */
request_queue_t *request_queue; request_queue_t * request_queue;
spinlock_t request_queue_lock; spinlock_t request_queue_lock;
/* Block frontend tasklet */
struct tasklet_struct tasklet; /* Task to move entries from block request to CCS request queue. */
struct work_struct requeue_task;
atomic_t requeue_scheduled;
/* Current position on the tape. */ /* Current position on the tape. */
long block_position; long block_position;
struct gendisk *disk; int medium_changed;
struct gendisk * disk;
}; };
#endif #endif
/* Tape Info */ /* Tape Info */
struct tape_device { struct tape_device {
/* entry in tape_device_list */ /* entry in tape_device_list */
struct list_head node; struct list_head node;
struct ccw_device *cdev; struct ccw_device * cdev;
/* Device discipline information. */ /* Device discipline information. */
struct tape_discipline *discipline; struct tape_discipline * discipline;
void *discdata; void * discdata;
/* Generic status flags */ /* Generic status flags */
long tape_generic_status; long tape_generic_status;
/* Device state information. */ /* Device state information. */
wait_queue_head_t state_change_wq; wait_queue_head_t state_change_wq;
enum tape_state tape_state; enum tape_state tape_state;
enum tape_medium_state medium_state; enum tape_medium_state medium_state;
unsigned char *modeset_byte; unsigned char * modeset_byte;
/* Reference count. */ /* Reference count. */
atomic_t ref_count; atomic_t ref_count;
/* Request queue. */ /* Request queue. */
struct list_head req_queue; struct list_head req_queue;
/* Each tape device has (currently) two minor numbers. */
int first_minor;
/* Number of tapemarks required for correct termination. */
int required_tapemarks;
/* Block ID of the BOF */
unsigned int bof;
int first_minor; /* each tape device has two minors */
/* Character device frontend data */ /* Character device frontend data */
struct tape_char_data char_data; struct tape_char_data char_data;
#ifdef CONFIG_S390_TAPE_BLOCK #ifdef CONFIG_S390_TAPE_BLOCK
/* Block dev frontend data */ /* Block dev frontend data */
struct tape_blk_data blk_data; struct tape_blk_data blk_data;
#endif #endif
}; };
...@@ -219,6 +248,7 @@ extern void tape_free_request(struct tape_request *); ...@@ -219,6 +248,7 @@ extern void tape_free_request(struct tape_request *);
extern int tape_do_io(struct tape_device *, struct tape_request *); extern int tape_do_io(struct tape_device *, struct tape_request *);
extern int tape_do_io_async(struct tape_device *, struct tape_request *); extern int tape_do_io_async(struct tape_device *, struct tape_request *);
extern int tape_do_io_interruptible(struct tape_device *, struct tape_request *); extern int tape_do_io_interruptible(struct tape_device *, struct tape_request *);
void tape_hotplug_event(struct tape_device *, int major, int action);
static inline int static inline int
tape_do_io_free(struct tape_device *device, struct tape_request *request) tape_do_io_free(struct tape_device *device, struct tape_request *request)
...@@ -234,19 +264,19 @@ extern int tape_oper_handler(int irq, int status); ...@@ -234,19 +264,19 @@ extern int tape_oper_handler(int irq, int status);
extern void tape_noper_handler(int irq, int status); extern void tape_noper_handler(int irq, int status);
extern int tape_open(struct tape_device *); extern int tape_open(struct tape_device *);
extern int tape_release(struct tape_device *); extern int tape_release(struct tape_device *);
extern int tape_assign(struct tape_device *);
extern int tape_unassign(struct tape_device *);
extern int tape_mtop(struct tape_device *, int, int); extern int tape_mtop(struct tape_device *, int, int);
extern void tape_state_set(struct tape_device *, enum tape_state);
extern int tape_enable_device(struct tape_device *, struct tape_discipline *); extern int tape_enable_device(struct tape_device *, struct tape_discipline *);
extern void tape_disable_device(struct tape_device *device); extern void tape_disable_device(struct tape_device *device);
/* Externals from tape_devmap.c */ /* Externals from tape_devmap.c */
extern int tape_generic_probe(struct ccw_device *); extern int tape_generic_probe(struct ccw_device *);
extern int tape_generic_remove(struct ccw_device *); extern void tape_generic_remove(struct ccw_device *);
extern struct tape_device *tape_get_device(int devindex); extern struct tape_device *tape_get_device(int devindex);
extern void tape_put_device(struct tape_device *); extern struct tape_device *tape_get_device_reference(struct tape_device *);
extern struct tape_device *tape_put_device(struct tape_device *);
/* Externals from tape_char.c */ /* Externals from tape_char.c */
extern int tapechar_init(void); extern int tapechar_init(void);
......
This diff is collapsed.
This diff is collapsed.
...@@ -19,8 +19,9 @@ ...@@ -19,8 +19,9 @@
#include <asm/uaccess.h> #include <asm/uaccess.h>
#include "tape.h" #include "tape.h"
#include "tape_std.h"
#define PRINTK_HEADER "TCHAR:" #define PRINTK_HEADER "TAPE_CHAR: "
#define TAPECHAR_MAJOR 0 /* get dynamic major */ #define TAPECHAR_MAJOR 0 /* get dynamic major */
...@@ -52,12 +53,14 @@ static int tapechar_major = TAPECHAR_MAJOR; ...@@ -52,12 +53,14 @@ static int tapechar_major = TAPECHAR_MAJOR;
int int
tapechar_setup_device(struct tape_device * device) tapechar_setup_device(struct tape_device * device)
{ {
tape_hotplug_event(device, tapechar_major, TAPE_HOTPLUG_CHAR_ADD);
return 0; return 0;
} }
void void
tapechar_cleanup_device(struct tape_device *device) tapechar_cleanup_device(struct tape_device *device)
{ {
tape_hotplug_event(device, tapechar_major, TAPE_HOTPLUG_CHAR_REMOVE);
} }
/* /*
...@@ -81,15 +84,27 @@ tapechar_check_idalbuffer(struct tape_device *device, size_t block_size) ...@@ -81,15 +84,27 @@ tapechar_check_idalbuffer(struct tape_device *device, size_t block_size)
struct idal_buffer *new; struct idal_buffer *new;
if (device->char_data.idal_buf != NULL && if (device->char_data.idal_buf != NULL &&
device->char_data.idal_buf->size >= block_size) device->char_data.idal_buf->size == block_size)
return 0; return 0;
/* The current idal buffer is not big enough. Allocate a new one. */
if (block_size > MAX_BLOCKSIZE) {
DBF_EVENT(3, "Invalid blocksize (%zd > %d)\n",
block_size, MAX_BLOCKSIZE);
PRINT_ERR("Invalid blocksize (%zd> %d)\n",
block_size, MAX_BLOCKSIZE);
return -EINVAL;
}
/* The current idal buffer is not correct. Allocate a new one. */
new = idal_buffer_alloc(block_size, 0); new = idal_buffer_alloc(block_size, 0);
if (new == NULL) if (new == NULL)
return -ENOMEM; return -ENOMEM;
if (device->char_data.idal_buf != NULL) if (device->char_data.idal_buf != NULL)
idal_buffer_free(device->char_data.idal_buf); idal_buffer_free(device->char_data.idal_buf);
device->char_data.idal_buf = new; device->char_data.idal_buf = new;
return 0; return 0;
} }
...@@ -116,6 +131,16 @@ tapechar_read (struct file *filp, char *data, size_t count, loff_t *ppos) ...@@ -116,6 +131,16 @@ tapechar_read (struct file *filp, char *data, size_t count, loff_t *ppos)
DBF_EVENT(6, "TCHAR:ppos wrong\n"); DBF_EVENT(6, "TCHAR:ppos wrong\n");
return -EOVERFLOW; return -EOVERFLOW;
} }
/*
* If the tape isn't terminated yet, do it now. And since we then
* are at the end of the tape there wouldn't be anything to read
* anyways. So we return immediatly.
*/
if(device->required_tapemarks) {
return tape_std_terminate_write(device);
}
/* Find out block size to use */ /* Find out block size to use */
if (device->char_data.block_size != 0) { if (device->char_data.block_size != 0) {
if (count < device->char_data.block_size) { if (count < device->char_data.block_size) {
...@@ -126,10 +151,17 @@ tapechar_read (struct file *filp, char *data, size_t count, loff_t *ppos) ...@@ -126,10 +151,17 @@ tapechar_read (struct file *filp, char *data, size_t count, loff_t *ppos)
block_size = device->char_data.block_size; block_size = device->char_data.block_size;
} else { } else {
block_size = count; block_size = count;
rc = tapechar_check_idalbuffer(device, block_size);
if (rc)
return rc;
} }
rc = tapechar_check_idalbuffer(device, block_size);
if (rc)
return rc;
#ifdef CONFIG_S390_TAPE_BLOCK
/* Changes position. */
device->blk_data.medium_changed = 1;
#endif
DBF_EVENT(6, "TCHAR:nbytes: %lx\n", block_size); DBF_EVENT(6, "TCHAR:nbytes: %lx\n", block_size);
/* Let the discipline build the ccw chain. */ /* Let the discipline build the ccw chain. */
request = device->discipline->read_block(device, block_size); request = device->discipline->read_block(device, block_size);
...@@ -182,11 +214,18 @@ tapechar_write(struct file *filp, const char *data, size_t count, loff_t *ppos) ...@@ -182,11 +214,18 @@ tapechar_write(struct file *filp, const char *data, size_t count, loff_t *ppos)
nblocks = count / block_size; nblocks = count / block_size;
} else { } else {
block_size = count; block_size = count;
rc = tapechar_check_idalbuffer(device, block_size);
if (rc)
return rc;
nblocks = 1; nblocks = 1;
} }
rc = tapechar_check_idalbuffer(device, block_size);
if (rc)
return rc;
#ifdef CONFIG_S390_TAPE_BLOCK
/* Changes position. */
device->blk_data.medium_changed = 1;
#endif
DBF_EVENT(6,"TCHAR:nbytes: %lx\n", block_size); DBF_EVENT(6,"TCHAR:nbytes: %lx\n", block_size);
DBF_EVENT(6, "TCHAR:nblocks: %x\n", nblocks); DBF_EVENT(6, "TCHAR:nblocks: %x\n", nblocks);
/* Let the discipline build the ccw chain. */ /* Let the discipline build the ccw chain. */
...@@ -225,6 +264,17 @@ tapechar_write(struct file *filp, const char *data, size_t count, loff_t *ppos) ...@@ -225,6 +264,17 @@ tapechar_write(struct file *filp, const char *data, size_t count, loff_t *ppos)
rc = 0; rc = 0;
} }
/*
* After doing a write we always need two tapemarks to correctly
* terminate the tape (one to terminate the file, the second to
* flag the end of recorded data.
* Since process_eov positions the tape in front of the written
* tapemark it doesn't hurt to write two marks again.
*/
if (!rc)
device->required_tapemarks = 2;
return rc ? rc : written; return rc ? rc : written;
} }
...@@ -237,24 +287,28 @@ tapechar_open (struct inode *inode, struct file *filp) ...@@ -237,24 +287,28 @@ tapechar_open (struct inode *inode, struct file *filp)
struct tape_device *device; struct tape_device *device;
int minor, rc; int minor, rc;
DBF_EVENT(6, "TCHAR:open: %i:%i\n",
imajor(filp->f_dentry->d_inode),
iminor(filp->f_dentry->d_inode));
if (imajor(filp->f_dentry->d_inode) != tapechar_major) if (imajor(filp->f_dentry->d_inode) != tapechar_major)
return -ENODEV; return -ENODEV;
minor = iminor(filp->f_dentry->d_inode); minor = iminor(filp->f_dentry->d_inode);
device = tape_get_device(minor / TAPE_MINORS_PER_DEV); device = tape_get_device(minor / TAPE_MINORS_PER_DEV);
if (IS_ERR(device)) { if (IS_ERR(device)) {
DBF_EVENT(3, "TCHAR:open: tape_get_device() failed\n");
return PTR_ERR(device); return PTR_ERR(device);
} }
DBF_EVENT(6, "TCHAR:open: %x\n", iminor(inode));
rc = tape_open(device); rc = tape_open(device);
if (rc == 0) { if (rc == 0) {
rc = tape_assign(device); filp->private_data = device;
if (rc == 0) { return 0;
filp->private_data = device;
return 0;
}
tape_release(device);
} }
tape_put_device(device); tape_put_device(device);
return rc; return rc;
} }
...@@ -267,29 +321,32 @@ tapechar_release(struct inode *inode, struct file *filp) ...@@ -267,29 +321,32 @@ tapechar_release(struct inode *inode, struct file *filp)
{ {
struct tape_device *device; struct tape_device *device;
device = (struct tape_device *) filp->private_data;
DBF_EVENT(6, "TCHAR:release: %x\n", iminor(inode)); DBF_EVENT(6, "TCHAR:release: %x\n", iminor(inode));
#if 0 device = (struct tape_device *) filp->private_data;
// FIXME: this is broken. Either MTWEOF/MTWEOF/MTBSR is done
// EVERYTIME the user switches from write to something different
// or it is not done at all. The second is IMHO better because
// we should NEVER do something the user didn't request.
if (device->last_op == TO_WRI)
tapechar_terminate_write(device);
#endif
/* /*
* If this is the rewinding tape minor then rewind. * If this is the rewinding tape minor then rewind. In that case we
* write all required tapemarks. Otherwise only one to terminate the
* file.
*/ */
if ((iminor(inode) & 1) != 0) if ((iminor(inode) & 1) != 0) {
if (device->required_tapemarks)
tape_std_terminate_write(device);
tape_mtop(device, MTREW, 1); tape_mtop(device, MTREW, 1);
} else {
if (device->required_tapemarks > 1) {
if (tape_mtop(device, MTWEOF, 1) == 0)
device->required_tapemarks--;
}
}
if (device->char_data.idal_buf != NULL) { if (device->char_data.idal_buf != NULL) {
idal_buffer_free(device->char_data.idal_buf); idal_buffer_free(device->char_data.idal_buf);
device->char_data.idal_buf = NULL; device->char_data.idal_buf = NULL;
} }
device->char_data.block_size = 0;
tape_release(device); tape_release(device);
tape_unassign(device); filp->private_data = tape_put_device(device);
tape_put_device(device);
return 0; return 0;
} }
...@@ -314,7 +371,40 @@ tapechar_ioctl(struct inode *inp, struct file *filp, ...@@ -314,7 +371,40 @@ tapechar_ioctl(struct inode *inp, struct file *filp,
return -EFAULT; return -EFAULT;
if (op.mt_count < 0) if (op.mt_count < 0)
return -EINVAL; return -EINVAL;
return tape_mtop(device, op.mt_op, op.mt_count);
/*
* Operations that change tape position should write final
* tapemarks.
*/
switch (op.mt_op) {
case MTFSF:
case MTBSF:
case MTFSR:
case MTBSR:
case MTREW:
case MTOFFL:
case MTEOM:
case MTRETEN:
case MTBSFM:
case MTFSFM:
case MTSEEK:
#ifdef CONFIG_S390_TAPE_BLOCK
device->blk_data.medium_changed = 1;
#endif
if (device->required_tapemarks)
tape_std_terminate_write(device);
default:
;
}
rc = tape_mtop(device, op.mt_op, op.mt_count);
if (op.mt_op == MTWEOF && rc == 0) {
if (op.mt_count > device->required_tapemarks)
device->required_tapemarks = 0;
else
device->required_tapemarks -= op.mt_count;
}
return rc;
} }
if (no == MTIOCPOS) { if (no == MTIOCPOS) {
/* MTIOCPOS: query the tape position. */ /* MTIOCPOS: query the tape position. */
...@@ -333,19 +423,30 @@ tapechar_ioctl(struct inode *inp, struct file *filp, ...@@ -333,19 +423,30 @@ tapechar_ioctl(struct inode *inp, struct file *filp,
struct mtget get; struct mtget get;
memset(&get, 0, sizeof(get)); memset(&get, 0, sizeof(get));
rc = tape_mtop(device, MTTELL, 1);
if (rc < 0)
return rc;
get.mt_type = MT_ISUNKNOWN; get.mt_type = MT_ISUNKNOWN;
get.mt_resid = 0 /* device->devstat.rescnt */;
get.mt_dsreg = device->tape_state; get.mt_dsreg = device->tape_state;
/* FIXME: mt_gstat, mt_erreg, mt_fileno */ /* FIXME: mt_gstat, mt_erreg, mt_fileno */
get.mt_resid = 0 /* device->devstat.rescnt */;
get.mt_gstat = 0; get.mt_gstat = 0;
get.mt_erreg = 0; get.mt_erreg = 0;
get.mt_fileno = 0; get.mt_fileno = 0;
get.mt_blkno = rc; get.mt_gstat = device->tape_generic_status;
if (device->medium_state == MS_LOADED) {
rc = tape_mtop(device, MTTELL, 1);
if (rc < 0)
return rc;
if (rc == 0)
get.mt_gstat |= GMT_BOT(~0);
get.mt_blkno = rc;
}
if (copy_to_user((char *) data, &get, sizeof(get)) != 0) if (copy_to_user((char *) data, &get, sizeof(get)) != 0)
return -EFAULT; return -EFAULT;
return 0; return 0;
} }
/* Try the discipline ioctl function. */ /* Try the discipline ioctl function. */
......
This diff is collapsed.
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
#include "tape.h" #include "tape.h"
#define PRINTK_HEADER "T390:" #define PRINTK_HEADER "TAPE_PROC: "
static const char *tape_med_st_verbose[MS_SIZE] = static const char *tape_med_st_verbose[MS_SIZE] =
{ {
...@@ -42,19 +42,19 @@ static int tape_proc_show(struct seq_file *m, void *v) ...@@ -42,19 +42,19 @@ static int tape_proc_show(struct seq_file *m, void *v)
n = (unsigned long) v - 1; n = (unsigned long) v - 1;
if (!n) { if (!n) {
seq_printf(m, "TapeNo\tDevNo\tCuType\tCuModel\tDevType\t" seq_printf(m, "TapeNo\tBusID CuType/Model\t"
"DevMod\tBlkSize\tState\tOp\tMedState\n"); "DevType/Model\tBlkSize\tState\tOp\tMedState\n");
} }
device = tape_get_device(n); device = tape_get_device(n);
if (IS_ERR(device)) if (IS_ERR(device))
return 0; return 0;
spin_lock_irq(get_ccwdev_lock(device->cdev)); spin_lock_irq(get_ccwdev_lock(device->cdev));
seq_printf(m, "%d\t", (int) n); seq_printf(m, "%d\t", (int) n);
seq_printf(m, "%s\t", device->cdev->dev.bus_id); seq_printf(m, "%-10.10s ", device->cdev->dev.bus_id);
seq_printf(m, "%04X\t", device->cdev->id.cu_type); seq_printf(m, "%04X/", device->cdev->id.cu_type);
seq_printf(m, "%02X\t", device->cdev->id.cu_model); seq_printf(m, "%02X\t", device->cdev->id.cu_model);
seq_printf(m, "%04X\t", device->cdev->id.dev_type); seq_printf(m, "%04X/", device->cdev->id.dev_type);
seq_printf(m, "%02X\t", device->cdev->id.dev_model); seq_printf(m, "%02X\t\t", device->cdev->id.dev_model);
if (device->char_data.block_size == 0) if (device->char_data.block_size == 0)
seq_printf(m, "auto\t"); seq_printf(m, "auto\t");
else else
......
This diff is collapsed.
...@@ -10,9 +10,16 @@ ...@@ -10,9 +10,16 @@
*/ */
#ifndef _TAPE_STD_H #ifndef _TAPE_STD_H
#define _TAPE_STD_H #define _TAPE_STD_H
#include <asm/tape390.h>
/*
* Biggest block size to handle. Currently 64K because we only build
* channel programs without data chaining.
*/
#define MAX_BLOCKSIZE 65535
/* /*
* The CCW commands for the Tape type of command. * The CCW commands for the Tape type of command.
*/ */
...@@ -105,7 +112,8 @@ struct tape_request *tape_std_bwrite(struct request *, ...@@ -105,7 +112,8 @@ struct tape_request *tape_std_bwrite(struct request *,
int tape_std_assign(struct tape_device *); int tape_std_assign(struct tape_device *);
int tape_std_unassign(struct tape_device *); int tape_std_unassign(struct tape_device *);
int tape_std_read_block_id(struct tape_device *device, __u64 *id); int tape_std_read_block_id(struct tape_device *device, __u64 *id);
int tape_std_display(struct tape_device *, int, unsigned long); int tape_std_display(struct tape_device *, struct display_struct *disp);
int tape_std_terminate_write(struct tape_device *);
/* Standard magnetic tape commands. */ /* Standard magnetic tape commands. */
int tape_std_mtbsf(struct tape_device *, int); int tape_std_mtbsf(struct tape_device *, int);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment