| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <fuchsia/hardware/block/c/fidl.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/fifo.h> |
| #include <lib/zx/vmo.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <zircon/device/block.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/time.h> |
| #include <zircon/types.h> |
| |
| #include <block-client/client.h> |
| #include <ramdevice-client/ramdisk.h> |
| |
| static uint64_t number(const char* str) { |
| char* end; |
| uint64_t n = strtoull(str, &end, 10); |
| |
| uint64_t m = 1; |
| switch (*end) { |
| case 'G': |
| case 'g': |
| m = 1024 * 1024 * 1024; |
| break; |
| case 'M': |
| case 'm': |
| m = 1024 * 1024; |
| break; |
| case 'K': |
| case 'k': |
| m = 1024; |
| break; |
| } |
| return m * n; |
| } |
| |
| static void bytes_per_second(uint64_t bytes, uint64_t nanos) { |
| double s = ((double)nanos) / ((double)1000000000); |
| double rate = ((double)bytes) / s; |
| |
| const char* unit = "B"; |
| if (rate > 1024 * 1024) { |
| unit = "MB"; |
| rate /= 1024 * 1024; |
| } else if (rate > 1024) { |
| unit = "KB"; |
| rate /= 1024; |
| } |
| fprintf(stderr, "%g %s/s\n", rate, unit); |
| } |
| |
| static zx_duration_t iotime_posix(int is_read, int fd, size_t total, size_t bufsz) { |
| void* buffer = malloc(bufsz); |
| if (buffer == NULL) { |
| fprintf(stderr, "error: out of memory\n"); |
| return ZX_TIME_INFINITE; |
| } |
| |
| zx_time_t t0 = zx_clock_get_monotonic(); |
| size_t n = total; |
| const char* fn_name = is_read ? "read" : "write"; |
| while (n > 0) { |
| size_t xfer = (n > bufsz) ? bufsz : n; |
| ssize_t r = is_read ? read(fd, buffer, xfer) : write(fd, buffer, xfer); |
| if (r < 0) { |
| fprintf(stderr, "error: %s() error %d\n", fn_name, errno); |
| return ZX_TIME_INFINITE; |
| } |
| if ((size_t)r != xfer) { |
| fprintf(stderr, "error: %s() %zu of %zu bytes processed\n", fn_name, r, xfer); |
| return ZX_TIME_INFINITE; |
| } |
| n -= xfer; |
| } |
| zx_time_t t1 = zx_clock_get_monotonic(); |
| |
| return zx_time_sub_time(t1, t0); |
| } |
| |
| static zx_duration_t iotime_block(int is_read, int fd, size_t total, size_t bufsz) { |
| if ((total % 4096) || (bufsz % 4096)) { |
| fprintf(stderr, "error: total and buffer size must be multiples of 4K\n"); |
| return ZX_TIME_INFINITE; |
| } |
| |
| return iotime_posix(is_read, fd, total, bufsz); |
| } |
| |
| static zx_duration_t iotime_fifo(char* dev, int is_read, int fd, size_t total, size_t bufsz) { |
| zx_status_t status; |
| zx::vmo vmo; |
| if ((status = zx::vmo::create(bufsz, 0, &vmo)) != ZX_OK) { |
| fprintf(stderr, "error: out of memory %d\n", status); |
| return ZX_TIME_INFINITE; |
| } |
| |
| fdio_cpp::UnownedFdioCaller disk_connection(fd); |
| zx::unowned_channel channel(disk_connection.borrow_channel()); |
| fuchsia_hardware_block_BlockInfo info; |
| |
| zx_status_t io_status = fuchsia_hardware_block_BlockGetInfo(channel->get(), &status, &info); |
| if (io_status != ZX_OK || status != ZX_OK) { |
| fprintf(stderr, "error: cannot get info for '%s'\n", dev); |
| return ZX_TIME_INFINITE; |
| } |
| |
| zx::fifo fifo; |
| io_status = |
| fuchsia_hardware_block_BlockGetFifo(channel->get(), &status, fifo.reset_and_get_address()); |
| if (io_status != ZX_OK || status != ZX_OK) { |
| fprintf(stderr, "error: cannot get fifo for '%s'\n", dev); |
| return ZX_TIME_INFINITE; |
| } |
| |
| groupid_t group = 0; |
| |
| zx::vmo dup; |
| if ((status = vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup)) != ZX_OK) { |
| fprintf(stderr, "error: cannot duplicate handle %d\n", status); |
| return ZX_TIME_INFINITE; |
| } |
| |
| fuchsia_hardware_block_VmoId vmoid; |
| io_status = fuchsia_hardware_block_BlockAttachVmo(channel->get(), dup.release(), &status, &vmoid); |
| if (io_status != ZX_OK || status != ZX_OK) { |
| fprintf(stderr, "error: cannot attach vmo for '%s'\n", dev); |
| return ZX_TIME_INFINITE; |
| } |
| |
| fifo_client_t* client; |
| if ((status = block_fifo_create_client(fifo.release(), &client)) != ZX_OK) { |
| fprintf(stderr, "error: cannot create block client for '%s' %d\n", dev, status); |
| return ZX_TIME_INFINITE; |
| } |
| |
| zx_time_t t0 = zx_clock_get_monotonic(); |
| size_t n = total; |
| while (n > 0) { |
| size_t xfer = (n > bufsz) ? bufsz : n; |
| block_fifo_request_t request = { |
| .opcode = static_cast<uint32_t>(is_read ? BLOCKIO_READ : BLOCKIO_WRITE), |
| .reqid = 0, |
| .group = group, |
| .vmoid = vmoid.id, |
| .length = static_cast<uint32_t>(xfer / info.block_size), |
| .vmo_offset = 0, |
| .dev_offset = (total - n) / info.block_size, |
| }; |
| if ((status = block_fifo_txn(client, &request, 1)) != ZX_OK) { |
| fprintf(stderr, "error: block_fifo_txn error %d\n", status); |
| return ZX_TIME_INFINITE; |
| } |
| n -= xfer; |
| } |
| zx_time_t t1 = zx_clock_get_monotonic(); |
| return zx_time_sub_time(t1, t0); |
| } |
| |
| static int usage(void) { |
| fprintf(stderr, |
| "usage: iotime <read|write> <posix|block|fifo> <device|--ramdisk> <bytes> <bufsize>\n\n" |
| " <bytes> and <bufsize> must be a multiple of 4k for block mode\n" |
| " --ramdisk only supported for block mode\n"); |
| return -1; |
| } |
| |
| int main(int argc, char** argv) { |
| if (argc != 6) { |
| return usage(); |
| } |
| |
| int is_read = !strcmp(argv[1], "read"); |
| size_t total = number(argv[4]); |
| size_t bufsz = number(argv[5]); |
| |
| int r = -1; |
| ramdisk_client_t* ramdisk = NULL; |
| int fd; |
| if (!strcmp(argv[3], "--ramdisk")) { |
| if (strcmp(argv[2], "block")) { |
| fprintf(stderr, "ramdisk only supported for block\n"); |
| goto done; |
| } |
| zx_status_t status = ramdisk_create(512, total / 512, &ramdisk); |
| if (status != ZX_OK) { |
| fprintf(stderr, "error: cannot create %zu-byte ramdisk\n", total); |
| goto done; |
| } |
| fd = ramdisk_get_block_fd(ramdisk); |
| } else { |
| if ((fd = open(argv[3], is_read ? O_RDONLY : O_WRONLY)) < 0) { |
| fprintf(stderr, "error: cannot open '%s'\n", argv[3]); |
| goto done; |
| } |
| } |
| |
| zx_duration_t res; |
| if (!strcmp(argv[2], "posix")) { |
| res = iotime_posix(is_read, fd, total, bufsz); |
| } else if (!strcmp(argv[2], "block")) { |
| res = iotime_block(is_read, fd, total, bufsz); |
| } else if (!strcmp(argv[2], "fifo")) { |
| res = iotime_fifo(argv[3], is_read, fd, total, bufsz); |
| } else { |
| fprintf(stderr, "error: unknown mode '%s'\n", argv[2]); |
| goto done; |
| } |
| |
| if (res != ZX_TIME_INFINITE) { |
| fprintf(stderr, "%s %zu bytes in %zu ns: ", is_read ? "read" : "write", total, res); |
| bytes_per_second(total, res); |
| r = 0; |
| goto done; |
| } else { |
| goto done; |
| } |
| |
| done: |
| if (ramdisk != NULL) { |
| ramdisk_destroy(ramdisk); |
| } |
| return r; |
| } |