mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
Merge patch series "scsi: st: Restore drive settings and state after reset and MTLOAD"
Laurence Oberman <loberman@redhat.com> says: After a device reset st blocks tape access until one of MTREW, MTOFFL, MTLOAD, MTRETEN, MTERASE, MTSEEK or MTEOM clears the reset condition. Testing the reset handling on scsi_debug and on IBM LTO drives found that several settings and parts of the driver state do not survive the reset or an MTLOAD. 1. Changed block size and density are lost after MTLOAD or MTRETEN A reset returns the drive's block size and density to their defaults. Commit7081dc75df("scsi: st: Restore some drive settings after reset") re-applies the values set by the user, but only for MTREW, MTSEEK and MTEOM. Because check_tape() reads the drive's block size with MODE SENSE on every open, the driver follows the drive back to its default after the reset. An application that set fixed-block mode with MTSETBLK and recovers with MTLOAD or MTRETEN keeps writing without any error, but in variable-length blocks. Patch 1 restores the changed settings for MTLOAD and MTRETEN as well. Both may make the drive report a new medium, and the new session started by check_tape() clears the "changed" flags and applies the mode defaults. The values are therefore saved before the operation and re-applied after check_tape() has run. scsi_debug reports a new medium after MTRETEN and MTLOAD; the IBM drives do not. The series was tested against both behaviours. 2. Tape state is not reset after MTLOAD of an already loaded medium A LOAD positions the medium at the beginning of partition 0. check_tape() records that only when it starts a new session, which requires a new-medium unit attention. Drives that do not report one for an already loaded cartridge (IBM LTO) are left with the state from before the load: - After a reset, MTIOCGET keeps reporting file/block -1 and no BOT, although commit0b120edb37("scsi: st: Add MTIOCGET and MTLOAD to ioctls allowed after device reset") allows MTLOAD because the tape location is known after it. - After reading to EOD, a read following MTLOAD fails with EIO. - If partition 1 was selected, st still records partition 1, so a later MTSETPART 1 is not performed and I/O goes to partition 0. Patch 2 resets the partition and the state of all partitions after a successful load, as the new-session path in check_tape() does: partition 0, position at BOT, EOF state cleared and no pending write. Resetting all partitions matters: a partition switch done by read() or write() (in rw_checks()) leaves the rw state of the partition that is left, and a stale ST_WRITING state would make st_flush() write a filemark at the beginning of partition 0 when the device is closed. 3. The drive buffering mode is lost after a reset A reset also returns the drive's buffered mode to its default, and check_tape() reads it back on every open, so a mode set with MTSETDRVBUFFER is lost - with every recovery operation, including MTREW. Unlike density and block size there was no "changed" state to restore it from. Patch 3 records the value set with MTSETDRVBUFFER and restores it in both restore paths, before density and block size. 4. The door is not locked again after a reset With auto-lock, st locks the door at the first read or write. A reset clears the drive's medium removal prevention, but st kept its locked state and never locked the door again. Patch 4 marks the door unlocked when a reset is recognized, so the next access locks it. MTOFFL is deliberately not changed: the medium is unloaded, and a different one may be loaded next, for which the mode defaults should apply. Immediate mode: with MT_ST_NOWAIT, MTRETEN returns before the retension completes, so patch 1 does not restore the settings there rather than waiting for the drive. After MTLOAD and MTRETEN the settings are only restored when the drive is ready: with an immediate-mode load and O_NONBLOCK, check_tape() does not wait and the drive may still be loading. The SCSI tape driver is currently marked Orphan in MAINTAINERS. Testing (v7.3-rc2, scsi_debug and IBM LTO drives on FC): Block size after reset + recovery (patch 1): R04.load R04.retension R04.offline unpatched: scsi_debug discarded discarded discarded IBM ULTRIUM-TD5 (LTO-5) discarded discarded discarded IBM ULT3580-TDA discarded discarded discarded patched: scsi_debug restored restored discarded (*) IBM ULTRIUM-TD5 (LTO-5) restored restored discarded (*) IBM ULT3580-TDA restored restored discarded (*) (*) unchanged by design, see above. State after MTLOAD (patch 2): R03.load: position after reset + MTLOAD IBM LTO-5 and ULT3580-TDA unpatched: MTIOCGET -1:-1 v3: ok (0:0) scsi_debug unpatched and v3: ok (new session) B09: read after MTLOAD at EOD, no reset IBM ULT3580-TDA v1: read() fails with EIO v2, v3: ok scsi_debug v1, v2, v3: ok (new session) X04: MTLOAD while in partition 1, then MTSETPART 1 and read IBM ULTRIUM-TD5 (LTO-5) v2: MTIOCGET still reports partition 1, MTSETPART 1 not performed, read in partition 0 fails with EIO v3: ok scsi_debug v2, v3, v4: ok (new session) X05: one open file: write in partition 0, MTSETPART 1, read() (switches in rw_checks() without resetting partition 0's state), MTLOAD, close; then read partition 0 IBM ULTRIUM-TD5 (LTO-5) v3: close wrote a filemark at BOT of partition 0, the file there is lost v4: ok, partition 0 intact scsi_debug v4: ok Drive buffering mode after reset (patch 3), read from the drive with MODE SENSE through the sg node, mode 0 set with MTSETDRVBUFFER: R15: reset + MTREW / MTLOAD / MTRETEN IBM ULTRIUM-TD5 (LTO-5) patches 1-2: drive back in mode 1 v3: mode 0 restored scsi_debug does not implement buffered mode Door lock after reset (patch 4), auto-lock on, same open file: R16: read (locks), reset, recover, read IBM ULTRIUM-TD5 (LTO-5) patches 1-2: door not locked again v3: locked again scsi_debug v3: locked again For the position tests, "ok" means that MTIOCPOS returns 0 and the first block read without repositioning is block 0 of file 0; it is not based on MTIOCGET alone. The restore after MTREW/MTSEEK/MTEOM from7081dc75dfis unaffected on all devices. Every test ends with a byte-exact read-back of the whole tape. The tests are part of a tape validation harness (reset detection, the blocked-operation matrix, recovery operations, interrupted I/O, SCSI EH via scsi_debug error injection); B09, X04, X05, R15 and R16 were added for the review comments on v1 to v3: https://gitlab.com/loberman/tapetest (developed with AI assistance) [mkp: fix merge conflict] Link: https://patch.msgid.link/20260929192821.997675-1-loberman@redhat.com Signed-off-by: Martin K. Petersen (Oracle) <mkp@kernel.org>
This commit is contained in:
commit
bdb20c40e2
|
|
@ -368,6 +368,8 @@ static int st_chk_result(struct scsi_tape *STp, struct st_request * SRpnt)
|
|||
if (ctr != STp->por_ctr) {
|
||||
STp->por_ctr = ctr;
|
||||
STp->pos_unknown = 1; /* ASC => power on / reset */
|
||||
/* The reset allowed medium removal; relock at next access */
|
||||
STp->door_locked = ST_UNLOCKED;
|
||||
st_printk(KERN_WARNING, STp, "Power on/reset recognized.");
|
||||
}
|
||||
|
||||
|
|
@ -428,6 +430,7 @@ static int st_chk_result(struct scsi_tape *STp, struct st_request * SRpnt)
|
|||
ASC_POWER_ON_RESET_OR_BUS_DEVICE_RESET_OCCURRED &&
|
||||
!STp->pos_unknown) {
|
||||
STp->pos_unknown = 1;
|
||||
STp->door_locked = ST_UNLOCKED;
|
||||
st_printk(KERN_WARNING, STp, "Power on/reset recognized.");
|
||||
}
|
||||
|
||||
|
|
@ -2688,6 +2691,30 @@ static int do_load_unload(struct scsi_tape *STp, struct file *filp, int load_cod
|
|||
else {
|
||||
STp->rew_at_close = STp->autorew_dev;
|
||||
retval = check_tape(STp, filp);
|
||||
/*
|
||||
* LOAD leaves the medium at the beginning of partition
|
||||
* 0. check_tape() records that only for a new session;
|
||||
* without a new-medium unit attention (the medium was
|
||||
* already loaded) the partition and the partition state
|
||||
* would be left as before the load. Reset them as a
|
||||
* new session does, for all partitions: a stale
|
||||
* ST_WRITING state would make st_flush() write a
|
||||
* filemark at the beginning of partition 0.
|
||||
*/
|
||||
if (retval == CHKRES_READY) {
|
||||
int i;
|
||||
|
||||
STp->partition = STp->new_partition = 0;
|
||||
for (i = 0; i < ST_NBR_PARTITIONS; i++) {
|
||||
STps = &(STp->ps[i]);
|
||||
STps->rw = ST_IDLE;
|
||||
STps->eof = ST_NOEOF;
|
||||
STps->at_sm = 0;
|
||||
STps->last_block_valid = 0;
|
||||
STps->drv_block = 0;
|
||||
STps->drv_file = 0;
|
||||
}
|
||||
}
|
||||
if (retval > 0)
|
||||
retval = 0;
|
||||
}
|
||||
|
|
@ -2941,9 +2968,11 @@ static int st_int_ioctl(struct scsi_tape *STp, unsigned int cmd_in, unsigned lon
|
|||
direction = DMA_TO_DEVICE;
|
||||
|
||||
memset((STp->buffer)->b_data, 0, 12);
|
||||
if (cmd_in == MTSETDRVBUFFER)
|
||||
if (cmd_in == MTSETDRVBUFFER) {
|
||||
(STp->buffer)->b_data[2] = (arg & 7) << 4;
|
||||
else
|
||||
STp->drv_buffer_changed = 1; /* At least we tried ;-) */
|
||||
STp->changed_drv_buffer = arg & 7;
|
||||
} else
|
||||
(STp->buffer)->b_data[2] =
|
||||
STp->drv_buffer << 4;
|
||||
(STp->buffer)->b_data[3] = 8; /* block descriptor length */
|
||||
|
|
@ -3589,6 +3618,26 @@ static long st_common_ioctl(struct scsi_tape *STp, struct st_modedef *STm,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-apply a drive buffering mode, density and block size that were changed
|
||||
* by the user before a device reset (a negative value means "not changed").
|
||||
* The buffering mode goes first: the other MODE SELECTs send the current
|
||||
* STp->drv_buffer. A unit attention still pending after the operation
|
||||
* (e.g. new medium after a load) fails the first MODE SELECT, so retry each
|
||||
* once. As in the other post-reset restore path, errors are ignored and one
|
||||
* setting failing does not prevent restoring the others.
|
||||
*/
|
||||
static void st_restore_changed_settings(struct scsi_tape *STp, int drv_buffer,
|
||||
int density, int blksize)
|
||||
{
|
||||
if (drv_buffer >= 0 && st_int_ioctl(STp, MTSETDRVBUFFER, drv_buffer))
|
||||
st_int_ioctl(STp, MTSETDRVBUFFER, drv_buffer);
|
||||
if (density >= 0 && st_int_ioctl(STp, MTSETDENSITY, density))
|
||||
st_int_ioctl(STp, MTSETDENSITY, density);
|
||||
if (blksize >= 0 && st_int_ioctl(STp, MTSETBLK, blksize))
|
||||
st_int_ioctl(STp, MTSETBLK, blksize);
|
||||
}
|
||||
|
||||
/* The ioctl command */
|
||||
static long st_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg)
|
||||
{
|
||||
|
|
@ -3597,6 +3646,7 @@ static long st_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg)
|
|||
int retval = 0;
|
||||
unsigned int blk;
|
||||
bool cmd_mtiocget;
|
||||
int restore_drv_buffer = -1, restore_density = -1, restore_blksize = -1;
|
||||
struct scsi_tape *STp = file->private_data;
|
||||
struct st_modedef *STm;
|
||||
struct st_partstat *STps;
|
||||
|
|
@ -3739,10 +3789,26 @@ static long st_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg)
|
|||
STp->partition = 0;
|
||||
switch_partition(STp);
|
||||
}
|
||||
if (STp->drv_buffer_changed)
|
||||
st_int_ioctl(STp, MTSETDRVBUFFER,
|
||||
STp->changed_drv_buffer);
|
||||
if (STp->density_changed)
|
||||
st_int_ioctl(STp, MTSETDENSITY, STp->changed_density);
|
||||
if (STp->blksize_changed)
|
||||
st_int_ioctl(STp, MTSETBLK, STp->changed_blksize);
|
||||
} else if (mtc.mt_op == MTLOAD || mtc.mt_op == MTRETEN) {
|
||||
/*
|
||||
* The same medium ends up at BOT, so the settings
|
||||
* apply as with MTREW. The operation may start a
|
||||
* new session, which clears the "changed" flags:
|
||||
* save the values and restore them afterwards.
|
||||
*/
|
||||
if (STp->drv_buffer_changed)
|
||||
restore_drv_buffer = STp->changed_drv_buffer;
|
||||
if (STp->density_changed)
|
||||
restore_density = STp->changed_density;
|
||||
if (STp->blksize_changed)
|
||||
restore_blksize = STp->changed_blksize;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3822,6 +3888,15 @@ static long st_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg)
|
|||
|
||||
if (mtc.mt_op == MTLOAD) {
|
||||
retval = do_load_unload(STp, file, max(1, mtc.mt_count));
|
||||
/*
|
||||
* Restore only if the drive is ready: after an
|
||||
* immediate-mode load with O_NONBLOCK, check_tape()
|
||||
* does not wait and the drive may still be loading.
|
||||
*/
|
||||
if (!retval && STp->ready == ST_READY)
|
||||
st_restore_changed_settings(STp, restore_drv_buffer,
|
||||
restore_density,
|
||||
restore_blksize);
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
|
@ -3840,6 +3915,22 @@ static long st_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg)
|
|||
retval = st_compression(STp, (mtc.mt_count & 1));
|
||||
else
|
||||
retval = st_int_ioctl(STp, mtc.mt_op, mtc.mt_count);
|
||||
if (!retval && mtc.mt_op == MTRETEN && !STp->immediate &&
|
||||
(restore_drv_buffer >= 0 || restore_density >= 0 ||
|
||||
restore_blksize >= 0)) {
|
||||
/*
|
||||
* Retension reloads the medium and the drive may
|
||||
* report a new medium. Let check_tape() start the new
|
||||
* session (applying the mode defaults) now, as for
|
||||
* MTLOAD; otherwise it happens at the next open and
|
||||
* overrides the restored settings.
|
||||
*/
|
||||
if (check_tape(STp, file) >= 0 &&
|
||||
STp->ready == ST_READY)
|
||||
st_restore_changed_settings(STp, restore_drv_buffer,
|
||||
restore_density,
|
||||
restore_blksize);
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
if (!STm->defined) {
|
||||
|
|
@ -4445,7 +4536,7 @@ static int st_probe(struct scsi_device *SDp)
|
|||
tpnt->modes[0].defined = 1;
|
||||
|
||||
tpnt->density_changed = tpnt->compression_changed =
|
||||
tpnt->blksize_changed = 0;
|
||||
tpnt->blksize_changed = tpnt->drv_buffer_changed = 0;
|
||||
mutex_init(&tpnt->lock);
|
||||
|
||||
idr_preload(GFP_KERNEL);
|
||||
|
|
|
|||
|
|
@ -162,10 +162,12 @@ struct scsi_tape {
|
|||
unsigned char in_use;
|
||||
unsigned char blksize_changed;
|
||||
unsigned char density_changed;
|
||||
unsigned char drv_buffer_changed;
|
||||
unsigned char compression_changed;
|
||||
unsigned char drv_buffer;
|
||||
unsigned char density;
|
||||
unsigned char changed_density;
|
||||
unsigned char changed_drv_buffer;
|
||||
unsigned char door_locked;
|
||||
unsigned char autorew_dev; /* auto-rewind device */
|
||||
unsigned char rew_at_close; /* rewind necessary at close */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user