summaryrefslogtreecommitdiff
path: root/devio/rdwr.c
diff options
context:
space:
mode:
Diffstat (limited to 'devio/rdwr.c')
-rw-r--r--devio/rdwr.c473
1 files changed, 473 insertions, 0 deletions
diff --git a/devio/rdwr.c b/devio/rdwr.c
new file mode 100644
index 00000000..99ba9414
--- /dev/null
+++ b/devio/rdwr.c
@@ -0,0 +1,473 @@
+/* Implements various types of I/O on top of raw devices.
+
+ Copyright (C) 1995 Free Software Foundation, Inc.
+
+ Written by Miles Bader <miles@gnu.ai.mit.edu>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include <hurd.h>
+#include <assert.h>
+#include <string.h>
+
+#include "open.h"
+#include "dev.h"
+#include "mem.h"
+#include "window.h"
+
+/* ---------------------------------------------------------------- */
+
+/* Writes BUF to DEV, copying through an intermediate buffer to page-align
+ it. If STAGING_BUF isn't 0, it is used as the copy buffer for
+ small-enough transfers (staging_buf is assumed to be one block in length).
+ AMOUNT is the actual amount written, and LEN is the amount of source
+ material actually in BUF; if LEN is smaller than AMOUNT, the remainder is
+ zero. */
+static error_t
+copying_block_write(struct dev *dev, vm_address_t staging_buf,
+ vm_address_t buf, vm_size_t len, vm_size_t amount,
+ vm_offset_t *offs)
+{
+ error_t err = 0;
+ vm_address_t copy_buf = staging_buf;
+ vm_size_t copy_buf_len = dev->block_size;
+
+ if (amount > dev->block_size || staging_buf == 0)
+ {
+ copy_buf_len = amount;
+ err = vm_allocate(mach_task_self(), &copy_buf, copy_buf_len, 1);
+ if (err)
+ return err;
+ }
+
+ bcopy((char *)buf, (char *)copy_buf, len);
+ if (len < amount && copy_buf == staging_buf)
+ /* We need to zero the rest of the bloc, but only if we didn't
+ vm_allocate it (in which case it will be zero-filled). */
+ bzero((char *)buf + len, amount - len);
+
+ err = dev_write(dev, copy_buf, amount, offs);
+
+ if (copy_buf != staging_buf)
+ vm_deallocate(mach_task_self(), copy_buf, copy_buf_len);
+
+ return err;
+}
+
+/* ---------------------------------------------------------------- */
+
+/* Copies LEN bytes from BUF to DEV, using STAGING_BUF to do buffering of
+ partial blocks, and returning the amount actually written in AMOUNT.
+ *OFFS is incremented to reflect the amount read/written. If an error
+ occurs, the error code is returned, otherwise 0. */
+error_t
+buffered_write(struct dev *dev, vm_address_t staging_buf,
+ vm_address_t buf, vm_size_t len, vm_size_t *amount,
+ vm_offset_t *offs)
+{
+ error_t err = 0;
+ int bsize = dev->block_size;
+ int staging_buf_loc = *offs % bsize;
+ int left_in_staging_buf = bsize - staging_buf_loc;
+ vm_offset_t start_offs = *offs;
+
+ if (left_in_staging_buf > 0)
+ /* Write what's buffered from the last I/O. */
+ {
+ /* Amount of the current i/o we can put in the staging buffer. */
+ int stage = (left_in_staging_buf > len ? len : left_in_staging_buf);
+
+ bcopy((char *)buf, (char *)staging_buf + staging_buf_loc, stage);
+
+ buf += stage;
+ len -= stage;
+ *offs += stage;
+
+ if (stage == left_in_staging_buf)
+ /* We've filled up STAGING_BUF so we can write it out now. */
+ {
+ /* Backup OFFS to reflect the beginning-of-block position. */
+ *offs -= bsize;
+ err = dev_write(dev, staging_buf, bsize, offs);
+ }
+ }
+
+ if (!err && len > bsize)
+ /* Enough i/o pending to do whole block transfers. */
+ {
+ /* The number of bytes at the end of the transfer that aren't a
+ multiple of the block-size. We have to deal with these separately
+ because device i/o must be in block multiples. */
+ int excess = len % bsize;
+ vm_size_t block_len = len - excess;
+
+ if (dev_write_valid(dev, buf, block_len, offs))
+ /* BUF is page-aligned, so we can do i/o directly to the device, or
+ it is small enough that it doesn't matter. */
+ err = dev_write(dev, buf, block_len, offs);
+ else
+ /* Argh! BUF isn't page aligned! We must filter the i/o though an
+ intermediate buffer... */
+ err = copying_block_write(dev, staging_buf,
+ buf, block_len, block_len, offs);
+
+ if (*offs - start_offs < left_in_staging_buf + block_len)
+ /* Didn't write out all the blocks, so suppress buffering the rest. */
+ len = 0;
+ else
+ len = excess;
+ }
+
+ /* At this point, LEN should be < BLOCK_SIZE, so we use buffering again. */
+ if (!err && len > 0)
+ {
+ bcopy((char *)staging_buf, (char *)buf, len);
+ *offs += len;
+ }
+
+ *amount = *offs - start_offs;
+ if (*amount > 0)
+ /* If an error occurred, but we successfully wrote *something*, then
+ pretend nothing bad happened; the error will probably get caught next
+ time. */
+ err = 0;
+
+ return err;
+}
+
+/* ---------------------------------------------------------------- */
+
+/* Reads AMOUNT bytes from DEV and returns them in BUF and BUF_LEN (using the
+ standard mach out-array conventions), using STAGING_BUF to do buffering of
+ partial blocks. *OFFS is incremented to reflect the amount read/written.
+ If an error occurs, the error code is returned, otherwise 0. */
+error_t
+buffered_read (struct dev *dev, vm_address_t staging_buf,
+ vm_address_t *buf, vm_size_t *buf_len, vm_size_t amount,
+ vm_offset_t *offs)
+{
+ error_t err = 0;
+ int bsize = dev->block_size;
+ vm_offset_t start_offs = *offs;
+ int staging_buf_loc = *offs % bsize;
+ int from_staging_buf = bsize - staging_buf_loc;
+ vm_address_t block_buf = *buf;
+ vm_size_t block_buf_size = *buf_len;
+ vm_size_t block_amount = amount;
+
+ if (staging_buf_loc > 0)
+ {
+ /* Read into a temporary buffer. */
+ block_buf = 0;
+ block_buf_size = 0;
+
+ if (from_staging_buf > amount)
+ from_staging_buf = amount;
+
+ block_amount -= from_staging_buf;
+ }
+ else
+ from_staging_buf = 0;
+
+ /* Read any new block required. */
+ if (block_amount > 0)
+ {
+ /* We read enough to get every full block of BLOCK_AMOUNT, plus an
+ additional whole block if there's any more; we just copy any excess
+ from that last block into STAGING_BUF for next time. */
+ block_amount = ((block_amount + bsize - 1) / bsize) * bsize;
+
+ err = dev_read(dev, &block_buf, &block_buf_size, block_amount, offs);
+ if (err && staging_buf_loc > 0)
+ /* We got an error, but don't abort, since we did get the bit from
+ the buffer. */
+ {
+ err = 0;
+ amount = from_staging_buf;
+ block_amount = 0;
+ }
+
+ if (amount > *offs - start_offs)
+ /* If we read less than we hoped, reflect this down below. */
+ amount = *offs - start_offs;
+ }
+
+ if (staging_buf_loc > 0)
+ /* Coalesce what we have in STAGING_BUF with what we read. */
+ {
+ err = allocate(buf, buf_len, amount);
+ assert_perror(err);
+ bcopy((char *)staging_buf + staging_buf_loc, (char *)*buf,
+ from_staging_buf);
+
+ if (block_amount > 0)
+ bcopy((char *)block_buf, (char *)*buf + from_staging_buf,
+ amount - from_staging_buf);
+ }
+ else
+ /* Otherwise, BLOCK_BUF should already contain the correct data. */
+ {
+ *buf = block_buf;
+ *buf_len = block_buf_size;
+ }
+
+ if (*offs - start_offs > amount)
+ /* We've read too far, so put some amount from the end back into
+ STAGING_BUF. */
+ {
+ int excess = (*offs - start_offs) - amount;
+
+ bcopy((char *)block_buf + amount,
+ (char *)staging_buf + bsize - excess,
+ excess);
+ *offs -= excess;
+
+ if (excess >= vm_page_size)
+ deallocate_excess(*buf, *buf_len, excess);
+ *buf_len -= excess;
+ }
+
+ /* Deallocate any extra copy buffer if necessary. */
+ if (*buf != block_buf)
+ vm_deallocate(mach_task_self(), block_buf, block_buf_size);
+
+ return err;
+}
+
+/* ---------------------------------------------------------------- */
+
+/* Write BUF_LEN bytes from BUF to DEV, padding with zeros as necessary to
+ write whole blocks, and returning the amount actually written in AMOUNT.
+ If successful, 0 is returned, otherwise an error code is returned. *OFFS
+ is incremented by the change in device location. */
+error_t
+raw_write(struct dev *dev,
+ vm_address_t buf, vm_size_t buf_len,
+ vm_size_t *amount, vm_offset_t *offs)
+{
+ error_t err;
+ int bsize = dev->block_size;
+ int block_amount = ((buf_len + bsize - 1) / bsize) * bsize;
+ vm_offset_t start_offs = *offs;
+
+ if (block_amount == buf_len && dev_write_valid(dev, buf, block_amount, offs))
+ /* BUF is page-aligned, so we can do i/o directly to the device, or
+ it is small enough that it doesn't matter. */
+ err = dev_write(dev, buf, block_amount, offs);
+ else
+ /* Argh! BUF isn't page aligned! We must filter the i/o though an
+ intermediate buffer... [We use DEV's io_state buffer, as we know
+ that the io_state is locked in open_rdwr, and it isn't otherwise
+ used...] */
+ err = copying_block_write(dev, dev->io_state.buffer,
+ buf, buf_len, block_amount, offs);
+
+ if (!err && *offs - start_offs < buf_len)
+ *amount = *offs - start_offs;
+ else
+ *amount = buf_len;
+
+ return err;
+}
+
+/* Read AMOUNT bytes from DEV into BUF and BUF_LEN; only whole blocks are
+ read, but anything greater than *AMOUNT bytes is discarded. The standard
+ mach out-array convention is used to return the data in BUF and BUF_LEN.
+ If successful, 0 is returned, otherwise an error code is returned. *OFFS
+ is incremented by the change in device location. */
+error_t
+raw_read(struct dev *dev,
+ vm_address_t *buf, vm_size_t *buf_len,
+ vm_size_t amount, vm_offset_t *offs)
+{
+ error_t err;
+ int bsize = dev->block_size;
+ int block_amount = ((amount + bsize - 1) / bsize) * bsize;
+ err = dev_read(dev, buf, buf_len, block_amount, offs);
+
+ if (!err)
+ {
+ int excess = *buf_len - amount;
+ if (excess > vm_page_size)
+ deallocate_excess(*buf, *buf_len, excess);
+ if (excess > 0)
+ *buf_len = amount;
+ }
+
+ return err;
+}
+
+/* ---------------------------------------------------------------- */
+
+struct rdwr_state
+{
+ struct dev *dev;
+ off_t user_offs;
+ vm_offset_t *offs_p;
+ struct io_state *io_state;
+};
+
+/* Setup state needed for I/O to/from OPEN, putting it into STATE. OFFS
+ should be the original user-supplied offset. */
+static void
+rdwr_state_init(struct rdwr_state *state, struct open *open, off_t offs)
+{
+ state->dev = open->dev;
+ state->io_state = open_get_io_state(open);
+ state->user_offs = offs;
+
+ if (dev_is(state->dev, DEV_SERIAL))
+ /* For serial i/o, we always ignore the proffered offs, and use the
+ actual device offset. */
+ state->user_offs = -1;
+
+ if (state->user_offs == -1 || !dev_is(state->dev, DEV_BUFFERED))
+ /* If we're going to use some bit of IO_STATE, lock it first. This
+ should only not happen if we're going to used windowed i/o with an
+ explicit offset. */
+ io_state_lock(state->io_state);
+
+ if (state->user_offs == -1)
+ state->offs_p = &state->io_state->location;
+ else
+ state->offs_p = (vm_offset_t *)&state->user_offs;
+}
+
+/* Destroy any state created by rdwr_state_init. */
+static void
+rdwr_state_finalize(struct rdwr_state *state)
+{
+ if (state->user_offs == -1 || !dev_is(state->dev, DEV_BUFFERED))
+ io_state_unlock(state->io_state);
+}
+
+/* ---------------------------------------------------------------- */
+
+/* Writes up to LEN bytes from BUF to OPEN's device at device offset OFFS
+ (which may be ignored if the device doesn't support random access),
+ and returns the number of bytes written in AMOUNT. If no error occurs,
+ zero is returned, otherwise the error code is returned. */
+error_t
+open_write(struct open *open, vm_address_t buf, vm_size_t len,
+ vm_size_t *amount, off_t offs)
+{
+ error_t err;
+ struct rdwr_state state;
+ struct dev *dev = open->dev;
+#ifdef MSG
+ off_t start_offs;
+#endif
+
+ rdwr_state_init(&state, open, offs);
+
+#ifdef MSG
+ start_offs = *state.offs_p;
+#endif
+
+ if (!dev_is(dev, DEV_BUFFERED))
+ err = raw_write(dev, buf, len, amount, state.offs_p);
+ else if (dev_is(dev, DEV_SERIAL))
+ {
+ state.io_state->buffer_use = IO_STATE_BUFFERED_WRITE;
+ err = buffered_write(dev, state.io_state->buffer, buf, len,
+ amount, state.offs_p);
+ }
+ else
+ err = window_write(open->window, buf, len, amount, state.offs_p);
+
+#ifdef MSG
+ if (debug)
+ {
+ char *mode =
+ (dev_is(dev, DEV_BUFFERED)
+ ? dev_is(dev, DEV_SERIAL) ? "buffered" : "windowed" : "raw");
+ char *estr = err ? strerror(err) : "OK";
+ char *bstr = err ? "-" : brep(buf, len);
+
+ mutex_lock(&debug_lock);
+ fprintf(debug, "open_rdwr:\n using %s offset\n",
+ (offs == -1 || !dev_is(dev, DEV_BUFFERED))
+ ? (state.offs_p == &dev->io_state.location
+ ? "device" : "open")
+ : "msg");
+ fprintf(debug, " %s write(%s, %d, %d) => %s, %d\n",
+ mode, bstr, len, (int)start_offs, estr, *amount);
+ fprintf(debug, " offset = %d\n", (int)*state.offs_p);
+ mutex_unlock(&debug_lock);
+ }
+#endif
+
+ rdwr_state_finalize(&state);
+
+ return err;
+}
+
+/* Reads up to AMOUNT bytes from the device into BUF and BUF_LEN using the
+ standard mach out-array convention. If no error occurs, zero is returned,
+ otherwise the error code is returned. */
+error_t
+open_read(struct open *open, vm_address_t *buf, vm_size_t *buf_len,
+ vm_size_t amount, off_t offs)
+{
+ error_t err;
+ struct rdwr_state state;
+ struct dev *dev = open->dev;
+#ifdef MSG
+ off_t start_offs;
+#endif
+
+ rdwr_state_init(&state, open, offs);
+
+#ifdef MSG
+ start_offs = *state.offs_p;
+#endif
+
+ if (!dev_is(dev, DEV_BUFFERED))
+ err = raw_read(dev, buf, buf_len, amount, state.offs_p);
+ else if (dev_is(dev, DEV_SERIAL))
+ {
+ state.io_state->buffer_use = IO_STATE_BUFFERED_READ;
+ err = buffered_read(dev, state.io_state->buffer, buf, buf_len,
+ amount, state.offs_p);
+ }
+ else
+ err = window_read(open->window, buf, buf_len, amount, state.offs_p);
+
+#ifdef MSG
+ if (debug)
+ {
+ char *mode =
+ (dev_is(dev, DEV_BUFFERED)
+ ? dev_is(dev, DEV_SERIAL) ? "buffered" : "windowed" : "raw");
+ char *estr = err ? strerror(err) : "OK";
+ char *bstr = err ? "-" : brep(*buf, *buf_len);
+
+ mutex_lock(&debug_lock);
+ fprintf(debug, "open_rdwr:\n using %s offset\n",
+ (offs == -1 || !dev_is(dev, DEV_BUFFERED))
+ ? (state.offs_p == &dev->io_state.location
+ ? "device" : "open")
+ : "msg");
+ fprintf(debug, " %s read(%d, %d) => %s, %s, %d\n",
+ mode, amount, (int)start_offs, estr, bstr, *buf_len);
+ fprintf(debug, " offset = %d\n", (int)*state.offs_p);
+ mutex_unlock(&debug_lock);
+ }
+#endif
+
+ rdwr_state_finalize(&state);
+
+ return err;
+}