]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
s390/tape: Add support for bigger block sizes
authorJan Höppner <hoeppner@linux.ibm.com>
Thu, 16 Oct 2025 07:47:18 +0000 (09:47 +0200)
committerHeiko Carstens <hca@linux.ibm.com>
Tue, 21 Oct 2025 08:25:55 +0000 (10:25 +0200)
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 <hoeppner@linux.ibm.com>
Reviewed-by: Jens Remus <jremus@linux.ibm.com>
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
drivers/s390/char/tape.h
drivers/s390/char/tape_char.c
drivers/s390/char/tape_core.c
drivers/s390/char/tape_std.c
drivers/s390/char/tape_std.h

index 6e97b26d4e70a4aa9adc77626b89f7620f980d88..3953b31b0c559e0844be2fa3fc09fec0e628b295 100644 (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 *);
@@ -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)
 {
index bcc49e9eabb8d77678b6042caa6b3715d12b64c7..96e6c8a5735ed92d9fccd5eff23c8e912ead63ff 100644 (file)
@@ -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) {
                /*
index 304c6eae139c99bf3fc3cb847c1957e709851a31..ab1a2dc7d711aba64940c4e207bcb99a06047799 100644 (file)
@@ -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;
index 5f3646f78bfacaccfa7bd779e984cfa1eb158a7d..4e1c52313fbcd817329425227c684289b0a9e381 100644 (file)
@@ -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;
 }
index eefeb5484214b5607741c3ec2b08f62be918247b..2cf9f725b3b3fa0ca7debd09e837397d7a83dd07 100644 (file)
 #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.