Merge branch 'tape-block-sizes'

Jan Höppner says:

====================

The tape device driver is limited to a block size of 65535 bytes since a
single CCW can only transfer up to 64K-1 bytes (The count field is a
16bit value). This series introduces data chaining for all read/write
functions to support block sizes larger than 65535.

The tape device type 3490 (emulated) and 3590/3592 can handle up to
256K. [1]

[1] https://www.ibm.com/docs/en/zos/3.1.0?topic=blksize-system-determined-block-size

====================

Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
This commit is contained in:
Heiko Carstens 2025-10-21 10:26:26 +02:00
commit 5e09c0a03e
9 changed files with 214 additions and 237 deletions

View File

@ -18,6 +18,8 @@
#include <asm/scsw.h>
#define CCW_MAX_BYTE_COUNT 65535
/**
* struct ccw1 - channel command word
* @cmd_code: command code

View File

@ -180,6 +180,82 @@ static inline void idal_buffer_free(struct idal_buffer *ib)
kfree(ib);
}
/*
* Allocate an array of IDAL buffers to cover a total data size of @size. The
* resulting array is null-terminated.
*
* The amount of individual IDAL buffers is determined based on @size.
* Each IDAL buffer can have a maximum size of @CCW_MAX_BYTE_COUNT.
*/
static inline struct idal_buffer **idal_buffer_array_alloc(size_t size, int page_order)
{
struct idal_buffer **ibs;
size_t ib_size; /* Size of a single idal buffer */
int count; /* Amount of individual idal buffers */
int i;
count = (size + CCW_MAX_BYTE_COUNT - 1) / CCW_MAX_BYTE_COUNT;
ibs = kmalloc_array(count + 1, sizeof(*ibs), GFP_KERNEL);
for (i = 0; i < count; i++) {
/* Determine size for the current idal buffer */
ib_size = min(size, CCW_MAX_BYTE_COUNT);
size -= ib_size;
ibs[i] = idal_buffer_alloc(ib_size, page_order);
if (IS_ERR(ibs[i])) {
while (i--)
idal_buffer_free(ibs[i]);
kfree(ibs);
ibs = NULL;
return ERR_PTR(-ENOMEM);
}
}
ibs[i] = NULL;
return ibs;
}
/*
* Free array of IDAL buffers
*/
static inline void idal_buffer_array_free(struct idal_buffer ***ibs)
{
struct idal_buffer **p;
if (!ibs || !*ibs)
return;
for (p = *ibs; *p; p++)
idal_buffer_free(*p);
kfree(*ibs);
*ibs = NULL;
}
/*
* Determine size of IDAL buffer array
*/
static inline int idal_buffer_array_size(struct idal_buffer **ibs)
{
int size = 0;
while (ibs && *ibs) {
size++;
ibs++;
}
return size;
}
/*
* Determine total data size covered by IDAL buffer array
*/
static inline size_t idal_buffer_array_datasize(struct idal_buffer **ibs)
{
size_t size = 0;
while (ibs && *ibs) {
size += (*ibs)->size;
ibs++;
}
return size;
}
/*
* Test if a idal list is really needed.
*/

View File

@ -130,6 +130,7 @@ struct tape_request {
int retries; /* retry counter for error recovery. */
int rescnt; /* residual count from devstat. */
struct timer_list timer; /* timer for std_assign_timeout(). */
struct irb irb; /* device status */
/* Callback for delivering final status. */
void (*callback)(struct tape_request *, void *);
@ -151,8 +152,8 @@ struct tape_discipline {
int (*setup_device)(struct tape_device *);
void (*cleanup_device)(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);
struct tape_request *(*read_block)(struct tape_device *);
struct tape_request *(*write_block)(struct tape_device *);
void (*process_eov)(struct tape_device*);
/* ioctl function for additional ioctls. */
int (*ioctl_fn)(struct tape_device *, unsigned int, unsigned long);
@ -172,7 +173,7 @@ struct tape_discipline {
/* Char Frontend Data */
struct tape_char_data {
struct idal_buffer *idal_buf; /* idal buffer for user char data */
struct idal_buffer **ibs; /* idal buffer array for user char data */
int block_size; /* of size block_size. */
};
@ -234,6 +235,7 @@ struct tape_device {
/* Externals from tape_core.c */
extern struct tape_request *tape_alloc_request(int cplength, int datasize);
extern void tape_free_request(struct tape_request *);
extern int tape_check_idalbuffer(struct tape_device *device, size_t size);
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 *);
@ -346,13 +348,22 @@ tape_ccw_repeat(struct ccw1 *ccw, __u8 cmd_code, int count)
return ccw;
}
static inline struct ccw1 *
tape_ccw_dc_idal(struct ccw1 *ccw, __u8 cmd_code, struct idal_buffer *idal)
{
ccw->cmd_code = cmd_code;
ccw->flags = CCW_FLAG_DC;
idal_buffer_set_cda(idal, ccw);
return ccw + 1;
}
static inline struct ccw1 *
tape_ccw_cc_idal(struct ccw1 *ccw, __u8 cmd_code, struct idal_buffer *idal)
{
ccw->cmd_code = cmd_code;
ccw->flags = CCW_FLAG_CC;
idal_buffer_set_cda(idal, ccw);
return ccw++;
return ccw + 1;
}
static inline struct ccw1 *
@ -361,7 +372,7 @@ tape_ccw_end_idal(struct ccw1 *ccw, __u8 cmd_code, struct idal_buffer *idal)
ccw->cmd_code = cmd_code;
ccw->flags = 0;
idal_buffer_set_cda(idal, ccw);
return ccw++;
return ccw + 1;
}
/* Global vars */

View File

@ -234,31 +234,6 @@ tape_34xx_unsolicited_irq(struct tape_device *device, struct irb *irb)
return TAPE_IO_SUCCESS;
}
/*
* Read Opposite Error Recovery Function:
* Used, when Read Forward does not work
*/
static int
tape_34xx_erp_read_opposite(struct tape_device *device,
struct tape_request *request)
{
if (request->op == TO_RFO) {
/*
* We did read forward, but the data could not be read
* *correctly*. We transform the request to a read backward
* and try again.
*/
tape_std_read_backward(device, request);
return tape_34xx_erp_retry(request);
}
/*
* We tried to read forward and backward, but hat no
* success -> failed.
*/
return tape_34xx_erp_failed(request, -EIO);
}
static int
tape_34xx_erp_bug(struct tape_device *device, struct tape_request *request,
struct irb *irb, int no)
@ -440,9 +415,6 @@ tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
dev_warn (&device->cdev->dev, "A write error on the "
"tape cannot be recovered\n");
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 */
dev_warn (&device->cdev->dev, "Writing the ID-mark "

View File

@ -550,31 +550,6 @@ tape_3590_mtseek(struct tape_device *device, int count)
return tape_do_io_free(device, request);
}
/*
* Read Opposite Error Recovery Function:
* Used, when Read Forward does not work
*/
static void
tape_3590_read_opposite(struct tape_device *device,
struct tape_request *request)
{
struct tape_3590_disc_data *data;
/*
* We have allocated 4 ccws in tape_std_read, so we can now
* transform the request to a read backward, followed by a
* forward space block.
*/
request->op = TO_RBA;
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
data = device->discdata;
tape_ccw_cc_idal(request->cpaddr + 1, data->read_back_op,
device->char_data.idal_buf);
tape_ccw_cc(request->cpaddr + 2, FORSPACEBLOCK, 0, NULL);
tape_ccw_end(request->cpaddr + 3, NOP, 0, NULL);
DBF_EVENT(6, "xrop ccwg\n");
}
/*
* Read Attention Msg
* This should be done after an interrupt with attention bit (0x80)
@ -896,60 +871,6 @@ tape_3590_erp_special_interrupt(struct tape_device *device,
return tape_3590_erp_basic(device, request, irb, -EIO);
}
/*
* RDA: Read Alternate
*/
static int
tape_3590_erp_read_alternate(struct tape_device *device,
struct tape_request *request, struct irb *irb)
{
struct tape_3590_disc_data *data;
/*
* The issued Read Backward or Read Previous command is not
* supported by the device
* The recovery action should be to issue another command:
* Read Revious: if Read Backward is not supported
* Read Backward: if Read Previous is not supported
*/
data = device->discdata;
if (data->read_back_op == READ_PREVIOUS) {
DBF_EVENT(2, "(%08x): No support for READ_PREVIOUS command\n",
device->cdev_id);
data->read_back_op = READ_BACKWARD;
} else {
DBF_EVENT(2, "(%08x): No support for READ_BACKWARD command\n",
device->cdev_id);
data->read_back_op = READ_PREVIOUS;
}
tape_3590_read_opposite(device, request);
return tape_3590_erp_retry(device, request, irb);
}
/*
* Error Recovery read opposite
*/
static int
tape_3590_erp_read_opposite(struct tape_device *device,
struct tape_request *request, struct irb *irb)
{
switch (request->op) {
case TO_RFO:
/*
* We did read forward, but the data could not be read.
* We will read backward and then skip forward again.
*/
tape_3590_read_opposite(device, request);
return tape_3590_erp_retry(device, request, irb);
case TO_RBA:
/* We tried to read forward and backward, but hat no success */
return tape_3590_erp_failed(device, request, irb, -EIO);
break;
default:
return tape_3590_erp_failed(device, request, irb, -EIO);
}
}
/*
* Print an MIM (Media Information Message) (message code f0)
*/
@ -1348,10 +1269,6 @@ tape_3590_unit_check(struct tape_device *device, struct tape_request *request,
tape_3590_print_era_msg(device, irb);
return tape_3590_erp_read_buf_log(device, request, irb);
case 0x2011:
tape_3590_print_era_msg(device, irb);
return tape_3590_erp_read_alternate(device, request, irb);
case 0x2230:
case 0x2231:
tape_3590_print_era_msg(device, irb);
@ -1405,12 +1322,6 @@ tape_3590_unit_check(struct tape_device *device, struct tape_request *request,
tape_3590_print_era_msg(device, irb);
return tape_3590_erp_swap(device, request, irb);
}
if (sense->rac == 0x26) {
/* Read Opposite */
tape_3590_print_era_msg(device, irb);
return tape_3590_erp_read_opposite(device, request,
irb);
}
return tape_3590_erp_basic(device, request, irb, -EIO);
case 0x5020:
case 0x5021:

View File

@ -93,33 +93,6 @@ tapechar_cleanup_device(struct tape_device *device)
device->nt = NULL;
}
static int
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)
return 0;
if (block_size > MAX_BLOCKSIZE) {
DBF_EVENT(3, "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 (IS_ERR(new))
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;
}
/*
* Tape device read function
@ -127,9 +100,12 @@ tapechar_check_idalbuffer(struct tape_device *device, size_t block_size)
static ssize_t
tapechar_read(struct file *filp, char __user *data, size_t count, loff_t *ppos)
{
struct tape_device *device;
struct tape_request *request;
struct ccw1 *ccw, *last_ccw;
struct tape_device *device;
struct idal_buffer **ibs;
size_t block_size;
size_t read = 0;
int rc;
DBF_EVENT(6, "TCHAR:read\n");
@ -156,24 +132,37 @@ tapechar_read(struct file *filp, char __user *data, size_t count, loff_t *ppos)
block_size = count;
}
rc = tapechar_check_idalbuffer(device, block_size);
rc = tape_check_idalbuffer(device, block_size);
if (rc)
return rc;
DBF_EVENT(6, "TCHAR:nbytes: %lx\n", block_size);
/* Let the discipline build the ccw chain. */
request = device->discipline->read_block(device, block_size);
request = device->discipline->read_block(device);
if (IS_ERR(request))
return PTR_ERR(request);
/* Execute it. */
rc = tape_do_io(device, request);
if (rc == 0) {
rc = block_size - request->rescnt;
DBF_EVENT(6, "TCHAR:rbytes: %x\n", rc);
/* Copy data from idal buffer to user space. */
if (idal_buffer_to_user(device->char_data.idal_buf,
data, rc) != 0)
rc = -EFAULT;
/* Channel Program Address (cpa) points to last CCW + 8 */
last_ccw = dma32_to_virt(request->irb.scsw.cmd.cpa);
ccw = request->cpaddr;
ibs = device->char_data.ibs;
while (++ccw < last_ccw) {
/* Copy data from idal buffer to user space. */
if (idal_buffer_to_user(*ibs++, data, ccw->count) != 0) {
rc = -EFAULT;
break;
}
read += ccw->count;
data += ccw->count;
}
if (&last_ccw[-1] == &request->cpaddr[1] &&
request->rescnt == last_ccw[-1].count)
rc = 0;
else
rc = read - request->rescnt;
}
tape_free_request(request);
return rc;
@ -185,10 +174,12 @@ tapechar_read(struct file *filp, char __user *data, size_t count, loff_t *ppos)
static ssize_t
tapechar_write(struct file *filp, const char __user *data, size_t count, loff_t *ppos)
{
struct tape_device *device;
struct tape_request *request;
struct ccw1 *ccw, *last_ccw;
struct tape_device *device;
struct idal_buffer **ibs;
size_t written = 0;
size_t block_size;
size_t written;
int nblocks;
int i, rc;
@ -208,35 +199,45 @@ tapechar_write(struct file *filp, const char __user *data, size_t count, loff_t
nblocks = 1;
}
rc = tapechar_check_idalbuffer(device, block_size);
rc = tape_check_idalbuffer(device, block_size);
if (rc)
return rc;
DBF_EVENT(6,"TCHAR:nbytes: %lx\n", block_size);
DBF_EVENT(6, "TCHAR:nbytes: %lx\n", block_size);
DBF_EVENT(6, "TCHAR:nblocks: %x\n", nblocks);
/* Let the discipline build the ccw chain. */
request = device->discipline->write_block(device, block_size);
request = device->discipline->write_block(device);
if (IS_ERR(request))
return PTR_ERR(request);
rc = 0;
written = 0;
for (i = 0; i < nblocks; i++) {
/* Copy data from user space to idal buffer. */
if (idal_buffer_from_user(device->char_data.idal_buf,
data, block_size)) {
rc = -EFAULT;
break;
size_t wbytes = 0; /* Used to trace written data in dbf */
ibs = device->char_data.ibs;
while (ibs && *ibs) {
if (idal_buffer_from_user(*ibs, data, (*ibs)->size)) {
rc = -EFAULT;
goto out;
}
data += (*ibs)->size;
ibs++;
}
rc = tape_do_io(device, request);
if (rc)
break;
DBF_EVENT(6, "TCHAR:wbytes: %lx\n",
block_size - request->rescnt);
written += block_size - request->rescnt;
goto out;
/* Channel Program Address (cpa) points to last CCW + 8 */
last_ccw = dma32_to_virt(request->irb.scsw.cmd.cpa);
ccw = request->cpaddr;
while (++ccw < last_ccw)
wbytes += ccw->count;
DBF_EVENT(6, "TCHAR:wbytes: %lx\n", wbytes - request->rescnt);
written += wbytes - request->rescnt;
if (request->rescnt != 0)
break;
data += block_size;
}
out:
tape_free_request(request);
if (rc == -ENOSPC) {
/*
@ -324,10 +325,8 @@ tapechar_release(struct inode *inode, struct file *filp)
}
}
if (device->char_data.idal_buf != NULL) {
idal_buffer_free(device->char_data.idal_buf);
device->char_data.idal_buf = NULL;
}
if (device->char_data.ibs)
idal_buffer_array_free(&device->char_data.ibs);
tape_release(device);
filp->private_data = NULL;
tape_put_device(device);

View File

@ -726,6 +726,36 @@ tape_free_request (struct tape_request * request)
kfree(request);
}
int
tape_check_idalbuffer(struct tape_device *device, size_t size)
{
struct idal_buffer **new;
size_t old_size = 0;
old_size = idal_buffer_array_datasize(device->char_data.ibs);
if (old_size == size)
return 0;
if (size > MAX_BLOCKSIZE) {
DBF_EVENT(3, "Invalid blocksize (%zd > %d)\n",
size, MAX_BLOCKSIZE);
return -EINVAL;
}
/* The current idal buffer is not correct. Allocate a new one. */
new = idal_buffer_array_alloc(size, 0);
if (IS_ERR(new))
return -ENOMEM;
/* Free old idal buffer array */
if (device->char_data.ibs)
idal_buffer_array_free(&device->char_data.ibs);
device->char_data.ibs = new;
return 0;
}
static int
__tape_start_io(struct tape_device *device, struct tape_request *request)
{
@ -1099,9 +1129,10 @@ __tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
}
/* May be an unsolicited irq */
if(request != NULL)
if (request != NULL) {
request->rescnt = irb->scsw.cmd.count;
else if ((irb->scsw.cmd.dstat == 0x85 || irb->scsw.cmd.dstat == 0x80) &&
memcpy(&request->irb, irb, sizeof(*irb));
} else if ((irb->scsw.cmd.dstat == 0x85 || irb->scsw.cmd.dstat == 0x80) &&
!list_empty(&device->req_queue)) {
/* Not Ready to Ready after long busy ? */
struct tape_request *req;

View File

@ -212,7 +212,7 @@ tape_std_mtload(struct tape_device *device, int count)
int
tape_std_mtsetblk(struct tape_device *device, int count)
{
struct idal_buffer *new;
int rc;
DBF_LH(6, "tape_std_mtsetblk(%d)\n", count);
if (count <= 0) {
@ -224,26 +224,12 @@ tape_std_mtsetblk(struct tape_device *device, int count)
device->char_data.block_size = 0;
return 0;
}
if (device->char_data.idal_buf != NULL &&
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);
return -EINVAL;
}
rc = tape_check_idalbuffer(device, count);
if (rc)
return rc;
/* Allocate a new idal buffer. */
new = idal_buffer_alloc(count, 0);
if (IS_ERR(new))
return -ENOMEM;
if (device->char_data.idal_buf != NULL)
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;
@ -641,63 +627,54 @@ tape_std_mtcompression(struct tape_device *device, int mt_count)
* Read Block
*/
struct tape_request *
tape_std_read_block(struct tape_device *device, size_t count)
tape_std_read_block(struct tape_device *device)
{
struct tape_request *request;
struct idal_buffer **ibs;
struct ccw1 *ccw;
size_t count;
/*
* We have to alloc 4 ccws in order to be able to transform request
* into a read backward request in error case.
*/
request = tape_alloc_request(4, 0);
ibs = device->char_data.ibs;
count = idal_buffer_array_size(ibs);
request = tape_alloc_request(count + 1 /* MODE_SET_DB */, 0);
if (IS_ERR(request)) {
DBF_EXCEPTION(6, "xrbl fail");
return request;
}
request->op = TO_RFO;
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_end_idal(request->cpaddr + 1, READ_FORWARD,
device->char_data.idal_buf);
ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
while (count-- > 1)
ccw = tape_ccw_dc_idal(ccw, READ_FORWARD, *ibs++);
tape_ccw_end_idal(ccw, READ_FORWARD, *ibs);
DBF_EVENT(6, "xrbl ccwg\n");
return request;
}
/*
* Read Block backward transformation function.
*/
void
tape_std_read_backward(struct tape_device *device, struct tape_request *request)
{
/*
* We have allocated 4 ccws in tape_std_read, so we can now
* transform the request to a read backward, followed by a
* forward space block.
*/
request->op = TO_RBA;
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_cc_idal(request->cpaddr + 1, READ_BACKWARD,
device->char_data.idal_buf);
tape_ccw_cc(request->cpaddr + 2, FORSPACEBLOCK, 0, NULL);
tape_ccw_end(request->cpaddr + 3, NOP, 0, NULL);
DBF_EVENT(6, "xrop ccwg");}
/*
* Write Block
*/
struct tape_request *
tape_std_write_block(struct tape_device *device, size_t count)
tape_std_write_block(struct tape_device *device)
{
struct tape_request *request;
struct idal_buffer **ibs;
struct ccw1 *ccw;
size_t count;
request = tape_alloc_request(2, 0);
count = idal_buffer_array_size(device->char_data.ibs);
request = tape_alloc_request(count + 1 /* MODE_SET_DB */, 0);
if (IS_ERR(request)) {
DBF_EXCEPTION(6, "xwbl fail\n");
return request;
}
request->op = TO_WRI;
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_end_idal(request->cpaddr + 1, WRITE_CMD,
device->char_data.idal_buf);
ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
ibs = device->char_data.ibs;
while (count-- > 1)
ccw = tape_ccw_dc_idal(ccw, WRITE_CMD, *ibs++);
tape_ccw_end_idal(ccw, WRITE_CMD, *ibs);
DBF_EVENT(6, "xwbl ccwg\n");
return request;
}
@ -741,6 +718,5 @@ EXPORT_SYMBOL(tape_std_mterase);
EXPORT_SYMBOL(tape_std_mtunload);
EXPORT_SYMBOL(tape_std_mtcompression);
EXPORT_SYMBOL(tape_std_read_block);
EXPORT_SYMBOL(tape_std_read_backward);
EXPORT_SYMBOL(tape_std_write_block);
EXPORT_SYMBOL(tape_std_process_eov);

View File

@ -14,10 +14,9 @@
#include <asm/tape390.h>
/*
* Biggest block size to handle. Currently 64K because we only build
* channel programs without data chaining.
* Biggest block size of 256K to handle.
*/
#define MAX_BLOCKSIZE 65535
#define MAX_BLOCKSIZE 262144
/*
* The CCW commands for the Tape type of command.
@ -97,10 +96,10 @@
#define SENSE_TAPE_POSITIONING 0x01
/* discipline functions */
struct tape_request *tape_std_read_block(struct tape_device *, size_t);
struct tape_request *tape_std_read_block(struct tape_device *);
void tape_std_read_backward(struct tape_device *device,
struct tape_request *request);
struct tape_request *tape_std_write_block(struct tape_device *, size_t);
struct tape_request *tape_std_write_block(struct tape_device *);
/* Some non-mtop commands. */
int tape_std_assign(struct tape_device *);