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);
......
......@@ -14,31 +14,60 @@
#include <linux/init.h>
#include <linux/bio.h>
#include <linux/workqueue.h>
#include <asm/tape390.h>
#include "tape.h"
#include "tape_std.h"
#define PRINTK_HEADER "T34xx:"
#define PRINTK_HEADER "TAPE_34XX: "
enum tape34xx_type {
enum tape_34xx_type {
tape_3480,
tape_3490,
};
#define TAPE34XX_FMT_3480 0
#define TAPE34XX_FMT_3480_2_XF 1
#define TAPE34XX_FMT_3480_XF 2
struct tape_34xx_block_id {
unsigned int wrap : 1;
unsigned int segment : 7;
unsigned int format : 2;
unsigned int block : 22;
};
/*
* Medium sense (asynchronous with callback) for 34xx tapes. There is no 'real'
* medium sense call. So we just do a normal sense.
* A list of block ID's is used to faster seek blocks.
*/
static void
__tape_34xx_medium_sense_callback(struct tape_request *request, void *data)
struct tape_34xx_sbid {
struct list_head list;
struct tape_34xx_block_id bid;
};
static void tape_34xx_delete_sbid_from(struct tape_device *, int);
/*
* Medium sense for 34xx tapes. There is no 'real' medium sense call.
* So we just do a normal sense.
*/
static int
tape_34xx_medium_sense(struct tape_device *device)
{
unsigned char *sense;
struct tape_device *device;
struct tape_request *request;
unsigned char *sense;
int rc;
request->callback = NULL;
request = tape_alloc_request(1, 32);
if (IS_ERR(request)) {
DBF_EXCEPTION(6, "MSEN fail\n");
return PTR_ERR(request);
}
if(request->rc == 0 && (device = request->device) != NULL) {
request->op = TO_MSEN;
tape_ccw_end(request->cpaddr, SENSE, 32, request->cpdata);
rc = tape_do_io_interruptible(device, request);
if (request->rc == 0) {
sense = request->cpdata;
/*
......@@ -47,36 +76,20 @@ __tape_34xx_medium_sense_callback(struct tape_request *request, void *data)
* only slightly inaccurate to say there is no tape loaded if
* the drive isn't online...
*/
if(sense[0] & SENSE_INTERVENTION_REQUIRED)
if (sense[0] & SENSE_INTERVENTION_REQUIRED)
tape_med_state_set(device, MS_UNLOADED);
else
tape_med_state_set(device, MS_LOADED);
if(sense[1] & SENSE_WRITE_PROTECT)
if (sense[1] & SENSE_WRITE_PROTECT)
device->tape_generic_status |= GMT_WR_PROT(~0);
else
device->tape_generic_status &= ~GMT_WR_PROT(~0);
} else {
DBF_EVENT(4, "tape_34xx: medium sense failed with rc=%d\n",
request->rc);
}
tape_free_request(request);
}
static int
tape_34xx_medium_sense(struct tape_device *device)
{
struct tape_request *request;
int rc;
request = tape_alloc_request(1, 32);
if(IS_ERR(request)) {
DBF_EXCEPTION(6, "MSEN fail\n");
return PTR_ERR(request);
}
request->op = TO_MSEN;
tape_ccw_end(request->cpaddr, SENSE, 32, request->cpdata);
request->callback = __tape_34xx_medium_sense_callback;
rc = tape_do_io_async(device, request);
return rc;
}
......@@ -104,7 +117,7 @@ tape_34xx_work_handler(void *data)
DBF_EVENT(3, "T34XX: internal error: unknown work\n");
}
tape_put_device(p->device);
p->device = tape_put_device(p->device);
kfree(p);
}
......@@ -117,14 +130,13 @@ tape_34xx_schedule_work(struct tape_device *device, enum tape_op op)
struct work_struct work;
} *p;
if((p = kmalloc(sizeof(*p), GFP_ATOMIC)) == NULL)
if ((p = kmalloc(sizeof(*p), GFP_ATOMIC)) == NULL)
return -ENOMEM;
memset(p, 0, sizeof(*p));
INIT_WORK(&p->work, tape_34xx_work_handler, p);
atomic_inc(&device->ref_count);
p->device = device;
p->device = tape_get_device_reference(device);
p->op = op;
schedule_work(&p->work);
......@@ -134,37 +146,46 @@ tape_34xx_schedule_work(struct tape_device *device, enum tape_op op)
/*
* Done Handler is called when dev stat = DEVICE-END (successful operation)
*/
static int
tape_34xx_done(struct tape_device *device, struct tape_request *request)
static inline int
tape_34xx_done(struct tape_request *request)
{
DBF_EVENT(6, "%s done\n", tape_op_verbose[request->op]);
switch (request->op) {
case TO_DSE:
case TO_RUN:
case TO_WRI:
case TO_WTM:
case TO_ASSIGN:
case TO_UNASSIGN:
tape_34xx_delete_sbid_from(request->device, 0);
break;
default:
;
}
return TAPE_IO_SUCCESS;
}
static inline int
tape_34xx_erp_failed(struct tape_device *device,
struct tape_request *request, int rc)
tape_34xx_erp_failed(struct tape_request *request, int rc)
{
DBF_EVENT(3, "Error recovery failed for %s\n",
tape_op_verbose[request->op]);
DBF_EVENT(3, "Error recovery failed for %s (RC=%d)\n",
tape_op_verbose[request->op], rc);
return rc;
}
static inline int
tape_34xx_erp_succeeded(struct tape_device *device,
struct tape_request *request)
tape_34xx_erp_succeeded(struct tape_request *request)
{
DBF_EVENT(3, "Error Recovery successful for %s\n",
tape_op_verbose[request->op]);
return tape_34xx_done(device, request);
return tape_34xx_done(request);
}
static inline int
tape_34xx_erp_retry(struct tape_device *device, struct tape_request *request)
tape_34xx_erp_retry(struct tape_request *request)
{
DBF_EVENT(3, "xerp retr %s\n",
tape_op_verbose[request->op]);
DBF_EVENT(3, "xerp retr %s\n", tape_op_verbose[request->op]);
return TAPE_IO_RETRY;
}
......@@ -178,6 +199,7 @@ tape_34xx_unsolicited_irq(struct tape_device *device, struct irb *irb)
if (irb->scsw.dstat == 0x85 /* READY */) {
/* A medium was inserted in the drive. */
DBF_EVENT(6, "xuud med\n");
tape_34xx_delete_sbid_from(device, 0);
tape_34xx_schedule_work(device, TO_MSEN);
} else {
DBF_EVENT(3, "unsol.irq! dev end: %s\n",
......@@ -203,7 +225,7 @@ tape_34xx_erp_read_opposite(struct tape_device *device,
* and try again.
*/
tape_std_read_backward(device, request);
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
}
if (request->op != TO_RBA)
PRINT_ERR("read_opposite called with state:%s\n",
......@@ -212,7 +234,7 @@ tape_34xx_erp_read_opposite(struct tape_device *device,
* We tried to read forward and backward, but hat no
* success -> failed.
*/
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
}
static int
......@@ -228,7 +250,7 @@ tape_34xx_erp_bug(struct tape_device *device, struct tape_request *request,
tape_op_verbose[request->op]);
tape_dump_sense(device, request, irb);
}
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
}
/*
......@@ -243,7 +265,7 @@ tape_34xx_erp_overrun(struct tape_device *device, struct tape_request *request,
PRINT_WARN ("Data overrun error between control-unit "
"and drive. Use a faster channel connection, "
"if possible! \n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
}
return tape_34xx_erp_bug(device, request, irb, -1);
}
......@@ -260,7 +282,7 @@ tape_34xx_erp_sequence(struct tape_device *device,
* cu detected incorrect block-id sequence on tape.
*/
PRINT_WARN("Illegal block-id sequence found!\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
}
/*
* Record sequence error bit is set, but erpa does not
......@@ -281,6 +303,9 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
int inhibit_cu_recovery;
__u8* sense;
inhibit_cu_recovery = (*device->modeset_byte & 0x80) ? 1 : 0;
sense = irb->ecw;
#ifdef CONFIG_S390_TAPE_BLOCK
if (request->op == TO_BLOCK) {
/*
......@@ -289,51 +314,95 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
*/
device->blk_data.block_position = -1;
if (request->retries-- <= 0)
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
else
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
}
#endif
inhibit_cu_recovery = (*device->modeset_byte & 0x80) ? 1 : 0;
sense = irb->ecw;
if (sense[0] & SENSE_COMMAND_REJECT) {
if ((sense[1] & SENSE_WRITE_PROTECT) &&
(request->op == TO_DSE ||
request->op == TO_WRI ||
request->op == TO_WTM))
if (
sense[0] & SENSE_COMMAND_REJECT &&
sense[1] & SENSE_WRITE_PROTECT
) {
if (
request->op == TO_DSE ||
request->op == TO_WRI ||
request->op == TO_WTM
) {
/* medium is write protected */
return tape_34xx_erp_failed(device, request, -EACCES);
else
return tape_34xx_erp_failed(request, -EACCES);
} else {
return tape_34xx_erp_bug(device, request, irb, -3);
}
}
/*
* special cases for various tape-states when reaching
* Special cases for various tape-states when reaching
* end of recorded area
*
* FIXME: Maybe a special case of the special case:
* sense[0] == SENSE_EQUIPMENT_CHECK &&
* sense[1] == SENSE_DRIVE_ONLINE &&
* sense[3] == 0x47 (Volume Fenced)
*
* This was caused by continued FSF or FSR after an
* 'End Of Data'.
*/
if ((sense[0] == 0x08 || sense[0] == 0x10 || sense[0] == 0x12) &&
(sense[1] == 0x40 || sense[1] == 0x0c))
if ((
sense[0] == SENSE_DATA_CHECK ||
sense[0] == SENSE_EQUIPMENT_CHECK ||
sense[0] == SENSE_EQUIPMENT_CHECK + SENSE_DEFERRED_UNIT_CHECK
) && (
sense[1] == SENSE_DRIVE_ONLINE ||
sense[1] == SENSE_BEGINNING_OF_TAPE + SENSE_WRITE_MODE
)) {
switch (request->op) {
/*
* sense[0] == SENSE_DATA_CHECK &&
* sense[1] == SENSE_DRIVE_ONLINE
* sense[3] == 0x36 (End Of Data)
*
* Further seeks might return a 'Volume Fenced'.
*/
case TO_FSF:
case TO_FSB:
/* Trying to seek beyond end of recorded area */
return tape_34xx_erp_failed(device, request, -ENOSPC);
return tape_34xx_erp_failed(request, -ENOSPC);
case TO_BSB:
return tape_34xx_erp_retry(request);
/*
* sense[0] == SENSE_DATA_CHECK &&
* sense[1] == SENSE_DRIVE_ONLINE &&
* sense[3] == 0x36 (End Of Data)
*/
case TO_LBL:
/* Block could not be located. */
return tape_34xx_erp_failed(device, request, -EIO);
tape_34xx_delete_sbid_from(device, 0);
return tape_34xx_erp_failed(request, -EIO);
case TO_RFO:
/* Read beyond end of recorded area -> 0 bytes read */
return tape_34xx_erp_failed(device, request, 0);
return tape_34xx_erp_failed(request, 0);
/*
* sense[0] == SENSE_EQUIPMENT_CHECK &&
* sense[1] == SENSE_DRIVE_ONLINE &&
* sense[3] == 0x38 (Physical End Of Volume)
*/
case TO_WRI:
/* Writing at physical end of volume */
return tape_34xx_erp_failed(request, -ENOSPC);
default:
PRINT_ERR("Invalid op in %s:%i\n",
__FUNCTION__, __LINE__);
return tape_34xx_erp_failed(device, request, 0);
return tape_34xx_erp_failed(request, 0);
}
}
/* Sensing special bits */
if (sense[0] & SENSE_BUS_OUT_CHECK)
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
if (sense[0] & SENSE_DATA_CHECK) {
/*
......@@ -353,7 +422,7 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
/* data check is permanent, CU recovery has failed */
PRINT_WARN("Permanent read error\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x25:
// a write data check occurred
if ((sense[2] & SENSE_TAPE_SYNC_MODE) ||
......@@ -366,22 +435,22 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
// data check is permanent, cu-recovery has failed
PRINT_WARN("Permanent write error\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x26:
/* Data Check (read opposite) occurred. */
return tape_34xx_erp_read_opposite(device, request);
case 0x28:
/* ID-Mark at tape start couldn't be written */
PRINT_WARN("ID-Mark could not be written.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x31:
/* Tape void. Tried to read beyond end of device. */
PRINT_WARN("Read beyond end of recorded area.\n");
return tape_34xx_erp_failed(device, request, -ENOSPC);
return tape_34xx_erp_failed(request, -ENOSPC);
case 0x41:
/* Record sequence error. */
PRINT_WARN("Invalid block-id sequence found.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
default:
/* all data checks for 3480 should result in one of
* the above erpa-codes. For 3490, other data-check
......@@ -412,7 +481,7 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
*/
PRINT_WARN("Data streaming not operational. "
"Switching to interlock-mode.\n");
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
case 0x22:
/*
* Path equipment check. Might be drive adapter error, buffer
......@@ -424,14 +493,14 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
PRINT_WARN("drive adapter error, buffer error on the lower "
"interface, internal path not usable, error "
"during cartridge load.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x24:
/*
* Load display check. Load display was command was issued,
* but the drive is displaying a drive check message. Can
* be threated as "device end".
*/
return tape_34xx_erp_succeeded(device, request);
return tape_34xx_erp_succeeded(request);
case 0x27:
/*
* Command reject. May indicate illegal channel program or
......@@ -439,7 +508,7 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
* issued by this driver and ought be correct, we assume a
* over/underrun situation and retry the channel program.
*/
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
case 0x29:
/*
* Function incompatible. Either the tape is idrc compressed
......@@ -447,13 +516,13 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
* subsystem func is issued and the CU is not on-line.
*/
PRINT_WARN ("Function incompatible. Try to switch off idrc\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x2a:
/*
* Unsolicited environmental data. An internal counter
* overflows, we can ignore this and reissue the cmd.
*/
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
case 0x2b:
/*
* Environmental data present. Indicates either unload
......@@ -462,7 +531,7 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
if (request->op == TO_RUN) {
/* Rewind unload completed ok. */
tape_med_state_set(device, MS_UNLOADED);
return tape_34xx_erp_succeeded(device, request);
return tape_34xx_erp_succeeded(request);
}
/* tape_34xx doesn't use read buffered log commands. */
return tape_34xx_erp_bug(device, request, irb, sense[3]);
......@@ -471,11 +540,11 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
* Permanent equipment check. CU has tried recovery, but
* did not succeed.
*/
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x2d:
/* Data security erase failure. */
if (request->op == TO_DSE)
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
/* Data security erase failure, but no such command issued. */
return tape_34xx_erp_bug(device, request, irb, sense[3]);
case 0x2e:
......@@ -484,16 +553,16 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
* reading the format id mark or that that format specified
* is not supported by the drive.
*/
PRINT_WARN("Drive not capable processing the tape format!");
return tape_34xx_erp_failed(device, request, -EMEDIUMTYPE);
PRINT_WARN("Drive not capable processing the tape format!\n");
return tape_34xx_erp_failed(request, -EMEDIUMTYPE);
case 0x30:
/* The medium is write protected. */
PRINT_WARN("Medium is write protected!\n");
return tape_34xx_erp_failed(device, request, -EACCES);
return tape_34xx_erp_failed(request, -EACCES);
case 0x32:
// Tension loss. We cannot recover this, it's an I/O error.
PRINT_WARN("The drive lost tape tension.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x33:
/*
* Load Failure. The cartridge was not inserted correctly or
......@@ -501,7 +570,8 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
*/
PRINT_WARN("Cartridge load failure. Reload the cartridge "
"and try again.\n");
return tape_34xx_erp_failed(device, request, -EIO);
tape_34xx_delete_sbid_from(device, 0);
return tape_34xx_erp_failed(request, -EIO);
case 0x34:
/*
* Unload failure. The drive cannot maintain tape tension
......@@ -510,7 +580,7 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
PRINT_WARN("Failure during cartridge unload. "
"Please try manually.\n");
if (request->op == TO_RUN)
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
return tape_34xx_erp_bug(device, request, irb, sense[3]);
case 0x35:
/*
......@@ -522,11 +592,11 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
*/
PRINT_WARN("Equipment check! Please check the drive and "
"the cartridge loader.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x36:
if (device->cdev->id.driver_info == tape_3490)
/* End of data. */
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
/* This erpa is reserved for 3480 */
return tape_34xx_erp_bug(device, request, irb, sense[3]);
case 0x37:
......@@ -535,7 +605,7 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
* the beginning-of-tape data.
*/
PRINT_WARN("Tape length error.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x38:
/*
* Physical end of tape. A read/write operation reached
......@@ -544,55 +614,58 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
if (request->op==TO_WRI ||
request->op==TO_DSE ||
request->op==TO_WTM)
return tape_34xx_erp_failed(device, request, -ENOSPC);
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -ENOSPC);
return tape_34xx_erp_failed(request, -EIO);
case 0x39:
/* Backward at Beginning of tape. */
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x3a:
/* Drive switched to not ready. */
PRINT_WARN("Drive not ready. Turn the ready/not ready switch "
"to ready position and try again.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x3b:
/* Manual rewind or unload. This causes an I/O error. */
PRINT_WARN("Medium was rewound or unloaded manually.\n");
return tape_34xx_erp_failed(device, request, -EIO);
tape_34xx_delete_sbid_from(device, 0);
return tape_34xx_erp_failed(request, -EIO);
case 0x42:
/*
* Degraded mode. A condition that can cause degraded
* performance is detected.
*/
PRINT_WARN("Subsystem is running in degraded mode.\n");
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
case 0x43:
/* Drive not ready. */
tape_34xx_delete_sbid_from(device, 0);
tape_med_state_set(device, MS_UNLOADED);
/* Some commands commands are successful even in this case */
if(sense[1] & SENSE_DRIVE_ONLINE) {
if (sense[1] & SENSE_DRIVE_ONLINE) {
switch(request->op) {
case TO_ASSIGN:
case TO_UNASSIGN:
case TO_DIS:
return tape_34xx_done(device, request);
case TO_NOP:
return tape_34xx_done(request);
break;
default:
break;
}
}
PRINT_WARN("The drive is not ready.\n");
return tape_34xx_erp_failed(device, request, -ENOMEDIUM);
return tape_34xx_erp_failed(request, -ENOMEDIUM);
case 0x44:
/* Locate Block unsuccessful. */
if (request->op != TO_BLOCK && request->op != TO_LBL)
/* No locate block was issued. */
return tape_34xx_erp_bug(device, request,
irb, sense[3]);
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x45:
/* The drive is assigned to a different channel path. */
PRINT_WARN("The drive is assigned elsewhere.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x46:
/*
* Drive not on-line. Drive may be switched offline,
......@@ -600,23 +673,24 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
* the drive address may not be set correctly.
*/
PRINT_WARN("The drive is not on-line.");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x47:
/* Volume fenced. CU reports volume integrity is lost. */
PRINT_WARN("Volume fenced. The volume integrity is lost.\n");
return tape_34xx_erp_failed(device, request, -EIO);
tape_34xx_delete_sbid_from(device, 0);
return tape_34xx_erp_failed(request, -EIO);
case 0x48:
/* Log sense data and retry request. */
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
case 0x49:
/* Bus out check. A parity check error on the bus was found. */
PRINT_WARN("Bus out check. A data transfer over the bus "
"has been corrupted.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x4a:
/* Control unit erp failed. */
PRINT_WARN("The control unit I/O error recovery failed.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x4b:
/*
* CU and drive incompatible. The drive requests micro-program
......@@ -624,13 +698,13 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
*/
PRINT_WARN("The drive needs microprogram patches from the "
"control unit, which are not available.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x4c:
/*
* Recovered Check-One failure. Cu develops a hardware error,
* but is able to recover.
*/
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
case 0x4d:
if (device->cdev->id.driver_info == tape_3490)
/*
......@@ -638,7 +712,7 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
* not support resetting event recovery (which has to
* be handled by the I/O Layer), retry our command.
*/
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
/* This erpa is reserved for 3480. */
return tape_34xx_erp_bug(device, request, irb, sense[3]);
case 0x4e:
......@@ -650,7 +724,7 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
*/
PRINT_WARN("Maximum block size for buffered "
"mode exceeded.\n");
return tape_34xx_erp_failed(device, request, -ENOBUFS);
return tape_34xx_erp_failed(request, -ENOBUFS);
}
/* This erpa is reserved for 3480. */
return tape_34xx_erp_bug(device, request, irb, sense[3]);
......@@ -661,7 +735,7 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
* never happen, since we're never running in extended
* buffered log mode.
*/
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
case 0x51:
/*
* Read buffered log (EOV). EOF processing occurs while the
......@@ -669,73 +743,83 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
* happen, since we're never running in extended buffered
* log mode.
*/
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
case 0x52:
/* End of Volume complete. Rewind unload completed ok. */
if (request->op == TO_RUN) {
tape_med_state_set(device, MS_UNLOADED);
return tape_34xx_erp_succeeded(device, request);
tape_34xx_delete_sbid_from(device, 0);
return tape_34xx_erp_succeeded(request);
}
return tape_34xx_erp_bug(device, request, irb, sense[3]);
case 0x53:
/* Global command intercept. */
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
case 0x54:
/* Channel interface recovery (temporary). */
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
case 0x55:
/* Channel interface recovery (permanent). */
PRINT_WARN("A permanent channel interface error occurred.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x56:
/* Channel protocol error. */
PRINT_WARN("A channel protocol error occurred.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_failed(request, -EIO);
case 0x57:
if (device->cdev->id.driver_info == tape_3480) {
/* Attention intercept. */
PRINT_WARN("An attention intercept occurred, "
"which will be recovered.\n");
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
} else {
/* Global status intercept. */
PRINT_WARN("An global status intercept was received, "
"which will be recovered.\n");
return tape_34xx_erp_retry(device, request);
return tape_34xx_erp_retry(request);
}
case 0x5a:
/*
* Tape length incompatible. The tape inserted is too long,
* which could cause damage to the tape or the drive.
*/
PRINT_WARN("Tape length incompatible [should be IBM Cartridge "
"System Tape]. May cause damage to drive or tape.\n");
return tape_34xx_erp_failed(device, request, -EIO);
PRINT_WARN("Tape Length Incompatible\n");
PRINT_WARN("Tape length exceeds IBM enhanced capacity "
"cartdridge length or a medium\n");
PRINT_WARN("with EC-CST identification mark has been mounted "
"in a device that writes\n");
PRINT_WARN("3480 or 3480 XF format.\n");
return tape_34xx_erp_failed(request, -EIO);
case 0x5b:
/* Format 3480 XF incompatible */
if (sense[1] & SENSE_BEGINNING_OF_TAPE)
/* The tape will get overwritten. */
return tape_34xx_erp_retry(device, request);
PRINT_WARN("Tape format is incompatible to the drive, "
"which writes 3480-2 XF.\n");
return tape_34xx_erp_failed(device, request, -EIO);
return tape_34xx_erp_retry(request);
PRINT_WARN("Format 3480 XF Incompatible\n");
PRINT_WARN("Medium has been created in 3480 format. "
"To change the format writes\n");
PRINT_WARN("must be issued at BOT.\n");
return tape_34xx_erp_failed(request, -EIO);
case 0x5c:
/* Format 3480-2 XF incompatible */
PRINT_WARN("Tape format is incompatible to the drive. "
"The drive cannot access 3480-2 XF volumes.\n");
return tape_34xx_erp_failed(device, request, -EIO);
PRINT_WARN("Format 3480-2 XF Incompatible\n");
PRINT_WARN("Device can only read 3480 or 3480 XF format.\n");
return tape_34xx_erp_failed(request, -EIO);
case 0x5d:
/* Tape length violation. */
PRINT_WARN("Tape length violation [should be IBM Enhanced "
"Capacity Cartridge System Tape]. May cause "
"damage to drive or tape.\n");
return tape_34xx_erp_failed(device, request, -EMEDIUMTYPE);
PRINT_WARN("Tape Length Violation\n");
PRINT_WARN("The mounted tape exceeds IBM Enhanced Capacity "
"Cartdridge System Tape length.\n");
PRINT_WARN("This may cause damage to the drive or tape when "
"processing to the EOV\n");
return tape_34xx_erp_failed(request, -EMEDIUMTYPE);
case 0x5e:
/* Compaction algorithm incompatible. */
PRINT_WARN("Compaction Algorithm Incompatible\n");
PRINT_WARN("The volume is recorded using an incompatible "
"compaction algorithm, which is not supported by "
"the control unit.\n");
return tape_34xx_erp_failed(device, request, -EMEDIUMTYPE);
"compaction algorithm,\n");
PRINT_WARN("which is not supported by the device.\n");
return tape_34xx_erp_failed(request, -EMEDIUMTYPE);
/* The following erpas should have been covered earlier. */
case 0x23: /* Read data check. */
......@@ -766,14 +850,24 @@ tape_34xx_irq(struct tape_device *device, struct tape_request *request,
(request->op == TO_WRI)) {
/* Write at end of volume */
PRINT_INFO("End of volume\n"); /* XXX */
return tape_34xx_erp_failed(device, request, -ENOSPC);
return tape_34xx_erp_failed(request, -ENOSPC);
}
if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK)
return tape_34xx_unit_check(device, request, irb);
if (irb->scsw.dstat & DEV_STAT_DEV_END)
return tape_34xx_done(device, request);
if (irb->scsw.dstat & DEV_STAT_DEV_END) {
/*
* A unit exception occurs on skipping over a tapemark block.
*/
if (irb->scsw.dstat & DEV_STAT_UNIT_EXCEP) {
if (request->op == TO_BSB || request->op == TO_FSB)
request->rescnt++;
else
DBF_EVENT(5, "Unit Exception!\n");
}
return tape_34xx_done(request);
}
DBF_EVENT(6, "xunknownirq\n");
PRINT_ERR("Unexpected interrupt.\n");
......@@ -786,45 +880,217 @@ tape_34xx_irq(struct tape_device *device, struct tape_request *request,
* ioctl_overload
*/
static int
tape_34xx_ioctl(struct tape_device *device,
unsigned int cmd, unsigned long arg)
tape_34xx_ioctl(struct tape_device *device, unsigned int cmd, unsigned long arg)
{
if (cmd == TAPE390_DISPLAY)
return tape_std_display(device, cmd, arg);
else
if (cmd == TAPE390_DISPLAY) {
struct display_struct disp;
if (copy_from_user(&disp, (char *) arg, sizeof(disp)) != 0)
return -EFAULT;
return tape_std_display(device, &disp);
} else
return -EINVAL;
}
static inline void
tape_34xx_append_new_sbid(struct tape_34xx_block_id bid, struct list_head *l)
{
struct tape_34xx_sbid * new_sbid;
new_sbid = kmalloc(sizeof(*new_sbid), GFP_ATOMIC);
if (!new_sbid)
return;
new_sbid->bid = bid;
list_add(&new_sbid->list, l);
}
/*
* Build up the search block ID list. The block ID consists of a logical
* block number and a hardware specific part. The hardware specific part
* helps the tape drive to speed up searching for a specific block.
*/
static void
tape_34xx_add_sbid(struct tape_device *device, struct tape_34xx_block_id bid)
{
struct list_head * sbid_list;
struct tape_34xx_sbid * sbid;
struct list_head * l;
/*
* immediately return if there is no list at all or the block to add
* is located in segment 1 of wrap 0 because this position is used
* if no hardware position data is supplied.
*/
sbid_list = (struct list_head *) device->discdata;
if (!sbid_list || (bid.segment < 2 && bid.wrap == 0))
return;
/*
* Search the position where to insert the new entry. Hardware
* acceleration uses only the segment and wrap number. So we
* need only one entry for a specific wrap/segment combination.
* If there is a block with a lower number but the same hard-
* ware position data we just update the block number in the
* existing entry.
*/
list_for_each(l, sbid_list) {
sbid = list_entry(l, struct tape_34xx_sbid, list);
if (
(sbid->bid.segment == bid.segment) &&
(sbid->bid.wrap == bid.wrap)
) {
if (bid.block < sbid->bid.block)
sbid->bid = bid;
else return;
break;
}
/* Sort in according to logical block number. */
if (bid.block < sbid->bid.block) {
tape_34xx_append_new_sbid(bid, l->prev);
break;
}
}
/* List empty or new block bigger than last entry. */
if (l == sbid_list)
tape_34xx_append_new_sbid(bid, l->prev);
DBF_LH(4, "Current list is:\n");
list_for_each(l, sbid_list) {
sbid = list_entry(l, struct tape_34xx_sbid, list);
DBF_LH(4, "%d:%03d@%05d\n",
sbid->bid.wrap,
sbid->bid.segment,
sbid->bid.block
);
}
}
/*
* Delete all entries from the search block ID list that belong to tape blocks
* equal or higher than the given number.
*/
static void
tape_34xx_delete_sbid_from(struct tape_device *device, int from)
{
struct list_head * sbid_list;
struct tape_34xx_sbid * sbid;
struct list_head * l;
struct list_head * n;
sbid_list = (struct list_head *) device->discdata;
if (!sbid_list)
return;
list_for_each_safe(l, n, sbid_list) {
sbid = list_entry(l, struct tape_34xx_sbid, list);
if (sbid->bid.block >= from) {
DBF_LH(4, "Delete sbid %d:%03d@%05d\n",
sbid->bid.wrap,
sbid->bid.segment,
sbid->bid.block
);
list_del(l);
kfree(sbid);
}
}
}
/*
* Merge hardware position data into a block id.
*/
static void
tape_34xx_merge_sbid(
struct tape_device * device,
struct tape_34xx_block_id * bid
) {
struct tape_34xx_sbid * sbid;
struct tape_34xx_sbid * sbid_to_use;
struct list_head * sbid_list;
struct list_head * l;
sbid_list = (struct list_head *) device->discdata;
bid->wrap = 0;
bid->segment = 1;
if (!sbid_list || list_empty(sbid_list))
return;
sbid_to_use = NULL;
list_for_each(l, sbid_list) {
sbid = list_entry(l, struct tape_34xx_sbid, list);
if (sbid->bid.block >= bid->block)
break;
sbid_to_use = sbid;
}
if (sbid_to_use) {
bid->wrap = sbid_to_use->bid.wrap;
bid->segment = sbid_to_use->bid.segment;
DBF_LH(4, "Use %d:%03d@%05d for %05d\n",
sbid_to_use->bid.wrap,
sbid_to_use->bid.segment,
sbid_to_use->bid.block,
bid->block
);
}
}
static int
tape_34xx_setup_device(struct tape_device * device)
{
DBF_EVENT(6, "34xx minor1: %x\n", device->first_minor);
tape_34xx_medium_sense(device);
return 0;
int rc;
struct list_head * discdata;
DBF_EVENT(6, "34xx device setup\n");
if ((rc = tape_std_assign(device)) == 0) {
if ((rc = tape_34xx_medium_sense(device)) != 0) {
DBF_LH(3, "34xx medium sense returned %d\n", rc);
}
}
discdata = kmalloc(sizeof(struct list_head), GFP_KERNEL);
if (discdata) {
INIT_LIST_HEAD(discdata);
device->discdata = discdata;
}
return rc;
}
static void
tape_34xx_cleanup_device(struct tape_device * device)
tape_34xx_cleanup_device(struct tape_device *device)
{
tape_std_unassign(device);
if (device->discdata) {
tape_34xx_delete_sbid_from(device, 0);
kfree(device->discdata);
device->discdata = NULL;
}
}
/*
* MTTELL: Tell block. Return the number of block relative to current file.
*/
static int
tape_34xx_mttell(struct tape_device *device, int mt_count)
{
__u64 block_id;
struct {
struct tape_34xx_block_id cbid;
struct tape_34xx_block_id dbid;
} __attribute__ ((packed)) block_id;
int rc;
rc = tape_std_read_block_id(device, &block_id);
rc = tape_std_read_block_id(device, (__u64 *) &block_id);
if (rc)
return rc;
return (block_id >> 32) & 0x3fffff;
tape_34xx_add_sbid(device, block_id.cbid);
return block_id.cbid.block;
}
/*
......@@ -834,21 +1100,28 @@ static int
tape_34xx_mtseek(struct tape_device *device, int mt_count)
{
struct tape_request *request;
struct tape_34xx_block_id * bid;
if (mt_count > 0x400000) {
if (mt_count > 0x3fffff) {
DBF_EXCEPTION(6, "xsee parm\n");
return -EINVAL;
}
request = tape_alloc_request(3, 4);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_LBL;
/* setup ccws */
request->op = TO_LBL;
bid = (struct tape_34xx_block_id *) request->cpdata;
bid->format = (*device->modeset_byte & 0x08) ?
TAPE34XX_FMT_3480_XF : TAPE34XX_FMT_3480;
bid->block = mt_count;
tape_34xx_merge_sbid(device, bid);
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
*(__u32 *) request->cpdata = mt_count |
((*device->modeset_byte & 0x08) ? 0x01800000 : 0x01000000);
tape_ccw_cc(request->cpaddr + 1, LOCATE, 4, request->cpdata);
tape_ccw_end(request->cpaddr + 2, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
......@@ -862,15 +1135,14 @@ tape_34xx_bread(struct tape_device *device, struct request *req)
{
struct tape_request *request;
struct ccw1 *ccw;
int count = 0,start_block,i;
int count = 0, i;
unsigned off;
char *dst;
struct bio_vec *bv;
struct bio *bio;
struct tape_34xx_block_id * start_block;
DBF_EVENT(6, "xBREDid:");
start_block = req->sector >> TAPEBLOCK_HSEC_S2B;
DBF_EVENT(6, "start_block = %i\n", start_block);
/* Count the number of blocks for the request. */
rq_for_each_bio(bio, req) {
......@@ -878,14 +1150,18 @@ tape_34xx_bread(struct tape_device *device, struct request *req)
count += bv->bv_len >> (TAPEBLOCK_HSEC_S2B + 9);
}
}
/* Allocate the ccw request. */
request = tape_alloc_request(2+count+1, 4);
request = tape_alloc_request(3+count+1, 8);
if (IS_ERR(request))
return request;
/* Setup ccws. */
request->op = TO_BLOCK;
*(__u32 *) request->cpdata = (start_block & 0x3fffff) |
((*device->modeset_byte & 0x08) ? 0x81000000 : 0x01000000);
start_block = (struct tape_34xx_block_id *) request->cpdata;
start_block->block = req->sector >> TAPEBLOCK_HSEC_S2B;
DBF_EVENT(6, "start_block = %i\n", start_block->block);
ccw = request->cpaddr;
ccw = tape_ccw_cc(ccw, MODE_SET_DB, 1, device->modeset_byte);
......@@ -893,8 +1169,12 @@ tape_34xx_bread(struct tape_device *device, struct request *req)
* We always setup a nop after the mode set ccw. This slot is
* used in tape_std_check_locate to insert a locate ccw if the
* current tape position doesn't match the start block to be read.
* The second nop will be filled with a read block id which is in
* turn used by tape_34xx_free_bread to populate the segment bid
* table.
*/
ccw = tape_ccw_cc(ccw, NOP, 0, NULL);
ccw = tape_ccw_cc(ccw, NOP, 0, NULL);
rq_for_each_bio(bio, req) {
bio_for_each_segment(bv, bio, i) {
......@@ -921,8 +1201,21 @@ tape_34xx_free_bread (struct tape_request *request)
{
struct ccw1* ccw;
ccw = request->cpaddr;
if ((ccw + 2)->cmd_code == READ_BLOCK_ID) {
struct {
struct tape_34xx_block_id cbid;
struct tape_34xx_block_id dbid;
} __attribute__ ((packed)) *rbi_data;
rbi_data = request->cpdata;
if (request->device)
tape_34xx_add_sbid(request->device, rbi_data->cbid);
}
/* Last ccw is a nop and doesn't need clear_normalized_cda */
for (ccw = request->cpaddr; ccw->flags & CCW_FLAG_CC; ccw++)
for (; ccw->flags & CCW_FLAG_CC; ccw++)
if (ccw->cmd_code == READ_FORWARD)
clear_normalized_cda(ccw);
tape_free_request(request);
......@@ -935,14 +1228,24 @@ tape_34xx_free_bread (struct tape_request *request)
* start block for the request.
*/
static void
tape_34xx_check_locate (struct tape_device *device,
struct tape_request *request)
tape_34xx_check_locate(struct tape_device *device, struct tape_request *request)
{
int start_block;
start_block = *(__u32 *) request->cpdata & 0x3fffff;
if (start_block != device->blk_data.block_position)
tape_ccw_cc(request->cpaddr + 1, LOCATE, 4, request->cpdata);
struct tape_34xx_block_id * start_block;
start_block = (struct tape_34xx_block_id *) request->cpdata;
if (start_block->block == device->blk_data.block_position)
return;
DBF_LH(4, "Block seek(%06d+%06d)\n", start_block->block, device->bof);
start_block->wrap = 0;
start_block->segment = 1;
start_block->format = (*device->modeset_byte & 0x08) ?
TAPE34XX_FMT_3480_XF :
TAPE34XX_FMT_3480;
start_block->block = start_block->block + device->bof;
tape_34xx_merge_sbid(device, start_block);
tape_ccw_cc(request->cpaddr + 1, LOCATE, 4, request->cpdata);
tape_ccw_cc(request->cpaddr + 2, READ_BLOCK_ID, 8, request->cpdata);
}
#endif
......@@ -995,8 +1298,6 @@ static struct tape_discipline tape_discipline_34xx = {
.irq = tape_34xx_irq,
.read_block = tape_std_read_block,
.write_block = tape_std_write_block,
.assign = tape_std_assign,
.unassign = tape_std_unassign,
#ifdef CONFIG_S390_TAPE_BLOCK
.bread = tape_34xx_bread,
.free_bread = tape_34xx_free_bread,
......@@ -1041,7 +1342,7 @@ tape_34xx_init (void)
{
int rc;
DBF_EVENT(3, "34xx init: $Revision: 1.8 $\n");
DBF_EVENT(3, "34xx init: $Revision: 1.18 $\n");
/* Register driver for 3480/3490 tapes. */
rc = ccw_driver_register(&tape_34xx_driver);
if (rc)
......@@ -1060,7 +1361,7 @@ tape_34xx_exit(void)
MODULE_DEVICE_TABLE(ccw, tape_34xx_ids);
MODULE_AUTHOR("(C) 2001-2002 IBM Deutschland Entwicklung GmbH");
MODULE_DESCRIPTION("Linux on zSeries channel attached 3480 tape "
"device driver ($Revision: 1.8 $)");
"device driver ($Revision: 1.18 $)");
MODULE_LICENSE("GPL");
module_init(tape_34xx_init);
......
......@@ -3,12 +3,14 @@
* block device frontend for tape device driver
*
* S390 and zSeries version
* Copyright (C) 2001,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Copyright (C) 2001,2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Carsten Otte <cotte@de.ibm.com>
* Tuan Ngo-Anh <ngoanh@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
* Stefan Bader <shbader@de.ibm.com>
*/
#include <linux/fs.h>
#include <linux/config.h>
#include <linux/module.h>
#include <linux/blkdev.h>
......@@ -19,25 +21,53 @@
#include "tape.h"
#define PRINTK_HEADER "TBLOCK:"
#define PRINTK_HEADER "TAPE_BLOCK: "
#define TAPEBLOCK_MAX_SEC 100
#define TAPEBLOCK_MIN_REQUEUE 3
/*
* 2003/11/25 Stefan Bader <shbader@de.ibm.com>
*
* In 2.5/2.6 the block device request function is very likely to be called
* with disabled interrupts (e.g. generic_unplug_device). So the driver can't
* just call any function that tries to allocate CCW requests from that con-
* text since it might sleep. There are two choices to work around this:
* a) do not allocate with kmalloc but use its own memory pool
* b) take requests from the queue outside that context, knowing that
* allocation might sleep
*/
/*
* file operation structure for tape block frontend
*/
static int tapeblock_open(struct inode *, struct file *);
static int tapeblock_release(struct inode *, struct file *);
static int tapeblock_ioctl(struct inode *, struct file *, unsigned int,
unsigned long);
static int tapeblock_medium_changed(struct gendisk *);
static int tapeblock_revalidate_disk(struct gendisk *);
static struct block_device_operations tapeblock_fops = {
.owner = THIS_MODULE,
.open = tapeblock_open,
.release = tapeblock_release,
.owner = THIS_MODULE,
.open = tapeblock_open,
.release = tapeblock_release,
.ioctl = tapeblock_ioctl,
.media_changed = tapeblock_medium_changed,
.revalidate_disk = tapeblock_revalidate_disk,
};
static int tapeblock_major = 0;
static void
tapeblock_trigger_requeue(struct tape_device *device)
{
/* Protect against rescheduling. */
if (atomic_compare_and_swap(0, 1, &device->blk_data.requeue_scheduled))
return;
schedule_work(&device->blk_data.requeue_task);
}
/*
* Post finished request.
*/
......@@ -55,6 +85,8 @@ __tapeblock_end_request(struct tape_request *ccw_req, void *data)
struct tape_device *device;
struct request *req;
DBF_LH(6, "__tapeblock_end_request()\n");
device = ccw_req->device;
req = (struct request *) data;
tapeblock_end_request(req, ccw_req->rc == 0);
......@@ -68,33 +100,75 @@ __tapeblock_end_request(struct tape_request *ccw_req, void *data)
device->discipline->free_bread(ccw_req);
if (!list_empty(&device->req_queue) ||
elv_next_request(device->blk_data.request_queue))
tasklet_schedule(&device->blk_data.tasklet);
tapeblock_trigger_requeue(device);
}
/*
* Fetch requests from block device queue.
* Feed the tape device CCW queue with requests supplied in a list.
*/
static inline void
__tape_process_blk_queue(struct tape_device *device, struct list_head *new_req)
static inline int
tapeblock_start_request(struct tape_device *device, struct request *req)
{
request_queue_t *queue;
struct list_head *l;
struct request *req;
struct tape_request *ccw_req;
int nr_queued;
struct tape_request * ccw_req;
int rc;
DBF_LH(6, "tapeblock_start_request(%p, %p)\n", device, req);
ccw_req = device->discipline->bread(device, req);
if (IS_ERR(ccw_req)) {
DBF_EVENT(1, "TBLOCK: bread failed\n");
tapeblock_end_request(req, 0);
return PTR_ERR(ccw_req);
}
ccw_req->callback = __tapeblock_end_request;
ccw_req->callback_data = (void *) req;
ccw_req->retries = TAPEBLOCK_RETRIES;
rc = tape_do_io_async(device, ccw_req);
if (rc) {
/*
* Start/enqueueing failed. No retries in
* this case.
*/
tapeblock_end_request(req, 0);
device->discipline->free_bread(ccw_req);
}
/* FIXME: we have to make sure that the tapeblock frontend
owns the device. tape_state != TS_IN_USE is NOT enough. */
if (device->tape_state != TS_IN_USE)
return rc;
}
/*
* Move requests from the block device request queue to the tape device ccw
* queue.
*/
static void
tapeblock_requeue(void *data) {
struct tape_device * device;
request_queue_t * queue;
int nr_queued;
struct request * req;
struct list_head * l;
int rc;
device = (struct tape_device *) data;
if (!device)
return;
queue = device->blk_data.request_queue;
nr_queued = 0;
spin_lock_irq(get_ccwdev_lock(device->cdev));
queue = device->blk_data.request_queue;
/* Count number of requests on ccw queue. */
nr_queued = 0;
list_for_each(l, &device->req_queue)
nr_queued++;
while (!blk_queue_plugged(queue) &&
elv_next_request(queue) &&
nr_queued < TAPEBLOCK_MIN_REQUEUE) {
spin_unlock(get_ccwdev_lock(device->cdev));
spin_lock(&device->blk_data.request_queue_lock);
while (
!blk_queue_plugged(queue) &&
elv_next_request(queue) &&
nr_queued < TAPEBLOCK_MIN_REQUEUE
) {
req = elv_next_request(queue);
if (rq_data_dir(req) == WRITE) {
DBF_EVENT(1, "TBLOCK: Rejecting write request\n");
......@@ -102,52 +176,14 @@ __tape_process_blk_queue(struct tape_device *device, struct list_head *new_req)
tapeblock_end_request(req, 0);
continue;
}
ccw_req = device->discipline->bread(device, req);
if (IS_ERR(ccw_req)) {
if (PTR_ERR(ccw_req) == -ENOMEM)
break; /* don't try again */
DBF_EVENT(1, "TBLOCK: bread failed\n");
blkdev_dequeue_request(req);
tapeblock_end_request(req, 0);
continue;
}
ccw_req->callback = __tapeblock_end_request;
ccw_req->callback_data = (void *) req;
ccw_req->retries = TAPEBLOCK_RETRIES;
spin_unlock_irq(&device->blk_data.request_queue_lock);
rc = tapeblock_start_request(device, req);
spin_lock_irq(&device->blk_data.request_queue_lock);
blkdev_dequeue_request(req);
list_add_tail(new_req, &ccw_req->list);
nr_queued++;
}
}
/*
* Feed requests to the tape device.
*/
static inline int
tape_queue_requests(struct tape_device *device, struct list_head *new_req)
{
struct list_head *l, *n;
struct tape_request *ccw_req;
struct request *req;
int rc, fail;
fail = 0;
list_for_each_safe(l, n, new_req) {
ccw_req = list_entry(l, struct tape_request, list);
list_del(&ccw_req->list);
rc = tape_do_io_async(device, ccw_req);
if (rc) {
/*
* Start/enqueueing failed. No retries in
* this case.
*/
req = (struct request *) ccw_req->callback_data;
tapeblock_end_request(req, 0);
device->discipline->free_bread(ccw_req);
fail = 1;
}
}
return fail;
spin_unlock_irq(&device->blk_data.request_queue_lock);
atomic_set(&device->blk_data.requeue_scheduled, 0);
}
/*
......@@ -156,48 +192,14 @@ tape_queue_requests(struct tape_device *device, struct list_head *new_req)
static void
tapeblock_request_fn(request_queue_t *queue)
{
struct list_head new_req;
struct tape_device *device;
device = (struct tape_device *) queue->queuedata;
while (elv_next_request(queue)) {
INIT_LIST_HEAD(&new_req);
spin_lock(get_ccwdev_lock(device->cdev));
__tape_process_blk_queue(device, &new_req);
spin_unlock(get_ccwdev_lock(device->cdev));
/*
* Now queue the new request to the tape. This needs to be
* done without the device lock held.
*/
if (tape_queue_requests(device, &new_req) == 0)
/* All requests queued. Thats enough for now. */
break;
}
}
/*
* Acquire the device lock and process queues for the device.
*/
static void
tapeblock_tasklet(unsigned long data)
{
struct list_head new_req;
struct tape_device *device;
DBF_LH(6, "tapeblock_request_fn(device=%p)\n", device);
if (device == NULL)
BUG();
device = (struct tape_device *) data;
while (elv_next_request(device->blk_data.request_queue)) {
INIT_LIST_HEAD(&new_req);
spin_lock_irq(get_ccwdev_lock(device->cdev));
__tape_process_blk_queue(device, &new_req);
spin_unlock_irq(get_ccwdev_lock(device->cdev));
/*
* Now queue the new request to the tape. This needs to be
* done without the device lock held.
*/
if (tape_queue_requests(device, &new_req) == 0)
/* All requests queued. Thats enough for now. */
break;
}
tapeblock_trigger_requeue(device);
}
/*
......@@ -206,126 +208,192 @@ tapeblock_tasklet(unsigned long data)
int
tapeblock_setup_device(struct tape_device * device)
{
struct tape_blk_data *d = &device->blk_data;
request_queue_t *q;
struct gendisk *disk = alloc_disk(1);
int rc;
if (!disk)
struct tape_blk_data * blkdat;
struct gendisk * disk;
int rc;
blkdat = &device->blk_data;
spin_lock_init(&blkdat->request_queue_lock);
atomic_set(&blkdat->requeue_scheduled, 0);
blkdat->request_queue = blk_init_queue(
tapeblock_request_fn,
&blkdat->request_queue_lock
);
if (!blkdat->request_queue)
return -ENOMEM;
tasklet_init(&d->tasklet, tapeblock_tasklet, (unsigned long)device);
spin_lock_init(&d->request_queue_lock);
q = blk_init_queue(tapeblock_request_fn, &d->request_queue_lock);
if (!q) {
rc = -ENXIO;
goto put_disk;
}
d->request_queue = q;
elevator_exit(q);
rc = elevator_init(q, &elevator_noop);
elevator_exit(blkdat->request_queue);
rc = elevator_init(blkdat->request_queue, &elevator_noop);
if (rc)
goto cleanup_queue;
/* FIXME: We should be able to sense the sectore size */
blk_queue_hardsect_size(q, TAPEBLOCK_HSEC_SIZE);
blk_queue_max_sectors(q, TAPEBLOCK_MAX_SEC);
blk_queue_max_phys_segments(q, -1L);
blk_queue_max_hw_segments(q, -1L);
blk_queue_max_segment_size(q, -1L);
blk_queue_segment_boundary(q, -1L);
blk_queue_hardsect_size(blkdat->request_queue, TAPEBLOCK_HSEC_SIZE);
blk_queue_max_sectors(blkdat->request_queue, TAPEBLOCK_MAX_SEC);
blk_queue_max_phys_segments(blkdat->request_queue, -1L);
blk_queue_max_hw_segments(blkdat->request_queue, -1L);
blk_queue_max_segment_size(blkdat->request_queue, -1L);
blk_queue_segment_boundary(blkdat->request_queue, -1L);
disk = alloc_disk(1);
if (!disk) {
rc = -ENOMEM;
goto cleanup_queue;
}
disk->major = tapeblock_major;
disk->first_minor = device->first_minor;
disk->fops = &tapeblock_fops;
disk->private_data = device;
disk->queue = q;
//set_capacity(disk, size);
disk->private_data = tape_get_device_reference(device);
disk->queue = blkdat->request_queue;
set_capacity(disk, 0);
sprintf(disk->disk_name, "btibm%d",
device->first_minor / TAPE_MINORS_PER_DEV);
sprintf(disk->disk_name, "tBLK/%d", device->first_minor / TAPE_MINORS_PER_DEV);
blkdat->disk = disk;
blkdat->medium_changed = 1;
blkdat->request_queue->queuedata = tape_get_device_reference(device);
add_disk(disk);
d->disk = disk;
INIT_WORK(&blkdat->requeue_task, tapeblock_requeue,
tape_get_device_reference(device));
/* Will vanish */
tape_hotplug_event(device, tapeblock_major, TAPE_HOTPLUG_BLOCK_ADD);
return 0;
cleanup_queue:
blk_cleanup_queue(q);
put_disk:
put_disk(disk);
cleanup_queue:
blk_cleanup_queue(blkdat->request_queue);
blkdat->request_queue = NULL;
return rc;
}
void
tapeblock_cleanup_device(struct tape_device *device)
{
struct tape_blk_data *d = &device->blk_data;
tape_hotplug_event(device, tapeblock_major, TAPE_HOTPLUG_BLOCK_REMOVE);
flush_scheduled_work();
device->blk_data.requeue_task.data = tape_put_device(device);
del_gendisk(device->blk_data.disk);
device->blk_data.disk->private_data =
tape_put_device(device->blk_data.disk->private_data);
put_disk(device->blk_data.disk);
del_gendisk(d->disk);
put_disk(d->disk);
blk_cleanup_queue(d->request_queue);
device->blk_data.disk = NULL;
device->blk_data.request_queue->queuedata = tape_put_device(device);
tasklet_kill(&d->tasklet);
blk_cleanup_queue(device->blk_data.request_queue);
device->blk_data.request_queue = NULL;
}
/*
* Detect number of blocks of the tape.
* FIXME: can we extent this to detect the blocks size as well ?
*/
static int tapeblock_mediumdetect(struct tape_device *device)
static int
tapeblock_revalidate_disk(struct gendisk *disk)
{
unsigned int nr_of_blks;
int rc;
struct tape_device * device;
unsigned int nr_of_blks;
int rc;
device = (struct tape_device *) disk->private_data;
if (!device)
BUG();
if (!device->blk_data.medium_changed)
return 0;
PRINT_INFO("Detecting media size...\n");
rc = tape_mtop(device, MTREW, 1);
if (rc)
return rc;
rc = tape_mtop(device, MTFSF, 1);
rc = tape_mtop(device, MTFSFM, 1);
if (rc)
return rc;
rc = tape_mtop(device, MTTELL, 1);
if (rc)
if (rc < 0)
return rc;
nr_of_blks = rc - 1; /* don't count FM */
rc = tape_mtop(device, MTREW, 1);
if (rc)
DBF_LH(3, "Image file ends at %d\n", rc);
nr_of_blks = rc;
/* This will fail for the first file. Catch the error by checking the
* position. */
tape_mtop(device, MTBSF, 1);
rc = tape_mtop(device, MTTELL, 1);
if (rc < 0)
return rc;
if (rc > nr_of_blks)
return -EINVAL;
DBF_LH(3, "Image file starts at %d\n", rc);
device->bof = rc;
nr_of_blks -= rc;
PRINT_INFO("Found %i blocks on media\n", nr_of_blks);
set_capacity(device->blk_data.disk,
nr_of_blks*(TAPEBLOCK_HSEC_SIZE/512));
device->blk_data.block_position = 0;
device->blk_data.medium_changed = 0;
return 0;
}
static int
tapeblock_medium_changed(struct gendisk *disk)
{
struct tape_device *device;
device = (struct tape_device *) disk->private_data;
DBF_LH(6, "tapeblock_medium_changed(%p) = %d\n",
device, device->blk_data.medium_changed);
return device->blk_data.medium_changed;
}
/*
* Block frontend tape device open function.
*/
static int
tapeblock_open(struct inode *inode, struct file *filp)
{
struct gendisk *disk = inode->i_bdev->bd_disk;
struct tape_device *device = disk->private_data;
int rc;
struct gendisk * disk;
struct tape_device * device;
int rc;
disk = inode->i_bdev->bd_disk;
device = tape_get_device_reference(disk->private_data);
if (device->required_tapemarks) {
DBF_EVENT(2, "TBLOCK: missing tapemarks\n");
PRINT_ERR("TBLOCK: Refusing to open tape with missing"
" end of file marks.\n");
rc = -EPERM;
goto put_device;
}
/*
* FIXME: this new tapeblock_open function is from 2.5.69.
* It doesn't do tape_get_device anymore but picks the device
* pointer from disk->private_data. It is stored in
* tapeblock_setup_device but WITHOUT proper ref-counting.
*/
rc = tape_open(device);
if (rc)
goto put_device;
rc = tape_assign(device);
rc = tapeblock_revalidate_disk(disk);
if (rc)
goto release;
device->blk_data.block_position = -1;
rc = tapeblock_mediumdetect(device);
if (rc)
goto unassign;
/*
* Note: The reference to <device> is hold until the release function
* is called.
*/
tape_state_set(device, TS_BLKUSE);
return 0;
unassign:
tape_unassign(device);
release:
release:
tape_release(device);
put_device:
tape_put_device(device);
......@@ -334,6 +402,9 @@ tapeblock_open(struct inode *inode, struct file *filp)
/*
* Block frontend tape device release function.
*
* Note: One reference to the tape device was made by the open function. So
* we just get the pointer here and release the reference.
*/
static int
tapeblock_release(struct inode *inode, struct file *filp)
......@@ -341,13 +412,53 @@ tapeblock_release(struct inode *inode, struct file *filp)
struct gendisk *disk = inode->i_bdev->bd_disk;
struct tape_device *device = disk->private_data;
tape_state_set(device, TS_IN_USE);
tape_release(device);
tape_unassign(device);
tape_put_device(device);
return 0;
}
/*
* Support of some generic block device IOCTLs.
*/
static int
tapeblock_ioctl(
struct inode * inode,
struct file * file,
unsigned int command,
unsigned long arg
) {
int rc;
int minor;
struct gendisk *disk = inode->i_bdev->bd_disk;
struct tape_device *device = disk->private_data;
rc = 0;
disk = inode->i_bdev->bd_disk;
if (!disk)
BUG();
device = disk->private_data;
if (!device)
BUG();
minor = iminor(inode);
DBF_LH(6, "tapeblock_ioctl(0x%0x)\n", command);
DBF_LH(6, "device = %d:%d\n", tapeblock_major, minor);
switch (command) {
/* Refuse some IOCTL calls without complaining (mount). */
case 0x5310: /* CDROMMULTISESSION */
rc = -EINVAL;
break;
default:
PRINT_WARN("invalid ioctl 0x%x\n", command);
rc = -EINVAL;
}
return rc;
}
/*
* Initialize block device frontend.
*/
......
......@@ -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. */
......
......@@ -23,9 +23,10 @@
#include "tape.h"
#include "tape_std.h"
#define PRINTK_HEADER "T390:"
#define PRINTK_HEADER "TAPE_CORE: "
static void tape_do_irq (struct ccw_device *, unsigned long, struct irb *);
static void __tape_do_irq (struct ccw_device *, unsigned long, struct irb *);
static void __tape_remove_request(struct tape_device *, struct tape_request *);
/*
* One list to contain all tape devices of all disciplines, so
......@@ -35,11 +36,6 @@ static void tape_do_irq (struct ccw_device *, unsigned long, struct irb *);
static struct list_head tape_device_list = LIST_HEAD_INIT(tape_device_list);
static rwlock_t tape_device_lock = RW_LOCK_UNLOCKED;
/*
* Wait queue for tape_delete_device waits.
*/
static DECLARE_WAIT_QUEUE_HEAD(tape_delete_wq);
/*
* Pointer to debug area.
*/
......@@ -50,8 +46,11 @@ debug_info_t *tape_dbf_area = NULL;
*/
const char *tape_state_verbose[TS_SIZE] =
{
[TS_UNUSED] = "UNUSED", [TS_IN_USE] = "IN_USE",
[TS_INIT] = "INIT ", [TS_NOT_OPER] = "NOT_OP"
[TS_UNUSED] = "UNUSED",
[TS_IN_USE] = "IN_USE",
[TS_BLKUSE] = "BLKUSE",
[TS_INIT] = "INIT ",
[TS_NOT_OPER] = "NOT_OP"
};
const char *tape_op_verbose[TO_SIZE] =
......@@ -70,10 +69,106 @@ const char *tape_op_verbose[TO_SIZE] =
[TO_UNASSIGN] = "UAS"
};
/*
* Some channel attached tape specific attributes.
*
* FIXME: In the future the first_minor and blocksize attribute should be
* replaced by a link to the cdev tree.
*/
static ssize_t
tape_medium_state_show(struct device *dev, char *buf)
{
struct tape_device *tdev;
tdev = (struct tape_device *) dev->driver_data;
return snprintf(buf, PAGE_SIZE, "%i\n", tdev->medium_state);
}
static
DEVICE_ATTR(medium_state, 0444, tape_medium_state_show, NULL);
static ssize_t
tape_first_minor_show(struct device *dev, char *buf)
{
struct tape_device *tdev;
tdev = (struct tape_device *) dev->driver_data;
return snprintf(buf, PAGE_SIZE, "%i\n", tdev->first_minor);
}
static
DEVICE_ATTR(first_minor, 0444, tape_first_minor_show, NULL);
static ssize_t
tape_state_show(struct device *dev, char *buf)
{
struct tape_device *tdev;
tdev = (struct tape_device *) dev->driver_data;
return snprintf(buf, PAGE_SIZE, "%s\n", (tdev->first_minor < 0) ?
"OFFLINE" : tape_state_verbose[tdev->tape_state]);
}
static
DEVICE_ATTR(state, 0444, tape_state_show, NULL);
static ssize_t
tape_operation_show(struct device *dev, char *buf)
{
struct tape_device *tdev;
ssize_t rc;
tdev = (struct tape_device *) dev->driver_data;
if (tdev->first_minor < 0)
return snprintf(buf, PAGE_SIZE, "N/A\n");
spin_lock_irq(get_ccwdev_lock(tdev->cdev));
if (list_empty(&tdev->req_queue))
rc = snprintf(buf, PAGE_SIZE, "---\n");
else {
struct tape_request *req;
req = list_entry(tdev->req_queue.next, struct tape_request,
list);
rc = snprintf(buf, PAGE_SIZE, "%s\n", tape_op_verbose[req->op]);
}
spin_unlock_irq(get_ccwdev_lock(tdev->cdev));
return rc;
}
static
DEVICE_ATTR(operation, 0444, tape_operation_show, NULL);
static ssize_t
tape_blocksize_show(struct device *dev, char *buf)
{
struct tape_device *tdev;
tdev = (struct tape_device *) dev->driver_data;
return snprintf(buf, PAGE_SIZE, "%i\n", tdev->char_data.block_size);
}
static
DEVICE_ATTR(blocksize, 0444, tape_blocksize_show, NULL);
static struct attribute *tape_attrs[] = {
&dev_attr_medium_state.attr,
&dev_attr_first_minor.attr,
&dev_attr_state.attr,
&dev_attr_operation.attr,
&dev_attr_blocksize.attr,
NULL
};
static struct attribute_group tape_attr_group = {
.attrs = tape_attrs,
};
/*
* Tape state functions
*/
static void
void
tape_state_set(struct tape_device *device, enum tape_state newstate)
{
const char *str;
......@@ -139,14 +234,20 @@ __tape_halt_io(struct tape_device *device, struct tape_request *request)
/* Check if interrupt has already been processed */
if (request->callback == NULL)
return 0;
rc = 0;
for (retries = 0; retries < 5; retries++) {
if (retries < 2)
rc = ccw_device_halt(device->cdev, (long) request);
else
rc = ccw_device_clear(device->cdev, (long) request);
if (rc == 0)
break; /* termination successful */
if (rc == 0) { /* Termination successful */
request->rc = -EIO;
request->status = TAPE_REQUEST_DONE;
return 0;
}
if (rc == -ENODEV)
DBF_EXCEPTION(2, "device gone, retry\n");
else if (rc == -EIO)
......@@ -156,8 +257,7 @@ __tape_halt_io(struct tape_device *device, struct tape_request *request)
else
BUG();
}
if (rc == 0)
request->status = TAPE_REQUEST_DONE;
return rc;
}
......@@ -207,8 +307,12 @@ tape_enable_device(struct tape_device *device,
{
int rc;
if (device->tape_state != TS_INIT)
DBF_LH(6, "tape_enable_device(%p, %p)\n", device, discipline);
if (device->tape_state != TS_INIT) {
DBF_LH(3, "Tapestate not INIT (%d)\n", device->tape_state);
return -EINVAL;
}
/* Let the discipline have a go at the device. */
device->discipline = discipline;
......@@ -218,6 +322,7 @@ tape_enable_device(struct tape_device *device,
rc = tape_assign_minor(device);
if (rc)
goto out_discipline;
rc = tapechar_setup_device(device);
if (rc)
goto out_minor;
......@@ -250,7 +355,6 @@ tape_disable_device(struct tape_device *device)
struct tape_request *request;
spin_lock_irq(get_ccwdev_lock(device->cdev));
tape_state_set(device, TS_NOT_OPER);
/* Post remaining requests with -EIO */
list_for_each_safe(l, n, &device->req_queue) {
request = list_entry(l, struct tape_request, list);
......@@ -258,7 +362,7 @@ tape_disable_device(struct tape_device *device)
__tape_halt_io(device, request);
list_del(&request->list);
/* Decrease ref_count for removed request. */
tape_put_device(device);
request->device = tape_put_device(device);
request->rc = -EIO;
if (request->callback != NULL)
request->callback(request, request->callback_data);
......@@ -269,6 +373,9 @@ tape_disable_device(struct tape_device *device)
tapechar_cleanup_device(device);
device->discipline->cleanup_device(device);
tape_remove_minor(device);
tape_med_state_set(device, MS_UNKNOWN);
device->tape_state = TS_INIT;
}
/*
......@@ -301,17 +408,51 @@ tape_alloc_device(void)
device->tape_state = TS_INIT;
device->medium_state = MS_UNKNOWN;
*device->modeset_byte = 0;
device->first_minor = -1;
atomic_set(&device->ref_count, 1);
return device;
}
/*
* Free memory of a device structure.
* Get a reference to an existing device structure. This will automatically
* increment the reference count.
*/
static void
tape_free_device(struct tape_device *device)
struct tape_device *
tape_get_device_reference(struct tape_device *device)
{
kfree(device->modeset_byte);
kfree(device);
DBF_EVENT(4, "tape_get_device_reference(%p) = %i\n", device,
atomic_inc_return(&device->ref_count));
return device;
}
/*
* Decrease the reference counter of a devices structure. If the
* reference counter reaches zero free the device structure.
* The function returns a NULL pointer to be used by the caller
* for clearing reference pointers.
*/
struct tape_device *
tape_put_device(struct tape_device *device)
{
int remain;
remain = atomic_dec_return(&device->ref_count);
if (remain > 0) {
DBF_EVENT(4, "tape_put_device(%p) -> %i\n", device, remain);
} else {
if (remain < 0) {
DBF_EVENT(4, "put device without reference\n");
PRINT_ERR("put device without reference\n");
} else {
DBF_EVENT(4, "tape_free_device(%p)\n", device);
kfree(device->modeset_byte);
kfree(device);
}
}
return NULL;
}
/*
......@@ -325,9 +466,8 @@ tape_get_device(int devindex)
device = ERR_PTR(-ENODEV);
read_lock(&tape_device_lock);
list_for_each_entry(tmp, &tape_device_list, node) {
if (tmp->first_minor * TAPE_MINORS_PER_DEV == devindex) {
device = tmp;
atomic_inc(&device->ref_count);
if (tmp->first_minor / TAPE_MINORS_PER_DEV == devindex) {
device = tape_get_device_reference(tmp);
break;
}
}
......@@ -335,25 +475,6 @@ tape_get_device(int devindex)
return device;
}
/*
* Decrease the reference counter of a devices structure. If the
* reference counter reaches zero free the device structure and
* wake up sleepers.
*/
void
tape_put_device(struct tape_device *device)
{
if (atomic_dec_return(&device->ref_count) > 0)
return;
/*
* Reference counter dropped to zero. This means
* that the device is deleted and the last user
* of the device structure is gone. That is what
* tape_delete_device is waiting for. Do a wake up.
*/
wake_up(&tape_delete_wq);
}
/*
* Driverfs tape probe function.
*/
......@@ -367,12 +488,12 @@ tape_generic_probe(struct ccw_device *cdev)
if (IS_ERR(device))
return -ENODEV;
PRINT_INFO("tape device %s found\n", bus_id);
atomic_inc(&device->ref_count);
cdev->dev.driver_data = device;
device->cdev = cdev;
cdev->handler = tape_do_irq;
cdev->handler = __tape_do_irq;
ccw_device_set_options(cdev, CCWDEV_DO_PATHGROUP);
sysfs_create_group(&cdev->dev.kobj, &tape_attr_group);
return 0;
}
......@@ -380,19 +501,14 @@ tape_generic_probe(struct ccw_device *cdev)
/*
* Driverfs tape remove function.
*/
int
void
tape_generic_remove(struct ccw_device *cdev)
{
struct tape_device *device;
device = cdev->dev.driver_data;
cdev->dev.driver_data = NULL;
if (device != NULL) {
tape_put_device(device);
wait_event(tape_delete_wq, atomic_read(&device->ref_count) == 0);
tape_free_device(device);
ccw_device_set_offline(cdev);
if (cdev->dev.driver_data != NULL) {
sysfs_remove_group(&cdev->dev.kobj, &tape_attr_group);
cdev->dev.driver_data = tape_put_device(cdev->dev.driver_data);
}
return 0;
}
/*
......@@ -406,6 +522,8 @@ tape_alloc_request(int cplength, int datasize)
if (datasize > PAGE_SIZE || (cplength*sizeof(struct ccw1)) > PAGE_SIZE)
BUG();
DBF_LH(6, "tape_alloc_request(%d, %d)\n", cplength, datasize);
request = (struct tape_request *) kmalloc(sizeof(struct tape_request),
GFP_KERNEL);
if (request == NULL) {
......@@ -436,6 +554,9 @@ tape_alloc_request(int cplength, int datasize)
}
memset(request->cpdata, 0, datasize);
}
DBF_LH(6, "New request %p(%p/%p)\n", request, request->cpaddr,
request->cpdata);
return request;
}
......@@ -445,9 +566,10 @@ tape_alloc_request(int cplength, int datasize)
void
tape_free_request (struct tape_request * request)
{
DBF_LH(6, "Free request %p\n", request);
if (request->device != NULL) {
tape_put_device(request->device);
request->device = NULL;
request->device = tape_put_device(request->device);
}
if (request->cpdata != NULL)
kfree(request->cpdata);
......@@ -456,6 +578,56 @@ tape_free_request (struct tape_request * request)
kfree(request);
}
static inline void
__tape_do_io_list(struct tape_device *device)
{
struct list_head *l, *n;
struct tape_request *request;
int rc;
DBF_LH(6, "__tape_do_io_list(%p)\n", device);
/*
* Try to start each request on request queue until one is
* started successful.
*/
list_for_each_safe(l, n, &device->req_queue) {
request = list_entry(l, struct tape_request, list);
#ifdef CONFIG_S390_TAPE_BLOCK
if (request->op == TO_BLOCK)
device->discipline->check_locate(device, request);
#endif
rc = ccw_device_start(device->cdev, request->cpaddr,
(unsigned long) request, 0x00,
request->options);
if (rc == 0) {
request->status = TAPE_REQUEST_IN_IO;
break;
}
/* Start failed. Remove request and indicate failure. */
DBF_EVENT(1, "tape: DOIO failed with er = %i\n", rc);
/* Set ending status and do callback. */
request->rc = rc;
request->status = TAPE_REQUEST_DONE;
__tape_remove_request(device, request);
}
}
static void
__tape_remove_request(struct tape_device *device, struct tape_request *request)
{
/* Remove from request queue. */
list_del(&request->list);
/* Do callback. */
if (request->callback != NULL)
request->callback(request, request->callback_data);
/* Start next request. */
if (!list_empty(&device->req_queue))
__tape_do_io_list(device);
}
/*
* Write sense data to console/dbf
*/
......@@ -514,12 +686,24 @@ __tape_do_io(struct tape_device *device, struct tape_request *request)
{
int rc;
if (device->tape_state != TS_IN_USE)
return -ENODEV;
switch (request->op) {
case TO_MSEN:
case TO_ASSIGN:
case TO_UNASSIGN:
case TO_READ_ATTMSG:
if (device->tape_state == TS_INIT)
break;
if (device->tape_state == TS_UNUSED)
break;
default:
if (device->tape_state == TS_BLKUSE)
break;
if (device->tape_state != TS_IN_USE)
return -ENODEV;
}
/* Increase use count of device for the added request. */
atomic_inc(&device->ref_count);
request->device = device;
request->device = tape_get_device_reference(device);
if (list_empty(&device->req_queue)) {
/* No other requests are on the queue. Start this one. */
......@@ -534,9 +718,11 @@ __tape_do_io(struct tape_device *device, struct tape_request *request)
DBF_EVENT(1, "tape: DOIO failed with rc = %i\n", rc);
return rc;
}
DBF_LH(5, "Request %p added for execution.\n", request);
list_add(&request->list, &device->req_queue);
request->status = TAPE_REQUEST_IN_IO;
} else {
DBF_LH(5, "Request %p add to queue.\n", request);
list_add_tail(&request->list, &device->req_queue);
request->status = TAPE_REQUEST_QUEUED;
}
......@@ -552,6 +738,8 @@ tape_do_io_async(struct tape_device *device, struct tape_request *request)
{
int rc;
DBF_LH(6, "tape_do_io_async(%p, %p)\n", device, request);
spin_lock_irq(get_ccwdev_lock(device->cdev));
/* Add request to request queue and try to start it. */
rc = __tape_do_io(device, request);
......@@ -637,48 +825,11 @@ tape_do_io_interruptible(struct tape_device *device,
return rc;
}
static inline void
__tape_do_io_list(struct tape_device *device)
{
struct list_head *l, *n;
struct tape_request *request;
int rc;
if (device->tape_state != TS_IN_USE)
return;
/*
* Try to start each request on request queue until one is
* started successful.
*/
list_for_each_safe(l, n, &device->req_queue) {
request = list_entry(l, struct tape_request, list);
#ifdef CONFIG_S390_TAPE_BLOCK
if (request->op == TO_BLOCK)
device->discipline->check_locate(device, request);
#endif
rc = ccw_device_start(device->cdev, request->cpaddr,
(unsigned long) request, 0x00,
request->options);
if (rc == 0) {
request->status = TAPE_REQUEST_IN_IO;
break;
}
/* Start failed. Remove request and indicate failure. */
DBF_EVENT(1, "tape: DOIO failed with er = %i\n", rc);
list_del(&request->list);
/* Set ending status and do callback. */
request->rc = rc;
request->status = TAPE_REQUEST_DONE;
if (request->callback != NULL)
request->callback(request, request->callback_data);
}
}
/*
* Tape interrupt routine, called from the ccw_device layer
*/
static void
tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
__tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
{
struct tape_device *device;
struct tape_request *request;
......@@ -693,11 +844,13 @@ tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
}
request = (struct tape_request *) intparm;
DBF_LH(6, "__tape_do_irq(device=%p, request=%p)\n", device, request);
/* May be an unsolicited irq */
if(request != NULL)
request->rescnt = irb->scsw.count;
if (irb->scsw.dstat != 0x0c){
if (irb->scsw.dstat != 0x0c) {
/* Set the 'ONLINE' flag depending on sense byte 1 */
if(*(((__u8 *) irb->ecw) + 1) & SENSE_DRIVE_ONLINE)
device->tape_generic_status |= GMT_ONLINE(~0);
......@@ -718,6 +871,16 @@ tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
DBF_EVENT(6, "tape:device is not operational\n");
return;
}
/*
* Request that were canceled still come back with an interrupt.
* To detect these request the state will be set to TAPE_REQUEST_DONE.
*/
if(request != NULL && request->status == TAPE_REQUEST_DONE) {
__tape_remove_request(device, request);
return;
}
rc = device->discipline->irq(device, request, irb);
/*
* rc < 0 : request finished unsuccessfully.
......@@ -729,6 +892,8 @@ tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
final = 0;
switch (rc) {
case TAPE_IO_SUCCESS:
/* Upon normal completion the device _is_ online */
device->tape_generic_status |= GMT_ONLINE(~0);
final = 1;
break;
case TAPE_IO_PENDING:
......@@ -748,8 +913,6 @@ tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
break;
case TAPE_IO_STOP:
__tape_halt_io(device, request);
rc = -EIO;
final = 1;
break;
default:
if (rc > 0) {
......@@ -767,59 +930,11 @@ tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
/* Set ending status. */
request->rc = rc;
request->status = TAPE_REQUEST_DONE;
/* Remove from request queue. */
list_del(&request->list);
/* Do callback. */
if (request->callback != NULL)
request->callback(request, request->callback_data);
__tape_remove_request(device, request);
} else {
__tape_do_io_list(device);
}
/* Start next request. */
__tape_do_io_list(device);
}
}
/*
* Lock a shared tape for our exclusive use.
*/
int
tape_assign(struct tape_device *device)
{
int rc;
rc = device->discipline->assign(device);
if (rc) {
PRINT_WARN("(%s): assign failed - device might be busy\n",
device->cdev->dev.bus_id);
DBF_EVENT(3, "(%s): assign failed - device might be busy\n",
device->cdev->dev.bus_id);
return rc;
}
DBF_EVENT(3, "(%s): assign lpum = %02x\n",
device->cdev->dev.bus_id,
0 /* FIXME: device->devstat.lpum */ );
return 0;
}
/*
* Unlock a shared tape.
*/
int
tape_unassign(struct tape_device *device)
{
int rc;
rc = device->discipline->unassign(device);
if (rc) {
PRINT_WARN("(%s): unassign failed\n",
device->cdev->dev.bus_id);
DBF_EVENT(3, "(%s): unassign failed\n",
device->cdev->dev.bus_id);
return rc;
}
DBF_EVENT(3, "(%s): unassign lpum = %02x\n",
device->cdev->dev.bus_id,
0 /* FIXME: device->devstat.lpum */ );
return 0;
}
/*
......@@ -837,6 +952,9 @@ tape_open(struct tape_device *device)
} else if (device->tape_state == TS_IN_USE) {
DBF_EVENT(6, "TAPE:dbusy\n");
rc = -EBUSY;
} else if (device->tape_state == TS_BLKUSE) {
DBF_EVENT(6, "TAPE:dbusy\n");
rc = -EBUSY;
} else if (device->discipline != NULL &&
!try_module_get(device->discipline->owner)) {
DBF_EVENT(6, "TAPE:nodisc\n");
......@@ -884,7 +1002,7 @@ tape_mtop(struct tape_device *device, int mt_op, int mt_count)
/* We assume that the backends can handle count up to 500. */
if (mt_op == MTBSR || mt_op == MTFSR || mt_op == MTFSF ||
mt_op == MTBSR || mt_op == MTFSFM || mt_op == MTBSFM) {
mt_op == MTBSF || mt_op == MTFSFM || mt_op == MTBSFM) {
rc = 0;
for (; mt_count > 500; mt_count -= 500)
if ((rc = fn(device, 500)) != 0)
......@@ -897,15 +1015,75 @@ tape_mtop(struct tape_device *device, int mt_op, int mt_count)
}
/*
* Hutplug event support.
*/
void
tape_hotplug_event(struct tape_device *device, int devmaj, int action) {
#ifdef CONFIG_HOTPLUG
char *argv[3];
char *envp[8];
char busid[20];
char major[20];
char minor[20];
/* Call the busid DEVNO to be compatible with old tape.agent. */
sprintf(busid, "DEVNO=%s", device->cdev->dev.bus_id);
sprintf(major, "MAJOR=%d", devmaj);
sprintf(minor, "MINOR=%d", device->first_minor);
argv[0] = hotplug_path;
argv[1] = "tape";
argv[2] = NULL;
envp[0] = "HOME=/";
envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
switch (action) {
case TAPE_HOTPLUG_CHAR_ADD:
case TAPE_HOTPLUG_BLOCK_ADD:
envp[2] = "ACTION=add";
break;
case TAPE_HOTPLUG_CHAR_REMOVE:
case TAPE_HOTPLUG_BLOCK_REMOVE:
envp[2] = "ACTION=remove";
break;
default:
BUG();
}
switch (action) {
case TAPE_HOTPLUG_CHAR_ADD:
case TAPE_HOTPLUG_CHAR_REMOVE:
envp[3] = "INTERFACE=char";
break;
case TAPE_HOTPLUG_BLOCK_ADD:
case TAPE_HOTPLUG_BLOCK_REMOVE:
envp[3] = "INTERFACE=block";
break;
default:
BUG();
}
envp[4] = busid;
envp[5] = major;
envp[6] = minor;
envp[7] = NULL;
call_usermodehelper(argv[0], argv, envp, 0);
#endif
}
/*
* Tape init function.
*/
static int
tape_init (void)
{
tape_dbf_area = debug_register ( "tape", 1, 2, 3*sizeof(long));
tape_dbf_area = debug_register ( "tape", 1, 2, 4*sizeof(long));
debug_register_view(tape_dbf_area, &debug_sprintf_view);
DBF_EVENT(3, "tape init: ($Revision: 1.26 $)\n");
#ifdef DBF_LIKE_HELL
debug_set_level(tape_dbf_area, 6);
#endif
DBF_EVENT(3, "tape init: ($Revision: 1.41 $)\n");
tape_proc_init();
tapechar_init ();
tapeblock_init ();
......@@ -930,7 +1108,8 @@ tape_exit(void)
MODULE_AUTHOR("(C) 2001 IBM Deutschland Entwicklung GmbH by Carsten Otte and "
"Michael Holzheu (cotte@de.ibm.com,holzheu@de.ibm.com)");
MODULE_DESCRIPTION("Linux on zSeries channel attached "
"tape device driver ($Revision: 1.26 $)");
"tape device driver ($Revision: 1.41 $)");
MODULE_LICENSE("GPL");
module_init(tape_init);
module_exit(tape_exit);
......@@ -941,6 +1120,7 @@ EXPORT_SYMBOL(tape_disable_device);
EXPORT_SYMBOL(tape_generic_probe);
EXPORT_SYMBOL(tape_enable_device);
EXPORT_SYMBOL(tape_put_device);
EXPORT_SYMBOL(tape_get_device_reference);
EXPORT_SYMBOL(tape_state_verbose);
EXPORT_SYMBOL(tape_op_verbose);
EXPORT_SYMBOL(tape_state_set);
......
......@@ -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
......
......@@ -8,12 +8,14 @@
* Michael Holzheu <holzheu@de.ibm.com>
* Tuan Ngo-Anh <ngoanh@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
* Stefan Bader <shbader@de.ibm.com>
*/
#include <linux/config.h>
#include <linux/stddef.h>
#include <linux/kernel.h>
#include <linux/bio.h>
#include <linux/timer.h>
#include <asm/types.h>
#include <asm/idals.h>
......@@ -23,23 +25,72 @@
#include "tape.h"
#include "tape_std.h"
#define PRINTK_HEADER "T3xxx:"
#define PRINTK_HEADER "TAPE_STD: "
/*
* tape_std_assign
*/
static void
tape_std_assign_timeout(unsigned long data)
{
struct tape_request * request;
struct tape_device * device;
request = (struct tape_request *) data;
if ((device = request->device) == NULL)
BUG();
spin_lock_irq(get_ccwdev_lock(device->cdev));
if (request->callback != NULL) {
DBF_EVENT(3, "%s: Assignment timeout. Device busy.\n",
device->cdev->dev.bus_id);
PRINT_ERR("%s: Assignment timeout. Device busy.\n",
device->cdev->dev.bus_id);
ccw_device_clear(device->cdev, (long) request);
}
spin_unlock_irq(get_ccwdev_lock(device->cdev));
}
int
tape_std_assign(struct tape_device *device)
{
int rc;
struct timer_list timeout;
struct tape_request *request;
request = tape_alloc_request(2, 11);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_ASSIGN;
tape_ccw_cc(request->cpaddr, ASSIGN, 11, request->cpdata);
tape_ccw_end(request->cpaddr + 1, NOP, 0, NULL);
return tape_do_io_free(device, request);
/*
* The assign command sometimes blocks if the device is assigned
* to another host (actually this shouldn't happen but it does).
* So we set up a timeout for this call.
*/
init_timer(&timeout);
timeout.function = tape_std_assign_timeout;
timeout.data = (unsigned long) request;
timeout.expires = jiffies + 2 * HZ;
add_timer(&timeout);
rc = tape_do_io_interruptible(device, request);
del_timer(&timeout);
if (rc != 0) {
PRINT_WARN("%s: assign failed - device might be busy\n",
device->cdev->dev.bus_id);
DBF_EVENT(3, "%s: assign failed - device might be busy\n",
device->cdev->dev.bus_id);
} else {
DBF_EVENT(3, "%s: Tape assigned\n", device->cdev->dev.bus_id);
}
tape_free_request(request);
return rc;
}
/*
......@@ -48,30 +99,36 @@ tape_std_assign(struct tape_device *device)
int
tape_std_unassign (struct tape_device *device)
{
int rc;
struct tape_request *request;
request = tape_alloc_request(2, 11);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_UNASSIGN;
tape_ccw_cc(request->cpaddr, UNASSIGN, 11, request->cpdata);
tape_ccw_end(request->cpaddr + 1, NOP, 0, NULL);
return tape_do_io_free(device, request);
if ((rc = tape_do_io(device, request)) != 0) {
DBF_EVENT(3, "%s: Unassign failed\n", device->cdev->dev.bus_id);
PRINT_WARN("%s: Unassign failed\n", device->cdev->dev.bus_id);
} else {
DBF_EVENT(3, "%s: Tape unassigned\n", device->cdev->dev.bus_id);
}
tape_free_request(request);
return rc;
}
/*
* TAPE390_DISPLAY: Show a string on the tape display.
*/
int
tape_std_display(struct tape_device *device, int cmd, unsigned long arg)
tape_std_display(struct tape_device *device, struct display_struct *disp)
{
struct display_struct d_struct;
struct tape_request *request;
int rc;
if (copy_from_user(&d_struct, (char *) arg, sizeof(d_struct)) != 0)
return -EFAULT;
request = tape_alloc_request(2, 17);
if (IS_ERR(request)) {
DBF_EVENT(3, "TAPE: load display failed\n");
......@@ -79,9 +136,10 @@ tape_std_display(struct tape_device *device, int cmd, unsigned long arg)
}
request->op = TO_DIS;
*(unsigned char *) request->cpdata = d_struct.cntrl;
memcpy(((unsigned char *) request->cpdata) + 1, d_struct.message1, 8);
memcpy(((unsigned char *) request->cpdata) + 9, d_struct.message2, 8);
*(unsigned char *) request->cpdata = disp->cntrl;
DBF_EVENT(5, "TAPE: display cntrl=%04x\n", disp->cntrl);
memcpy(((unsigned char *) request->cpdata) + 1, disp->message1, 8);
memcpy(((unsigned char *) request->cpdata) + 9, disp->message2, 8);
ASCEBC(((unsigned char*) request->cpdata) + 1, 16);
tape_ccw_cc(request->cpaddr, LOAD_DISPLAY, 17, request->cpdata);
......@@ -118,6 +176,25 @@ tape_std_read_block_id(struct tape_device *device, __u64 *id)
return rc;
}
int
tape_std_terminate_write(struct tape_device *device)
{
int rc;
if(device->required_tapemarks == 0)
return 0;
DBF_LH(5, "tape%d: terminate write %dxEOF\n", device->first_minor,
device->required_tapemarks);
rc = tape_mtop(device, MTWEOF, device->required_tapemarks);
if (rc)
return rc;
device->required_tapemarks = 0;
return tape_mtop(device, MTBSR, 1);
}
/*
* MTLOAD: Loads the tape.
* The default implementation just wait until the tape medium state changes
......@@ -138,6 +215,7 @@ tape_std_mtsetblk(struct tape_device *device, int count)
{
struct idal_buffer *new;
DBF_LH(6, "tape_std_mtsetblk(%d)\n", count);
if (count <= 0) {
/*
* Just set block_size to 0. tapechar_read/tapechar_write
......@@ -151,6 +229,15 @@ tape_std_mtsetblk(struct tape_device *device, int count)
device->char_data.idal_buf->size == count)
/* We already have a idal buffer of that size. */
return 0;
if (count > MAX_BLOCKSIZE) {
DBF_EVENT(3, "Invalid block size (%d > %d) given.\n",
count, MAX_BLOCKSIZE);
PRINT_ERR("Invalid block size (%d > %d) given.\n",
count, MAX_BLOCKSIZE);
return -EINVAL;
}
/* Allocate a new idal buffer. */
new = idal_buffer_alloc(count, 0);
if (new == NULL)
......@@ -159,6 +246,9 @@ tape_std_mtsetblk(struct tape_device *device, int count)
idal_buffer_free(device->char_data.idal_buf);
device->char_data.idal_buf = new;
device->char_data.block_size = count;
DBF_LH(6, "new blocksize is %d\n", device->char_data.block_size);
return 0;
}
......@@ -192,6 +282,7 @@ tape_std_mtfsf(struct tape_device *device, int mt_count)
device->modeset_byte);
ccw = tape_ccw_repeat(ccw, FORSPACEFILE, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
......@@ -205,6 +296,7 @@ tape_std_mtfsr(struct tape_device *device, int mt_count)
{
struct tape_request *request;
struct ccw1 *ccw;
int rc;
request = tape_alloc_request(mt_count + 2, 0);
if (IS_ERR(request))
......@@ -215,8 +307,16 @@ tape_std_mtfsr(struct tape_device *device, int mt_count)
device->modeset_byte);
ccw = tape_ccw_repeat(ccw, FORSPACEBLOCK, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
rc = tape_do_io(device, request);
if (rc == 0 && request->rescnt > 0) {
DBF_LH(3, "FSR over tapemark\n");
rc = 1;
}
tape_free_request(request);
return rc;
}
/*
......@@ -228,6 +328,7 @@ tape_std_mtbsr(struct tape_device *device, int mt_count)
{
struct tape_request *request;
struct ccw1 *ccw;
int rc;
request = tape_alloc_request(mt_count + 2, 0);
if (IS_ERR(request))
......@@ -238,8 +339,16 @@ tape_std_mtbsr(struct tape_device *device, int mt_count)
device->modeset_byte);
ccw = tape_ccw_repeat(ccw, BACKSPACEBLOCK, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
rc = tape_do_io(device, request);
if (rc == 0 && request->rescnt > 0) {
DBF_LH(3, "BSR over tapemark\n");
rc = 1;
}
tape_free_request(request);
return rc;
}
/*
......@@ -260,6 +369,7 @@ tape_std_mtweof(struct tape_device *device, int mt_count)
device->modeset_byte);
ccw = tape_ccw_repeat(ccw, WRITETAPEMARK, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
......@@ -284,6 +394,7 @@ tape_std_mtbsfm(struct tape_device *device, int mt_count)
device->modeset_byte);
ccw = tape_ccw_repeat(ccw, BACKSPACEFILE, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
......@@ -309,18 +420,12 @@ tape_std_mtbsf(struct tape_device *device, int mt_count)
ccw = tape_ccw_repeat(ccw, BACKSPACEFILE, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
rc = tape_do_io(device, request);
rc = tape_do_io_free(device, request);
if (rc == 0) {
request->op = TO_FSF;
/* need to skip forward over the filemark. */
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
device->modeset_byte);
tape_ccw_cc(request->cpaddr + 1, FORSPACEFILE, 0, NULL);
tape_ccw_end(request->cpaddr + 2, NOP, 0, NULL);
/* execute it */
rc = tape_do_io(device, request);
rc = tape_mtop(device, MTFSR, 1);
if (rc > 0)
rc = 0;
}
tape_free_request(request);
return rc;
}
......@@ -346,18 +451,13 @@ tape_std_mtfsfm(struct tape_device *device, int mt_count)
ccw = tape_ccw_repeat(ccw, FORSPACEFILE, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
rc = tape_do_io(device, request);
rc = tape_do_io_free(device, request);
if (rc == 0) {
request->op = TO_BSF;
/* need to skip forward over the filemark. */
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
device->modeset_byte);
tape_ccw_cc(request->cpaddr + 1, BACKSPACEFILE, 0, NULL);
tape_ccw_end(request->cpaddr + 2, NOP, 0, NULL);
/* execute it */
rc = tape_do_io(device, request);
rc = tape_mtop(device, MTBSR, 1);
if (rc > 0)
rc = 0;
}
tape_free_request(request);
return rc;
}
......@@ -378,6 +478,7 @@ tape_std_mtrew(struct tape_device *device, int mt_count)
device->modeset_byte);
tape_ccw_cc(request->cpaddr + 1, REWIND, 0, NULL);
tape_ccw_end(request->cpaddr + 2, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
......@@ -399,6 +500,7 @@ tape_std_mtoffl(struct tape_device *device, int mt_count)
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_cc(request->cpaddr + 1, REWIND_UNLOAD, 0, NULL);
tape_ccw_end(request->cpaddr + 2, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
......@@ -430,22 +532,28 @@ tape_std_mtnop(struct tape_device *device, int mt_count)
int
tape_std_mteom(struct tape_device *device, int mt_count)
{
struct tape_request *request;
int rc;
request = tape_alloc_request(4, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_FSF;
/* setup ccws */
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_cc(request->cpaddr + 1,FORSPACEFILE, 0, NULL);
tape_ccw_cc(request->cpaddr + 2, NOP, 0, NULL);
tape_ccw_end(request->cpaddr + 3, CCW_CMD_TIC, 0, request->cpaddr);
/* execute it */
tape_do_io_interruptible(device, request);
tape_free_request(request);
/* MTEOM/MTRETEN errors get ignored. */
return 0;
/*
* Seek from the beginning of tape (rewind).
*/
if ((rc = tape_mtop(device, MTREW, 1)) < 0)
return rc;
/*
* The logical end of volume is given by two sewuential tapemarks.
* Look for this by skipping to the next file (over one tapemark)
* and then test for another one (fsr returns 1 if a tapemark was
* encountered).
*/
do {
if ((rc = tape_mtop(device, MTFSF, 1)) < 0)
return rc;
if ((rc = tape_mtop(device, MTFSR, 1)) < 0)
return rc;
} while (rc == 0);
return tape_mtop(device, MTBSR, 1);
}
/*
......@@ -469,7 +577,7 @@ tape_std_mtreten(struct tape_device *device, int mt_count)
/* execute it, MTRETEN rc gets ignored */
rc = tape_do_io_interruptible(device, request);
tape_free_request(request);
return tape_std_mtrew(device, 1);
return tape_mtop(device, MTREW, 1);
}
/*
......@@ -480,7 +588,7 @@ tape_std_mterase(struct tape_device *device, int mt_count)
{
struct tape_request *request;
request = tape_alloc_request(5, 0);
request = tape_alloc_request(6, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_DSE;
......@@ -489,7 +597,9 @@ tape_std_mterase(struct tape_device *device, int mt_count)
tape_ccw_cc(request->cpaddr + 1, REWIND, 0, NULL);
tape_ccw_cc(request->cpaddr + 2, ERASE_GAP, 0, NULL);
tape_ccw_cc(request->cpaddr + 3, DATA_SEC_ERASE, 0, NULL);
tape_ccw_end(request->cpaddr + 4, NOP, 0, NULL);
tape_ccw_cc(request->cpaddr + 4, REWIND, 0, NULL);
tape_ccw_end(request->cpaddr + 5, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
......@@ -500,18 +610,7 @@ tape_std_mterase(struct tape_device *device, int mt_count)
int
tape_std_mtunload(struct tape_device *device, int mt_count)
{
struct tape_request *request;
request = tape_alloc_request(3, 32);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_RUN;
/* setup ccws */
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_cc(request->cpaddr + 1, REWIND_UNLOAD, 0, NULL);
tape_ccw_end(request->cpaddr + 2, SENSE, 32, request->cpdata);
/* execute it */
return tape_do_io_free(device, request);
return tape_mtop(device, MTOFFL, mt_count);
}
/*
......
......@@ -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