]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
s390/tape: Introduce idal buffer array
authorJan Höppner <hoeppner@linux.ibm.com>
Thu, 16 Oct 2025 07:47:17 +0000 (09:47 +0200)
committerHeiko Carstens <hca@linux.ibm.com>
Tue, 21 Oct 2025 08:25:55 +0000 (10:25 +0200)
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>
arch/s390/include/asm/cio.h
arch/s390/include/asm/idals.h
drivers/s390/char/tape.h
drivers/s390/char/tape_char.c
drivers/s390/char/tape_core.c
drivers/s390/char/tape_std.c

index b6b619f340a5eb0eadd0f593e4bee291ec1828e6..0a82ae2300b69034499c3e0ae7a1856d257e0eaa 100644 (file)
@@ -18,6 +18,8 @@
 
 #include <asm/scsw.h>
 
+#define CCW_MAX_BYTE_COUNT 65535
+
 /**
  * struct ccw1 - channel command word
  * @cmd_code: command code
index ac68c657b28c8910bf8dda9cca009380eceb517a..e5000ee6cdc6a3e06d868271886b821e8120ca92 100644 (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.
  */
index 349fa95dcedb89e786173ae3674cd519f1a1f880..6e97b26d4e70a4aa9adc77626b89f7620f980d88 100644 (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. */
 };
 
index e229585cfb9eda1b3d1b882156670d816b5e386c..bcc49e9eabb8d77678b6042caa6b3715d12b64c7 100644 (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);
index 30ace31a3bba498d6f1aa0a20c2ac1aa4368392a..304c6eae139c99bf3fc3cb847c1957e709851a31 100644 (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;
 }
index 29abbc23f9088681b979fa26df31d8c96623eed7..5f3646f78bfacaccfa7bd779e984cfa1eb158a7d 100644 (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;
 }