summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/s390/include/asm/cio.h2
-rw-r--r--arch/s390/include/asm/idals.h76
-rw-r--r--drivers/s390/char/tape.h2
-rw-r--r--drivers/s390/char/tape_char.c10
-rw-r--r--drivers/s390/char/tape_core.c16
-rw-r--r--drivers/s390/char/tape_std.c4
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 <asm/scsw.h>
+#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
@@ -181,6 +181,82 @@ static inline void idal_buffer_free(struct idal_buffer *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.
*/
static inline bool __idal_buffer_is_needed(struct idal_buffer *ib)
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;
}