From 39376c77a5d07eb2924104572c1c0582a3ea54ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B6ppner?= Date: Thu, 16 Oct 2025 09:47:11 +0200 Subject: [PATCH 1/8] s390/tape: Remove count parameter from read/write_block functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The count parameter of the read/write_block discipline functions was never used. Remove it. Signed-off-by: Jan Höppner Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens --- drivers/s390/char/tape.h | 4 ++-- drivers/s390/char/tape_char.c | 4 ++-- drivers/s390/char/tape_std.c | 4 ++-- drivers/s390/char/tape_std.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/s390/char/tape.h b/drivers/s390/char/tape.h index 0aba30efb483..dc04518b7bec 100644 --- a/drivers/s390/char/tape.h +++ b/drivers/s390/char/tape.h @@ -151,8 +151,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); diff --git a/drivers/s390/char/tape_char.c b/drivers/s390/char/tape_char.c index 89778d922d9f..3d557b55bda8 100644 --- a/drivers/s390/char/tape_char.c +++ b/drivers/s390/char/tape_char.c @@ -162,7 +162,7 @@ tapechar_read(struct file *filp, char __user *data, size_t count, loff_t *ppos) 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. */ @@ -215,7 +215,7 @@ tapechar_write(struct file *filp, const char __user *data, size_t count, loff_t 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; diff --git a/drivers/s390/char/tape_std.c b/drivers/s390/char/tape_std.c index 176ae8e2eb6b..66d4a9b7e710 100644 --- a/drivers/s390/char/tape_std.c +++ b/drivers/s390/char/tape_std.c @@ -641,7 +641,7 @@ 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; @@ -685,7 +685,7 @@ tape_std_read_backward(struct tape_device *device, struct tape_request *request) * 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; diff --git a/drivers/s390/char/tape_std.h b/drivers/s390/char/tape_std.h index dcc63ff587f9..eefeb5484214 100644 --- a/drivers/s390/char/tape_std.h +++ b/drivers/s390/char/tape_std.h @@ -97,10 +97,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 *); From 1b9df1a28f0f625bcd2ce6bba2139a95c3aae703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B6ppner?= Date: Thu, 16 Oct 2025 09:47:12 +0200 Subject: [PATCH 2/8] s390/tape: Remove 34xx Read Opposite error recovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On old native type 3490 tape devices a Read Opposite error recovery procedure on Error Recovery Action Code (ERA) 26 was issued if a Read Forward command failed. This recovery procedure was implemented with the Read Backward command. As a preparation for a subsequent commit, that adds support for bigger block sizes, remove the 34xx ERA 26 related recovery code. The recovery code would need to be adapted to the bigger block sizes, without any possibility to be tested, as modern Virtual Tape Servers (VTS) do neither report ERA 26 on a Read Forward command failure nor support the error recovery procedure anymore. Signed-off-by: Jan Höppner Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens --- drivers/s390/char/tape_34xx.c | 28 ---------------------------- drivers/s390/char/tape_std.c | 20 -------------------- 2 files changed, 48 deletions(-) diff --git a/drivers/s390/char/tape_34xx.c b/drivers/s390/char/tape_34xx.c index 1e4984acb648..9d0fb4b867c4 100644 --- a/drivers/s390/char/tape_34xx.c +++ b/drivers/s390/char/tape_34xx.c @@ -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 " diff --git a/drivers/s390/char/tape_std.c b/drivers/s390/char/tape_std.c index 66d4a9b7e710..29e40ad6fd19 100644 --- a/drivers/s390/char/tape_std.c +++ b/drivers/s390/char/tape_std.c @@ -662,25 +662,6 @@ tape_std_read_block(struct tape_device *device) 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 */ @@ -741,6 +722,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); From a984d712773d6573e4b0a05c9e4a77623d8c7dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B6ppner?= Date: Thu, 16 Oct 2025 09:47:13 +0200 Subject: [PATCH 3/8] s390/tape: Remove 3590 Read Opposite error recovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On old native type 3590 tape devices a Read Opposite error recovery procedure on Error Recovery Action Code (ERA) 26 was issued if a Read Forward command failed. This recovery procedure was implemented with the Read Backward command. This is no longer supported. Remove 3590 ERA 26 and Read Backward related recovery code. Signed-off-by: Jan Höppner Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens --- drivers/s390/char/tape_3590.c | 89 ----------------------------------- 1 file changed, 89 deletions(-) diff --git a/drivers/s390/char/tape_3590.c b/drivers/s390/char/tape_3590.c index 2a2931d303cb..5b25f5415e4c 100644 --- a/drivers/s390/char/tape_3590.c +++ b/drivers/s390/char/tape_3590.c @@ -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: From 83cff1b1245fcabe76889520c8a92158e4f25f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B6ppner?= Date: Thu, 16 Oct 2025 09:47:14 +0200 Subject: [PATCH 4/8] s390/tape: Remove extra CCW allocation for error recovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Read Opposite error recovery code required 2 extra CCWs to be allocated in order to transform the request. As this error recovery code for both 34xx and 3590 was removed the additional allocation isn't required anymore. Reduce it to two. Signed-off-by: Jan Höppner Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens --- drivers/s390/char/tape_std.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/s390/char/tape_std.c b/drivers/s390/char/tape_std.c index 29e40ad6fd19..e46ffc46f323 100644 --- a/drivers/s390/char/tape_std.c +++ b/drivers/s390/char/tape_std.c @@ -645,11 +645,7 @@ tape_std_read_block(struct tape_device *device) { struct tape_request *request; - /* - * 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); + request = tape_alloc_request(2, 0); if (IS_ERR(request)) { DBF_EXCEPTION(6, "xrbl fail"); return request; From e039400f75f1a88675132abc57a18d090cf75209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B6ppner?= Date: Thu, 16 Oct 2025 09:47:15 +0200 Subject: [PATCH 5/8] s390/tape: Fix return value of ccw helper functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In contrast to all other helper functions used to build CCW chains, tape_ccw_cc_idal() and tape_ccw_end_idal() return values using post-increments, which results in returning the same CCW pointer. Though, the intent of the CCW helper functions is to return the _next_ CCW in the chain, which can then be processed. There is currently no actual issue, as tape_ccw_cc_idal() is not used yet and tape_ccw_end_idal() is only used at the end of a chain. Change both functions return statement to ccw + 1 and bring them in line with the other helper functions. Signed-off-by: Jan Höppner Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens --- drivers/s390/char/tape.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/s390/char/tape.h b/drivers/s390/char/tape.h index dc04518b7bec..0596a9758662 100644 --- a/drivers/s390/char/tape.h +++ b/drivers/s390/char/tape.h @@ -352,7 +352,7 @@ 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 +361,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 */ From a5e2ca22c18bcda97c7fc99d216ffd742405fb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B6ppner?= Date: Thu, 16 Oct 2025 09:47:16 +0200 Subject: [PATCH 6/8] s390/tape: Move idal allocation to core functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently tapechar_check_idalbuffer() is part of tape_char.c and is used to ensure the idal buffer is big enough for the requested I/O and reallocates a new one if required. The same is done in tape_std.c when a fixed block size is set using the mtsetblk command. This is essentially duplicate code. The allocation of the buffer that is required for I/O can be considered core functionality. Move the idal buffer allocation to tape_core.c, make it generally available, and reduce code duplication. Signed-off-by: Jan Höppner Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens --- drivers/s390/char/tape.h | 1 + drivers/s390/char/tape_char.c | 31 ++----------------------------- drivers/s390/char/tape_core.c | 28 ++++++++++++++++++++++++++++ drivers/s390/char/tape_std.c | 22 ++++------------------ 4 files changed, 35 insertions(+), 47 deletions(-) diff --git a/drivers/s390/char/tape.h b/drivers/s390/char/tape.h index 0596a9758662..349fa95dcedb 100644 --- a/drivers/s390/char/tape.h +++ b/drivers/s390/char/tape.h @@ -234,6 +234,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 *); diff --git a/drivers/s390/char/tape_char.c b/drivers/s390/char/tape_char.c index 3d557b55bda8..e229585cfb9e 100644 --- a/drivers/s390/char/tape_char.c +++ b/drivers/s390/char/tape_char.c @@ -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 @@ -156,7 +129,7 @@ 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; @@ -208,7 +181,7 @@ 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; diff --git a/drivers/s390/char/tape_core.c b/drivers/s390/char/tape_core.c index 6ec812280221..30ace31a3bba 100644 --- a/drivers/s390/char/tape_core.c +++ b/drivers/s390/char/tape_core.c @@ -726,6 +726,34 @@ tape_free_request (struct tape_request * request) kfree(request); } +int +tape_check_idalbuffer(struct tape_device *device, size_t size) +{ + struct idal_buffer *new; + + if (device->char_data.idal_buf != NULL && + device->char_data.idal_buf->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_alloc(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; +} + static int __tape_start_io(struct tape_device *device, struct tape_request *request) { diff --git a/drivers/s390/char/tape_std.c b/drivers/s390/char/tape_std.c index e46ffc46f323..29abbc23f908 100644 --- a/drivers/s390/char/tape_std.c +++ b/drivers/s390/char/tape_std.c @@ -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; From 574817d6c0866e80e8f09b8537476685631fc992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B6ppner?= Date: Thu, 16 Oct 2025 09:47:17 +0200 Subject: [PATCH 7/8] s390/tape: Introduce idal buffer array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 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 b6b619f340a5..0a82ae2300b6 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 ac68c657b28c..e5000ee6cdc6 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 349fa95dcedb..6e97b26d4e70 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 e229585cfb9e..bcc49e9eabb8 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 30ace31a3bba..304c6eae139c 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 29abbc23f908..5f3646f78bfa 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; } From 319d3d66537e615e0729ae734099fe01581bc09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B6ppner?= Date: Thu, 16 Oct 2025 09:47:18 +0200 Subject: [PATCH 8/8] s390/tape: Add support for bigger block sizes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tape device type 3590/3592 and emulated 3490 VTS can handle a block size of up to 256K bytes. Currently the tape device driver is limited to a block size of 65535 bytes (64K-1). This limitation stems from the maximum of 65535 bytes of data that can be transferred with one Channel-Command Word (CCW). To work around this limitation data chaining is used which uses several CCW to transfer an entire 256K block of data. A single CCW holds a maximum of 65535 bytes of data. Set MAX_BLOCKSIZE to 262144 (= 256K) to allow for data transfers with larger block sizes. The read_block() and write_block() discipline functions calculate the number of CCWs required based on the IDAL buffer array size that was created for a given block size. If there is more than one CCW required for the data transfer, the new helper function tape_ccw_dc_idal() is used to build the data chain accordingly. The Interruption-Repsonse Block (irb) is added to the tape_request struct so that the tapechar_read/write() functions can analyze what data was read or written accordingly. Signed-off-by: Jan Höppner Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens --- drivers/s390/char/tape.h | 10 +++++ drivers/s390/char/tape_char.c | 70 ++++++++++++++++++++++++----------- drivers/s390/char/tape_core.c | 5 ++- drivers/s390/char/tape_std.c | 30 +++++++++++---- drivers/s390/char/tape_std.h | 5 +-- 5 files changed, 86 insertions(+), 34 deletions(-) diff --git a/drivers/s390/char/tape.h b/drivers/s390/char/tape.h index 6e97b26d4e70..3953b31b0c55 100644 --- a/drivers/s390/char/tape.h +++ b/drivers/s390/char/tape.h @@ -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 *); @@ -347,6 +348,15 @@ 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) { diff --git a/drivers/s390/char/tape_char.c b/drivers/s390/char/tape_char.c index bcc49e9eabb8..96e6c8a5735e 100644 --- a/drivers/s390/char/tape_char.c +++ b/drivers/s390/char/tape_char.c @@ -100,9 +100,12 @@ tapechar_cleanup_device(struct tape_device *device) 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"); @@ -141,12 +144,25 @@ tapechar_read(struct file *filp, char __user *data, size_t count, loff_t *ppos) /* 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.ibs, - 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; @@ -158,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; @@ -185,31 +203,41 @@ tapechar_write(struct file *filp, const char __user *data, size_t count, loff_t 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); 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.ibs, - 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) { /* diff --git a/drivers/s390/char/tape_core.c b/drivers/s390/char/tape_core.c index 304c6eae139c..ab1a2dc7d711 100644 --- a/drivers/s390/char/tape_core.c +++ b/drivers/s390/char/tape_core.c @@ -1129,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; diff --git a/drivers/s390/char/tape_std.c b/drivers/s390/char/tape_std.c index 5f3646f78bfa..4e1c52313fbc 100644 --- a/drivers/s390/char/tape_std.c +++ b/drivers/s390/char/tape_std.c @@ -630,16 +630,23 @@ struct tape_request * tape_std_read_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); + 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.ibs); + 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; } @@ -651,16 +658,23 @@ struct tape_request * 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.ibs); + 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; } diff --git a/drivers/s390/char/tape_std.h b/drivers/s390/char/tape_std.h index eefeb5484214..2cf9f725b3b3 100644 --- a/drivers/s390/char/tape_std.h +++ b/drivers/s390/char/tape_std.h @@ -14,10 +14,9 @@ #include /* - * 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.