s390/tape: Introduce idal buffer array

The tape device driver uses a single idal_buffer for I/O. While the
buffer itself can be arbitrary big, the limit for data transfer for a
single Channel-Command Word is at 65535 bytes (64K-1) since the count
field specifying the amount of data designated by the CCW is a 16-bit
unsigned value.

Provide functionality that allocates an array of multiple IDAL buffer
with the limitation mentioned above in mind.
A call to idal_buffer_array_alloc() allocates an array with a certain
amount of IDAL buffers which is determined based on the total size of
@size. Each individual buffer is limited to a size of CCW_MAX_BYTE_COUNT
(65535 bytes).

Add helper functions that determine the size (# of elements) and the
total data size covered by the array as well.

Current users of the single IDAL buffer are adapted to use the new
functions with one buffer to allocate.

The single IDAL buffer is removed from the tape_char_data struct.

Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: Jens Remus <jremus@linux.ibm.com>
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
This commit is contained in:
Jan Höppner 2025-10-16 09:47:17 +02:00 committed by Heiko Carstens
parent a5e2ca22c1
commit 574817d6c0
6 changed files with 94 additions and 16 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

@ -172,7 +172,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. */
};

View File

@ -144,7 +144,7 @@ tapechar_read(struct file *filp, char __user *data, size_t count, loff_t *ppos)
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,
if (idal_buffer_to_user(*device->char_data.ibs,
data, rc) != 0)
rc = -EFAULT;
}
@ -195,7 +195,7 @@ tapechar_write(struct file *filp, const char __user *data, size_t count, loff_t
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,
if (idal_buffer_from_user(*device->char_data.ibs,
data, block_size)) {
rc = -EFAULT;
break;
@ -297,10 +297,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

@ -729,10 +729,11 @@ tape_free_request (struct tape_request * request)
int
tape_check_idalbuffer(struct tape_device *device, size_t size)
{
struct idal_buffer *new;
struct idal_buffer **new;
size_t old_size = 0;
if (device->char_data.idal_buf != NULL &&
device->char_data.idal_buf->size == size)
old_size = idal_buffer_array_datasize(device->char_data.ibs);
if (old_size == size)
return 0;
if (size > MAX_BLOCKSIZE) {
@ -742,14 +743,15 @@ tape_check_idalbuffer(struct tape_device *device, size_t size)
}
/* The current idal buffer is not correct. Allocate a new one. */
new = idal_buffer_alloc(size, 0);
new = idal_buffer_array_alloc(size, 0);
if (IS_ERR(new))
return -ENOMEM;
if (device->char_data.idal_buf != NULL)
idal_buffer_free(device->char_data.idal_buf);
/* Free old idal buffer array */
if (device->char_data.ibs)
idal_buffer_array_free(&device->char_data.ibs);
device->char_data.idal_buf = new;
device->char_data.ibs = new;
return 0;
}

View File

@ -639,7 +639,7 @@ tape_std_read_block(struct tape_device *device)
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);
*device->char_data.ibs);
DBF_EVENT(6, "xrbl ccwg\n");
return request;
}
@ -660,7 +660,7 @@ tape_std_write_block(struct tape_device *device)
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);
*device->char_data.ibs);
DBF_EVENT(6, "xwbl ccwg\n");
return request;
}