From 574817d6c0866e80e8f09b8537476685631fc992 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jan=20H=C3=B6ppner?= Date: Thu, 16 Oct 2025 09:47:17 +0200 Subject: [PATCH] s390/tape: Introduce idal buffer array MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens --- arch/s390/include/asm/cio.h | 2 + arch/s390/include/asm/idals.h | 76 +++++++++++++++++++++++++++++++++++ drivers/s390/char/tape.h | 2 +- drivers/s390/char/tape_char.c | 10 ++--- drivers/s390/char/tape_core.c | 16 ++++---- drivers/s390/char/tape_std.c | 4 +- 6 files changed, 94 insertions(+), 16 deletions(-) diff --git a/arch/s390/include/asm/cio.h b/arch/s390/include/asm/cio.h index b6b619f340a5e..0a82ae2300b69 100644 --- a/arch/s390/include/asm/cio.h +++ b/arch/s390/include/asm/cio.h @@ -18,6 +18,8 @@ #include +#define CCW_MAX_BYTE_COUNT 65535 + /** * struct ccw1 - channel command word * @cmd_code: command code diff --git a/arch/s390/include/asm/idals.h b/arch/s390/include/asm/idals.h index ac68c657b28c8..e5000ee6cdc6a 100644 --- a/arch/s390/include/asm/idals.h +++ b/arch/s390/include/asm/idals.h @@ -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. */ diff --git a/drivers/s390/char/tape.h b/drivers/s390/char/tape.h index 349fa95dcedb8..6e97b26d4e70a 100644 --- a/drivers/s390/char/tape.h +++ b/drivers/s390/char/tape.h @@ -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. */ }; diff --git a/drivers/s390/char/tape_char.c b/drivers/s390/char/tape_char.c index e229585cfb9ed..bcc49e9eabb8d 100644 --- a/drivers/s390/char/tape_char.c +++ b/drivers/s390/char/tape_char.c @@ -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); diff --git a/drivers/s390/char/tape_core.c b/drivers/s390/char/tape_core.c index 30ace31a3bba4..304c6eae139c9 100644 --- a/drivers/s390/char/tape_core.c +++ b/drivers/s390/char/tape_core.c @@ -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; } diff --git a/drivers/s390/char/tape_std.c b/drivers/s390/char/tape_std.c index 29abbc23f9088..5f3646f78bfac 100644 --- a/drivers/s390/char/tape_std.c +++ b/drivers/s390/char/tape_std.c @@ -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; } -- 2.47.3