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 @@
#ifndef _TAPE_H
#define _TAPE_H
#include <asm/ccwdev.h>
#include <asm/debug.h>
#include <asm/idals.h>
#include <linux/config.h>
#include <linux/blkdev.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mtio.h>
#include <linux/interrupt.h>
#include <asm/ccwdev.h>
#include <asm/debug.h>
#include <asm/idals.h>
#include <linux/workqueue.h>
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)
*/
......@@ -46,7 +60,11 @@ do { \
#define TAPEBLOCK_HSEC_S2B 2
#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 {
MS_UNKNOWN,
......@@ -58,6 +76,7 @@ enum tape_medium_state {
enum tape_state {
TS_UNUSED=0,
TS_IN_USE,
TS_BLKUSE,
TS_INIT,
TS_NOT_OPER,
TS_SIZE
......@@ -130,8 +149,6 @@ struct tape_discipline {
struct module *owner;
int (*setup_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 *);
struct tape_request *(*read_block)(struct tape_device *, size_t);
struct tape_request *(*write_block)(struct tape_device *, size_t);
......@@ -168,48 +185,60 @@ struct tape_char_data {
struct tape_blk_data
{
/* Block device request queue. */
request_queue_t *request_queue;
spinlock_t request_queue_lock;
/* Block frontend tasklet */
struct tasklet_struct tasklet;
request_queue_t * request_queue;
spinlock_t request_queue_lock;
/* 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. */
long block_position;
struct gendisk *disk;
long block_position;
int medium_changed;
struct gendisk * disk;
};
#endif
/* Tape Info */
struct tape_device {
/* entry in tape_device_list */
struct list_head node;
struct list_head node;
struct ccw_device *cdev;
struct ccw_device * cdev;
/* Device discipline information. */
struct tape_discipline *discipline;
void *discdata;
struct tape_discipline * discipline;
void * discdata;
/* Generic status flags */
long tape_generic_status;
long tape_generic_status;
/* Device state information. */
wait_queue_head_t state_change_wq;
enum tape_state tape_state;
enum tape_medium_state medium_state;
unsigned char *modeset_byte;
wait_queue_head_t state_change_wq;
enum tape_state tape_state;
enum tape_medium_state medium_state;
unsigned char * modeset_byte;
/* Reference count. */
atomic_t ref_count;
atomic_t ref_count;
/* 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 */
struct tape_char_data char_data;
struct tape_char_data char_data;
#ifdef CONFIG_S390_TAPE_BLOCK
/* Block dev frontend data */
struct tape_blk_data blk_data;
struct tape_blk_data blk_data;
#endif
};
......@@ -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_async(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
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);
extern void tape_noper_handler(int irq, int status);
extern int tape_open(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 void tape_state_set(struct tape_device *, enum tape_state);
extern int tape_enable_device(struct tape_device *, struct tape_discipline *);
extern void tape_disable_device(struct tape_device *device);
/* Externals from tape_devmap.c */
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 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 */
extern int tapechar_init(void);
......
This diff is collapsed.
This diff is collapsed.
......@@ -19,8 +19,9 @@
#include <asm/uaccess.h>
#include "tape.h"
#include "tape_std.h"
#define PRINTK_HEADER "TCHAR:"
#define PRINTK_HEADER "TAPE_CHAR: "
#define TAPECHAR_MAJOR 0 /* get dynamic major */
......@@ -52,12 +53,14 @@ static int tapechar_major = TAPECHAR_MAJOR;
int
tapechar_setup_device(struct tape_device * device)
{
tape_hotplug_event(device, tapechar_major, TAPE_HOTPLUG_CHAR_ADD);
return 0;
}
void
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)
struct idal_buffer *new;
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;
/* 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);
if (new == NULL)
return -ENOMEM;
if (device->char_data.idal_buf != NULL)
idal_buffer_free(device->char_data.idal_buf);
device->char_data.idal_buf = new;
return 0;
}
......@@ -116,6 +131,16 @@ tapechar_read (struct file *filp, char *data, size_t count, loff_t *ppos)
DBF_EVENT(6, "TCHAR:ppos wrong\n");
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 */
if (device->char_data.block_size != 0) {
if (count < device->char_data.block_size) {
......@@ -126,10 +151,17 @@ tapechar_read (struct file *filp, char *data, size_t count, loff_t *ppos)
block_size = device->char_data.block_size;
} else {
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);
/* Let the discipline build the ccw chain. */
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)
nblocks = count / block_size;
} else {
block_size = count;
rc = tapechar_check_idalbuffer(device, block_size);
if (rc)
return rc;
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:nblocks: %x\n", nblocks);
/* 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)
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;
}
......@@ -237,24 +287,28 @@ tapechar_open (struct inode *inode, struct file *filp)
struct tape_device *device;
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)
return -ENODEV;
minor = iminor(filp->f_dentry->d_inode);
device = tape_get_device(minor / TAPE_MINORS_PER_DEV);
if (IS_ERR(device)) {
DBF_EVENT(3, "TCHAR:open: tape_get_device() failed\n");
return PTR_ERR(device);
}
DBF_EVENT(6, "TCHAR:open: %x\n", iminor(inode));
rc = tape_open(device);
if (rc == 0) {
rc = tape_assign(device);
if (rc == 0) {
filp->private_data = device;
return 0;
}
tape_release(device);
filp->private_data = device;
return 0;
}
tape_put_device(device);
return rc;
}
......@@ -267,29 +321,32 @@ tapechar_release(struct inode *inode, struct file *filp)
{
struct tape_device *device;
device = (struct tape_device *) filp->private_data;
DBF_EVENT(6, "TCHAR:release: %x\n", iminor(inode));
#if 0
// 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
device = (struct tape_device *) filp->private_data;
/*
* 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);
} else {
if (device->required_tapemarks > 1) {
if (tape_mtop(device, MTWEOF, 1) == 0)
device->required_tapemarks--;
}
}
if (device->char_data.idal_buf != NULL) {
idal_buffer_free(device->char_data.idal_buf);
device->char_data.idal_buf = NULL;
}
device->char_data.block_size = 0;
tape_release(device);
tape_unassign(device);
tape_put_device(device);
filp->private_data = tape_put_device(device);
return 0;
}
......@@ -314,7 +371,40 @@ tapechar_ioctl(struct inode *inp, struct file *filp,
return -EFAULT;
if (op.mt_count < 0)
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) {
/* MTIOCPOS: query the tape position. */
......@@ -333,19 +423,30 @@ tapechar_ioctl(struct inode *inp, struct file *filp,
struct mtget get;
memset(&get, 0, sizeof(get));
rc = tape_mtop(device, MTTELL, 1);
if (rc < 0)
return rc;
get.mt_type = MT_ISUNKNOWN;
get.mt_resid = 0 /* device->devstat.rescnt */;
get.mt_dsreg = device->tape_state;
/* FIXME: mt_gstat, mt_erreg, mt_fileno */
get.mt_resid = 0 /* device->devstat.rescnt */;
get.mt_gstat = 0;
get.mt_erreg = 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)
return -EFAULT;
return 0;
}
/* Try the discipline ioctl function. */
......
This diff is collapsed.
......@@ -18,7 +18,7 @@
#include "tape.h"
#define PRINTK_HEADER "T390:"
#define PRINTK_HEADER "TAPE_PROC: "
static const char *tape_med_st_verbose[MS_SIZE] =
{
......@@ -42,19 +42,19 @@ static int tape_proc_show(struct seq_file *m, void *v)
n = (unsigned long) v - 1;
if (!n) {
seq_printf(m, "TapeNo\tDevNo\tCuType\tCuModel\tDevType\t"
"DevMod\tBlkSize\tState\tOp\tMedState\n");
seq_printf(m, "TapeNo\tBusID CuType/Model\t"
"DevType/Model\tBlkSize\tState\tOp\tMedState\n");
}
device = tape_get_device(n);
if (IS_ERR(device))
return 0;
spin_lock_irq(get_ccwdev_lock(device->cdev));
seq_printf(m, "%d\t", (int) n);
seq_printf(m, "%s\t", device->cdev->dev.bus_id);
seq_printf(m, "%04X\t", device->cdev->id.cu_type);
seq_printf(m, "%-10.10s ", device->cdev->dev.bus_id);
seq_printf(m, "%04X/", device->cdev->id.cu_type);
seq_printf(m, "%02X\t", device->cdev->id.cu_model);
seq_printf(m, "%04X\t", device->cdev->id.dev_type);
seq_printf(m, "%02X\t", device->cdev->id.dev_model);
seq_printf(m, "%04X/", device->cdev->id.dev_type);
seq_printf(m, "%02X\t\t", device->cdev->id.dev_model);
if (device->char_data.block_size == 0)
seq_printf(m, "auto\t");
else
......
This diff is collapsed.
......@@ -10,9 +10,16 @@
*/
#ifndef _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.
*/
......@@ -105,7 +112,8 @@ struct tape_request *tape_std_bwrite(struct request *,
int tape_std_assign(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_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. */
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