/* Block Device Driver Test driver, by D.C. van Moolenbroek */ #include #include #include #include #include #include #include #include #include enum { RESULT_OK, /* exactly as expected */ RESULT_DEATH, /* driver died */ RESULT_COMMFAIL, /* communication failed */ RESULT_BADTYPE, /* bad type in message */ RESULT_BADID, /* bad request ID in message */ RESULT_BADSTATUS, /* bad/unexpected status in message */ RESULT_TRUNC, /* request truncated unexpectedly */ RESULT_CORRUPT, /* buffer touched erroneously */ RESULT_MISSING, /* buffer left untouched erroneously */ RESULT_OVERFLOW, /* area around buffer touched */ RESULT_BADVALUE /* bad/unexpected return value */ }; typedef struct { int type; ssize_t value; } result_t; static char driver_label[32] = ""; /* driver DS label */ static devminor_t driver_minor = -1; /* driver's partition minor to use */ static endpoint_t driver_endpt; /* driver endpoint */ static int may_write = FALSE; /* may we write to the device? */ static int sector_size = 512; /* size of a single disk sector */ static int min_read = 512; /* minimum total size of read req */ static int min_write = 0; /* minimum total size of write req */ static int element_size = 512; /* minimum I/O vector element size */ static int max_size = 131072; /* maximum total size of any req */ /* Note that we do not test exceeding the max_size limit, so it is safe to set * it to a value lower than the driver supports. */ /* These settings are used for automated test runs. */ static int contig = TRUE; /* allocate contiguous DMA memory? */ static int silent = FALSE; /* do not produce console output? */ static struct part_geom part; /* base and size of target partition */ #define NR_OPENED 10 /* maximum number of opened devices */ static dev_t opened[NR_OPENED]; /* list of currently opened devices */ static int nr_opened = 0; /* current number of opened devices */ static int total_tests = 0; /* total number of tests performed */ static int failed_tests = 0; /* number of tests that failed */ static int failed_groups = 0; /* nr of groups that had failures */ static int group_failure; /* has this group had a failure yet? */ static int driver_deaths = 0; /* number of restarts that we saw */ /* Options supported by this driver. */ static struct optset optset_table[] = { { "label", OPT_STRING, driver_label, sizeof(driver_label) }, { "minor", OPT_INT, &driver_minor, 10 }, { "rw", OPT_BOOL, &may_write, TRUE }, { "ro", OPT_BOOL, &may_write, FALSE }, { "sector", OPT_INT, §or_size, 10 }, { "element", OPT_INT, &element_size, 10 }, { "min_read", OPT_INT, &min_read, 10 }, { "min_write", OPT_INT, &min_write, 10 }, { "max", OPT_INT, &max_size, 10 }, { "nocontig", OPT_BOOL, &contig, FALSE }, { "silent", OPT_BOOL, &silent, TRUE }, { NULL, 0, NULL, 0 } }; static void output(char *fmt, ...) { /* Print debugging information, unless configured to be silent. */ va_list argp; if (silent) return; va_start(argp, fmt); vprintf(fmt, argp); va_end(argp); } static void *alloc_dma_memory(size_t size) { /* Allocate memory that may be used for direct DMA. For most drivers, * this means that the memory has to be physically contiguous. For some * drivers (e.g. VND) we allow non-contiguous allocation, because VM is * currently flaky and does not always manage to provide contiguous * memory even when it should, thus causing needless test failures. */ void *ptr; if (contig) ptr = alloc_contig(size, 0, NULL); else ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PREALLOC | MAP_ANON, -1, 0); if (ptr == MAP_FAILED) panic("unable to allocate %d bytes of memory", size); return ptr; } static void free_dma_memory(void *ptr, size_t size) { /* Free memory previously allocated for direct DMA. */ if (contig) free_contig(ptr, size); else munmap(ptr, size); } static int set_result(result_t *res, int type, ssize_t value) { /* Set the result to the given result type and with the given optional * extra value. Return the type. */ res->type = type; res->value = value; return type; } static int accept_result(result_t *res, int type, ssize_t value) { /* If the result is of the given type and value, reset it to a success * result. This allows for a logical OR on error codes. Return whether * the result was indeed reset. */ if (res->type == type && res->value == value) { set_result(res, RESULT_OK, 0); return TRUE; } return FALSE; } static void got_result(result_t *res, char *desc) { /* Process the result of a test. Keep statistics. */ static int i = 0; total_tests++; if (res->type != RESULT_OK) { failed_tests++; if (group_failure == FALSE) { failed_groups++; group_failure = TRUE; } } output("#%02d: %-38s\t[%s]\n", ++i, desc, (res->type == RESULT_OK) ? "PASS" : "FAIL"); switch (res->type) { case RESULT_DEATH: output("- driver died\n"); break; case RESULT_COMMFAIL: output("- communication failed; ipc_sendrec returned %d\n", res->value); break; case RESULT_BADTYPE: output("- bad type %d in reply message\n", res->value); break; case RESULT_BADID: output("- mismatched ID %d in reply message\n", res->value); break; case RESULT_BADSTATUS: output("- bad or unexpected status %d in reply message\n", res->value); break; case RESULT_TRUNC: output("- result size not as expected (%u bytes left)\n", res->value); break; case RESULT_CORRUPT: output("- buffer has been modified erroneously\n"); break; case RESULT_MISSING: output("- buffer has been left untouched erroneously\n"); break; case RESULT_OVERFLOW: output("- area around target buffer modified\n"); break; case RESULT_BADVALUE: output("- bad or unexpected return value %d from call\n", res->value); break; } } static void test_group(char *name, int exec) { /* Start a new group of tests. */ output("Test group: %s%s\n", name, exec ? "" : " (skipping)"); group_failure = FALSE; } static void reopen_device(dev_t minor) { /* Reopen a device after we were notified that the driver has died. * Explicitly ignore any errors here; this is a feeble attempt to get * ourselves back into business again. */ message m; memset(&m, 0, sizeof(m)); m.m_type = BDEV_OPEN; m.m_lbdev_lblockdriver_msg.minor = minor; m.m_lbdev_lblockdriver_msg.access = (may_write) ? (BDEV_R_BIT | BDEV_W_BIT) : BDEV_R_BIT; m.m_lbdev_lblockdriver_msg.id = 0; (void) ipc_sendrec(driver_endpt, &m); } static int sendrec_driver(message *m_ptr, ssize_t exp, result_t *res) { /* Make a call to the driver, and perform basic checks on the return * message. Fill in the result structure, wiping out what was in there * before. If the driver dies in the process, attempt to recover but * fail the request. */ message m_orig; endpoint_t last_endpt; int i, r; m_orig = *m_ptr; r = ipc_sendrec(driver_endpt, m_ptr); if (r == EDEADSRCDST) { /* The driver has died. Find its new endpoint, and reopen all * devices that we opened earlier. Then return failure. */ output("WARNING: driver has died, attempting to proceed\n"); driver_deaths++; /* Keep trying until we get a new endpoint. */ last_endpt = driver_endpt; for (;;) { r = ds_retrieve_label_endpt(driver_label, &driver_endpt); if (r == OK && last_endpt != driver_endpt) break; micro_delay(100000); } for (i = 0; i < nr_opened; i++) reopen_device(opened[i]); return set_result(res, RESULT_DEATH, 0); } if (r != OK) return set_result(res, RESULT_COMMFAIL, r); if (m_ptr->m_type != BDEV_REPLY) return set_result(res, RESULT_BADTYPE, m_ptr->m_type); if (m_ptr->m_lblockdriver_lbdev_reply.id != m_orig.m_lbdev_lblockdriver_msg.id) return set_result(res, RESULT_BADID, m_ptr->m_lblockdriver_lbdev_reply.id); if ((exp < 0 && m_ptr->m_lblockdriver_lbdev_reply.status >= 0) || (exp >= 0 && m_ptr->m_lblockdriver_lbdev_reply.status < 0)) return set_result(res, RESULT_BADSTATUS, m_ptr->m_lblockdriver_lbdev_reply.status); return set_result(res, RESULT_OK, 0); } static void raw_xfer(dev_t minor, u64_t pos, iovec_s_t *iovec, int nr_req, int write, ssize_t exp, result_t *res) { /* Perform a transfer with a safecopy iovec already supplied. */ cp_grant_id_t grant; message m; int r; assert(nr_req <= NR_IOREQS); assert(!write || may_write); if ((grant = cpf_grant_direct(driver_endpt, (vir_bytes) iovec, sizeof(*iovec) * nr_req, CPF_READ)) == GRANT_INVALID) panic("unable to allocate grant"); memset(&m, 0, sizeof(m)); m.m_type = write ? BDEV_SCATTER : BDEV_GATHER; m.m_lbdev_lblockdriver_msg.minor = minor; m.m_lbdev_lblockdriver_msg.pos = pos; m.m_lbdev_lblockdriver_msg.count = nr_req; m.m_lbdev_lblockdriver_msg.grant = grant; m.m_lbdev_lblockdriver_msg.id = lrand48(); r = sendrec_driver(&m, exp, res); if (cpf_revoke(grant) != OK) panic("unable to revoke grant"); if (r != RESULT_OK) return; if (m.m_lblockdriver_lbdev_reply.status == exp) return; if (exp < 0) set_result(res, RESULT_BADSTATUS, m.m_lblockdriver_lbdev_reply.status); else set_result(res, RESULT_TRUNC, exp - m.m_lblockdriver_lbdev_reply.status); } static void vir_xfer(dev_t minor, u64_t pos, iovec_t *iovec, int nr_req, int write, ssize_t exp, result_t *res) { /* Perform a transfer, creating and revoking grants for the I/O vector. */ iovec_s_t iov_s[NR_IOREQS]; int i; assert(nr_req <= NR_IOREQS); for (i = 0; i < nr_req; i++) { iov_s[i].iov_size = iovec[i].iov_size; if ((iov_s[i].iov_grant = cpf_grant_direct(driver_endpt, (vir_bytes) iovec[i].iov_addr, iovec[i].iov_size, write ? CPF_READ : CPF_WRITE)) == GRANT_INVALID) panic("unable to allocate grant"); } raw_xfer(minor, pos, iov_s, nr_req, write, exp, res); for (i = 0; i < nr_req; i++) { iovec[i].iov_size = iov_s[i].iov_size; if (cpf_revoke(iov_s[i].iov_grant) != OK) panic("unable to revoke grant"); } } static void simple_xfer(dev_t minor, u64_t pos, u8_t *buf, size_t size, int write, ssize_t exp, result_t *res) { /* Perform a transfer involving a single buffer. */ iovec_t iov; iov.iov_addr = (vir_bytes) buf; iov.iov_size = size; vir_xfer(minor, pos, &iov, 1, write, exp, res); } static void alloc_buf_and_grant(u8_t **ptr, cp_grant_id_t *grant, size_t size, int perms) { /* Allocate a buffer suitable for DMA (i.e. contiguous) and create a * grant for it with the requested CPF_* grant permissions. */ *ptr = alloc_dma_memory(size); if ((*grant = cpf_grant_direct(driver_endpt, (vir_bytes) *ptr, size, perms)) == GRANT_INVALID) panic("unable to allocate grant"); } static void free_buf_and_grant(u8_t *ptr, cp_grant_id_t grant, size_t size) { /* Revoke a grant and free a buffer. */ cpf_revoke(grant); free_dma_memory(ptr, size); } static void bad_read1(void) { /* Test various illegal read transfer requests, part 1. */ message mt, m; iovec_s_t iovt, iov; cp_grant_id_t grant, grant2, grant3; u8_t *buf_ptr; vir_bytes buf_size; result_t res; test_group("bad read requests, part one", TRUE); #define BUF_SIZE 4096 buf_size = BUF_SIZE; alloc_buf_and_grant(&buf_ptr, &grant2, buf_size, CPF_WRITE); if ((grant = cpf_grant_direct(driver_endpt, (vir_bytes) &iov, sizeof(iov), CPF_READ)) == GRANT_INVALID) panic("unable to allocate grant"); /* Initialize the defaults for some of the tests. * This is a legitimate request for the first block of the partition. */ memset(&mt, 0, sizeof(mt)); mt.m_type = BDEV_GATHER; mt.m_lbdev_lblockdriver_msg.minor = driver_minor; mt.m_lbdev_lblockdriver_msg.pos = 0LL; mt.m_lbdev_lblockdriver_msg.count = 1; mt.m_lbdev_lblockdriver_msg.grant = grant; mt.m_lbdev_lblockdriver_msg.id = lrand48(); memset(&iovt, 0, sizeof(iovt)); iovt.iov_grant = grant2; iovt.iov_size = buf_size; /* Test normal request. */ m = mt; iov = iovt; sendrec_driver(&m, OK, &res); if (res.type == RESULT_OK && m.m_lblockdriver_lbdev_reply.status != (ssize_t) iov.iov_size) { res.type = RESULT_TRUNC; res.value = m.m_lblockdriver_lbdev_reply.status; } got_result(&res, "normal request"); /* Test zero iovec elements. */ m = mt; iov = iovt; m.m_lbdev_lblockdriver_msg.count = 0; sendrec_driver(&m, EINVAL, &res); got_result(&res, "zero iovec elements"); /* Test bad iovec grant. */ m = mt; m.m_lbdev_lblockdriver_msg.grant = GRANT_INVALID; sendrec_driver(&m, EINVAL, &res); got_result(&res, "bad iovec grant"); /* Test revoked iovec grant. */ m = mt; iov = iovt; if ((grant3 = cpf_grant_direct(driver_endpt, (vir_bytes) &iov, sizeof(iov), CPF_READ)) == GRANT_INVALID) panic("unable to allocate grant"); cpf_revoke(grant3); m.m_lbdev_lblockdriver_msg.grant = grant3; sendrec_driver(&m, EINVAL, &res); accept_result(&res, RESULT_BADSTATUS, EPERM); got_result(&res, "revoked iovec grant"); /* Test normal request (final check). */ m = mt; iov = iovt; sendrec_driver(&m, OK, &res); if (res.type == RESULT_OK && m.m_lblockdriver_lbdev_reply.status != (ssize_t) iov.iov_size) { res.type = RESULT_TRUNC; res.value = m.m_lblockdriver_lbdev_reply.status; } got_result(&res, "normal request"); /* Clean up. */ free_buf_and_grant(buf_ptr, grant2, buf_size); cpf_revoke(grant); } static u32_t get_sum(u8_t *ptr, size_t size) { /* Compute a checksum over the given buffer. */ u32_t sum; for (sum = 0; size > 0; size--, ptr++) sum = sum ^ (sum << 5) ^ *ptr; return sum; } static u32_t fill_rand(u8_t *ptr, size_t size) { /* Fill the given buffer with random data. Return a checksum over the * resulting data. */ size_t i; for (i = 0; i < size; i++) ptr[i] = lrand48() % 256; return get_sum(ptr, size); } static void test_sum(u8_t *ptr, size_t size, u32_t sum, int should_match, result_t *res) { /* If the test succeeded so far, check whether the given buffer does * or does not match the given checksum, and adjust the test result * accordingly. */ u32_t sum2; if (res->type != RESULT_OK) return; sum2 = get_sum(ptr, size); if ((sum == sum2) != should_match) { res->type = should_match ? RESULT_CORRUPT : RESULT_MISSING; res->value = 0; /* not much that's useful here */ } } static void bad_read2(void) { /* Test various illegal read transfer requests, part 2. * * Consider allowing this test to be run twice, with different buffer * sizes. It appears that we can make at_wini misbehave by making the * size exceed the per-operation size (128KB ?). On the other hand, we * then need to start checking partition sizes, possibly. */ u8_t *buf_ptr, *buf2_ptr, *buf3_ptr, c1, c2; size_t buf_size, buf2_size, buf3_size; cp_grant_id_t buf_grant, buf2_grant, buf3_grant, grant; u32_t buf_sum, buf2_sum, buf3_sum; iovec_s_t iov[3], iovt[3]; result_t res; test_group("bad read requests, part two", TRUE); buf_size = buf2_size = buf3_size = BUF_SIZE; alloc_buf_and_grant(&buf_ptr, &buf_grant, buf_size, CPF_WRITE); alloc_buf_and_grant(&buf2_ptr, &buf2_grant, buf2_size, CPF_WRITE); alloc_buf_and_grant(&buf3_ptr, &buf3_grant, buf3_size, CPF_WRITE); iovt[0].iov_grant = buf_grant; iovt[0].iov_size = buf_size; iovt[1].iov_grant = buf2_grant; iovt[1].iov_size = buf2_size; iovt[2].iov_grant = buf3_grant; iovt[2].iov_size = buf3_size; /* Test normal vector request. */ memcpy(iov, iovt, sizeof(iovt)); buf_sum = fill_rand(buf_ptr, buf_size); buf2_sum = fill_rand(buf2_ptr, buf2_size); buf3_sum = fill_rand(buf3_ptr, buf3_size); raw_xfer(driver_minor, 0ULL, iov, 3, FALSE, buf_size + buf2_size + buf3_size, &res); test_sum(buf_ptr, buf_size, buf_sum, FALSE, &res); test_sum(buf2_ptr, buf2_size, buf2_sum, FALSE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, FALSE, &res); got_result(&res, "normal vector request"); /* Test zero sized iovec element. */ memcpy(iov, iovt, sizeof(iovt)); iov[1].iov_size = 0; buf_sum = fill_rand(buf_ptr, buf_size); buf2_sum = fill_rand(buf2_ptr, buf2_size); buf3_sum = fill_rand(buf3_ptr, buf3_size); raw_xfer(driver_minor, 0ULL, iov, 3, FALSE, EINVAL, &res); test_sum(buf_ptr, buf_size, buf_sum, TRUE, &res); test_sum(buf2_ptr, buf2_size, buf2_sum, TRUE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, TRUE, &res); got_result(&res, "zero size in iovec element"); /* Test negative sized iovec element. */ memcpy(iov, iovt, sizeof(iovt)); iov[1].iov_size = (vir_bytes) LONG_MAX + 1; raw_xfer(driver_minor, 0ULL, iov, 3, FALSE, EINVAL, &res); test_sum(buf_ptr, buf_size, buf_sum, TRUE, &res); test_sum(buf2_ptr, buf2_size, buf2_sum, TRUE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, TRUE, &res); got_result(&res, "negative size in iovec element"); /* Test iovec with negative total size. */ memcpy(iov, iovt, sizeof(iovt)); iov[0].iov_size = LONG_MAX / 2 - 1; iov[1].iov_size = LONG_MAX / 2 - 1; raw_xfer(driver_minor, 0ULL, iov, 3, FALSE, EINVAL, &res); test_sum(buf_ptr, buf_size, buf_sum, TRUE, &res); test_sum(buf2_ptr, buf2_size, buf2_sum, TRUE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, TRUE, &res); got_result(&res, "negative total size"); /* Test iovec with wrapping total size. */ memcpy(iov, iovt, sizeof(iovt)); iov[0].iov_size = LONG_MAX - 1; iov[1].iov_size = LONG_MAX - 1; raw_xfer(driver_minor, 0ULL, iov, 3, FALSE, EINVAL, &res); test_sum(buf_ptr, buf_size, buf_sum, TRUE, &res); test_sum(buf2_ptr, buf2_size, buf2_sum, TRUE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, TRUE, &res); got_result(&res, "wrapping total size"); /* Test word-unaligned iovec element size. */ memcpy(iov, iovt, sizeof(iovt)); iov[1].iov_size--; buf_sum = fill_rand(buf_ptr, buf_size); buf2_sum = fill_rand(buf2_ptr, buf2_size); buf3_sum = fill_rand(buf3_ptr, buf3_size); c1 = buf2_ptr[buf2_size - 1]; raw_xfer(driver_minor, 0ULL, iov, 3, FALSE, BUF_SIZE * 3 - 1, &res); if (accept_result(&res, RESULT_BADSTATUS, EINVAL)) { /* Do not test the first buffer, as it may contain a partial * result. */ test_sum(buf2_ptr, buf2_size, buf2_sum, TRUE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, TRUE, &res); } else { test_sum(buf_ptr, buf_size, buf_sum, FALSE, &res); test_sum(buf2_ptr, buf2_size, buf2_sum, FALSE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, FALSE, &res); if (c1 != buf2_ptr[buf2_size - 1]) set_result(&res, RESULT_CORRUPT, 0); } got_result(&res, "word-unaligned size in iovec element"); /* Test invalid grant in iovec element. */ memcpy(iov, iovt, sizeof(iovt)); iov[1].iov_grant = GRANT_INVALID; fill_rand(buf_ptr, buf_size); buf2_sum = fill_rand(buf2_ptr, buf2_size); buf3_sum = fill_rand(buf3_ptr, buf3_size); raw_xfer(driver_minor, 0ULL, iov, 3, FALSE, EINVAL, &res); /* Do not test the first buffer, as it may contain a partial result. */ test_sum(buf2_ptr, buf2_size, buf2_sum, TRUE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, TRUE, &res); got_result(&res, "invalid grant in iovec element"); /* Test revoked grant in iovec element. */ memcpy(iov, iovt, sizeof(iovt)); if ((grant = cpf_grant_direct(driver_endpt, (vir_bytes) buf2_ptr, buf2_size, CPF_WRITE)) == GRANT_INVALID) panic("unable to allocate grant"); cpf_revoke(grant); iov[1].iov_grant = grant; buf_sum = fill_rand(buf_ptr, buf_size); buf2_sum = fill_rand(buf2_ptr, buf2_size); buf3_sum = fill_rand(buf3_ptr, buf3_size); raw_xfer(driver_minor, 0ULL, iov, 3, FALSE, EINVAL, &res); accept_result(&res, RESULT_BADSTATUS, EPERM); /* Do not test the first buffer, as it may contain a partial result. */ test_sum(buf2_ptr, buf2_size, buf2_sum, TRUE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, TRUE, &res); got_result(&res, "revoked grant in iovec element"); /* Test read-only grant in iovec element. */ memcpy(iov, iovt, sizeof(iovt)); if ((grant = cpf_grant_direct(driver_endpt, (vir_bytes) buf2_ptr, buf2_size, CPF_READ)) == GRANT_INVALID) panic("unable to allocate grant"); iov[1].iov_grant = grant; buf_sum = fill_rand(buf_ptr, buf_size); buf2_sum = fill_rand(buf2_ptr, buf2_size); buf3_sum = fill_rand(buf3_ptr, buf3_size); raw_xfer(driver_minor, 0ULL, iov, 3, FALSE, EINVAL, &res); accept_result(&res, RESULT_BADSTATUS, EPERM); /* Do not test the first buffer, as it may contain a partial result. */ test_sum(buf2_ptr, buf2_size, buf2_sum, TRUE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, TRUE, &res); got_result(&res, "read-only grant in iovec element"); cpf_revoke(grant); /* Test word-unaligned iovec element buffer. */ memcpy(iov, iovt, sizeof(iovt)); if ((grant = cpf_grant_direct(driver_endpt, (vir_bytes) (buf2_ptr + 1), buf2_size - 2, CPF_WRITE)) == GRANT_INVALID) panic("unable to allocate grant"); iov[1].iov_grant = grant; iov[1].iov_size = buf2_size - 2; buf_sum = fill_rand(buf_ptr, buf_size); buf2_sum = fill_rand(buf2_ptr, buf2_size); buf3_sum = fill_rand(buf3_ptr, buf3_size); c1 = buf2_ptr[0]; c2 = buf2_ptr[buf2_size - 1]; raw_xfer(driver_minor, 0ULL, iov, 3, FALSE, BUF_SIZE * 3 - 2, &res); if (accept_result(&res, RESULT_BADSTATUS, EINVAL)) { /* Do not test the first buffer, as it may contain a partial * result. */ test_sum(buf2_ptr, buf2_size, buf2_sum, TRUE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, TRUE, &res); } else { test_sum(buf_ptr, buf_size, buf_sum, FALSE, &res); test_sum(buf2_ptr, buf2_size, buf2_sum, FALSE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, FALSE, &res); if (c1 != buf2_ptr[0] || c2 != buf2_ptr[buf2_size - 1]) set_result(&res, RESULT_CORRUPT, 0); } got_result(&res, "word-unaligned buffer in iovec element"); cpf_revoke(grant); /* Test word-unaligned position. */ /* Only perform this test if the minimum read size is not 1, in which * case it is safe to assume that the driver expects no position * alignment either. These tests are indeed not exhaustive yet. For now * we assume that if no alignment is required at all, the driver does * not implement special logic to achieve this, so we don't need to * test all possible positions and sizes either (yes, laziness..). */ if (min_read > 1) { memcpy(iov, iovt, sizeof(iovt)); buf_sum = fill_rand(buf_ptr, buf_size); buf2_sum = fill_rand(buf2_ptr, buf2_size); buf3_sum = fill_rand(buf3_ptr, buf3_size); raw_xfer(driver_minor, 1ULL, iov, 3, FALSE, EINVAL, &res); test_sum(buf_ptr, buf_size, buf_sum, TRUE, &res); test_sum(buf2_ptr, buf2_size, buf2_sum, TRUE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, TRUE, &res); got_result(&res, "word-unaligned position"); } /* Test normal vector request (final check). */ memcpy(iov, iovt, sizeof(iovt)); buf_sum = fill_rand(buf_ptr, buf_size); buf2_sum = fill_rand(buf2_ptr, buf2_size); buf3_sum = fill_rand(buf3_ptr, buf3_size); raw_xfer(driver_minor, 0ULL, iov, 3, FALSE, buf_size + buf2_size + buf3_size, &res); test_sum(buf_ptr, buf_size, buf_sum, FALSE, &res); test_sum(buf2_ptr, buf2_size, buf2_sum, FALSE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, FALSE, &res); got_result(&res, "normal vector request"); /* Clean up. */ free_buf_and_grant(buf3_ptr, buf3_grant, buf3_size); free_buf_and_grant(buf2_ptr, buf2_grant, buf2_size); free_buf_and_grant(buf_ptr, buf_grant, buf_size); } static void bad_write(void) { /* Test various illegal write transfer requests, if writing is allowed. * If handled correctly, these requests will not actually write data. * This part of the test set is in need of further expansion. */ u8_t *buf_ptr, *buf2_ptr, *buf3_ptr; size_t buf_size, buf2_size, buf3_size, sector_unalign; cp_grant_id_t buf_grant, buf2_grant, buf3_grant; cp_grant_id_t grant; u32_t buf_sum, buf2_sum, buf3_sum; iovec_s_t iov[3], iovt[3]; result_t res; test_group("bad write requests", may_write); if (!may_write) return; buf_size = buf2_size = buf3_size = BUF_SIZE; alloc_buf_and_grant(&buf_ptr, &buf_grant, buf_size, CPF_READ); alloc_buf_and_grant(&buf2_ptr, &buf2_grant, buf2_size, CPF_READ); alloc_buf_and_grant(&buf3_ptr, &buf3_grant, buf3_size, CPF_READ); iovt[0].iov_grant = buf_grant; iovt[0].iov_size = buf_size; iovt[1].iov_grant = buf2_grant; iovt[1].iov_size = buf2_size; iovt[2].iov_grant = buf3_grant; iovt[2].iov_size = buf3_size; /* Only perform write alignment tests if writes require alignment. */ if (min_write == 0) min_write = sector_size; if (min_write > 1) { /* If min_write is larger than 2, use 2 as sector-unaligned * size, as word-unaligned values (e.g., 1) may be filtered out * on another code path. */ sector_unalign = (min_write > 2) ? 2 : 1; /* Test sector-unaligned write position. */ memcpy(iov, iovt, sizeof(iovt)); buf_sum = fill_rand(buf_ptr, buf_size); buf2_sum = fill_rand(buf2_ptr, buf2_size); buf3_sum = fill_rand(buf3_ptr, buf3_size); raw_xfer(driver_minor, (u64_t)sector_unalign, iov, 3, TRUE, EINVAL, &res); test_sum(buf_ptr, buf_size, buf_sum, TRUE, &res); test_sum(buf2_ptr, buf2_size, buf2_sum, TRUE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, TRUE, &res); got_result(&res, "sector-unaligned write position"); /* Test sector-unaligned write size. */ memcpy(iov, iovt, sizeof(iovt)); iov[1].iov_size -= sector_unalign; buf_sum = fill_rand(buf_ptr, buf_size); buf2_sum = fill_rand(buf2_ptr, buf2_size); buf3_sum = fill_rand(buf3_ptr, buf3_size); raw_xfer(driver_minor, 0ULL, iov, 3, TRUE, EINVAL, &res); test_sum(buf_ptr, buf_size, buf_sum, TRUE, &res); test_sum(buf2_ptr, buf2_size, buf2_sum, TRUE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, TRUE, &res); got_result(&res, "sector-unaligned write size"); } /* Test write-only grant in iovec element. */ memcpy(iov, iovt, sizeof(iovt)); if ((grant = cpf_grant_direct(driver_endpt, (vir_bytes) buf2_ptr, buf2_size, CPF_WRITE)) == GRANT_INVALID) panic("unable to allocate grant"); iov[1].iov_grant = grant; buf_sum = fill_rand(buf_ptr, buf_size); buf2_sum = fill_rand(buf2_ptr, buf2_size); buf3_sum = fill_rand(buf3_ptr, buf3_size); raw_xfer(driver_minor, 0ULL, iov, 3, TRUE, EINVAL, &res); accept_result(&res, RESULT_BADSTATUS, EPERM); test_sum(buf_ptr, buf_size, buf_sum, TRUE, &res); test_sum(buf2_ptr, buf2_size, buf2_sum, TRUE, &res); test_sum(buf3_ptr, buf3_size, buf3_sum, TRUE, &res); got_result(&res, "write-only grant in iovec element"); cpf_revoke(grant); /* Clean up. */ free_buf_and_grant(buf3_ptr, buf3_grant, buf3_size); free_buf_and_grant(buf2_ptr, buf2_grant, buf2_size); free_buf_and_grant(buf_ptr, buf_grant, buf_size); } static void vector_and_large_sub(size_t small_size) { /* Check whether large vectored requests, and large single requests, * succeed. */ size_t large_size, buf_size, buf2_size; u8_t *buf_ptr, *buf2_ptr; iovec_t iovec[NR_IOREQS]; u64_t base_pos; result_t res; int i; base_pos = (u64_t)sector_size; large_size = small_size * NR_IOREQS; buf_size = large_size + sizeof(u32_t) * 2; buf2_size = large_size + sizeof(u32_t) * (NR_IOREQS + 1); buf_ptr = alloc_dma_memory(buf_size); buf2_ptr = alloc_dma_memory(buf2_size); /* The first buffer has one large chunk with dword-sized guards on each * side. LPTR(n) points to the start of the nth small data chunk within * the large chunk. The second buffer contains several small chunks. It * has dword-sized guards before each chunk and after the last chunk. * SPTR(n) points to the start of the nth small chunk. */ #define SPTR(n) (buf2_ptr + sizeof(u32_t) + (n) * (sizeof(u32_t) + small_size)) #define LPTR(n) (buf_ptr + sizeof(u32_t) + small_size * (n)) /* Write one large chunk, if writing is allowed. */ if (may_write) { fill_rand(buf_ptr, buf_size); /* don't need the checksum */ iovec[0].iov_addr = (vir_bytes) (buf_ptr + sizeof(u32_t)); iovec[0].iov_size = large_size; vir_xfer(driver_minor, base_pos, iovec, 1, TRUE, large_size, &res); got_result(&res, "large write"); } /* Read back in many small chunks. If writing is not allowed, do not * check checksums. */ for (i = 0; i < NR_IOREQS; i++) { * (((u32_t *) SPTR(i)) - 1) = 0xDEADBEEFL + i; iovec[i].iov_addr = (vir_bytes) SPTR(i); iovec[i].iov_size = small_size; } * (((u32_t *) SPTR(i)) - 1) = 0xFEEDFACEL; vir_xfer(driver_minor, base_pos, iovec, NR_IOREQS, FALSE, large_size, &res); if (res.type == RESULT_OK) { for (i = 0; i < NR_IOREQS; i++) { if (* (((u32_t *) SPTR(i)) - 1) != 0xDEADBEEFL + i) set_result(&res, RESULT_OVERFLOW, 0); } if (* (((u32_t *) SPTR(i)) - 1) != 0xFEEDFACEL) set_result(&res, RESULT_OVERFLOW, 0); } if (res.type == RESULT_OK && may_write) { for (i = 0; i < NR_IOREQS; i++) { test_sum(SPTR(i), small_size, get_sum(LPTR(i), small_size), TRUE, &res); } } got_result(&res, "vectored read"); /* Write new data in many small chunks, if writing is allowed. */ if (may_write) { fill_rand(buf2_ptr, buf2_size); /* don't need the checksum */ for (i = 0; i < NR_IOREQS; i++) { iovec[i].iov_addr = (vir_bytes) SPTR(i); iovec[i].iov_size = small_size; } vir_xfer(driver_minor, base_pos, iovec, NR_IOREQS, TRUE, large_size, &res); got_result(&res, "vectored write"); } /* Read back in one large chunk. If writing is allowed, the checksums * must match the last write; otherwise, they must match the last read. * In both cases, the expected content is in the second buffer. */ * (u32_t *) buf_ptr = 0xCAFEBABEL; * (u32_t *) (buf_ptr + sizeof(u32_t) + large_size) = 0xDECAFBADL; iovec[0].iov_addr = (vir_bytes) (buf_ptr + sizeof(u32_t)); iovec[0].iov_size = large_size; vir_xfer(driver_minor, base_pos, iovec, 1, FALSE, large_size, &res); if (res.type == RESULT_OK) { if (* (u32_t *) buf_ptr != 0xCAFEBABEL) set_result(&res, RESULT_OVERFLOW, 0); if (* (u32_t *) (buf_ptr + sizeof(u32_t) + large_size) != 0xDECAFBADL) set_result(&res, RESULT_OVERFLOW, 0); } if (res.type == RESULT_OK) { for (i = 0; i < NR_IOREQS; i++) { test_sum(SPTR(i), small_size, get_sum(LPTR(i), small_size), TRUE, &res); } } got_result(&res, "large read"); #undef LPTR #undef SPTR /* Clean up. */ free_dma_memory(buf2_ptr, buf2_size); free_dma_memory(buf_ptr, buf_size); } static void vector_and_large(void) { /* Check whether large vectored requests, and large single requests, * succeed. These are request patterns commonly used by MFS and the * filter driver, respectively. We try the same test twice: once with * a common block size, and once to push against the max request size. */ size_t max_block; /* Make sure that the maximum size does not exceed the target device * size, minus the margins we need for testing here and there. */ if (max_size > part.size - sector_size * 4) max_size = part.size - sector_size * 4; /* Compute the largest sector multiple which, when multiplied by * NR_IOREQS, is no more than the maximum transfer size. Note that if * max_size is not a multiple of sector_size, we're not going up to the * limit entirely this way. */ max_block = max_size / NR_IOREQS; max_block -= max_block % sector_size; #define COMMON_BLOCK_SIZE 4096 test_group("vector and large, common block", TRUE); vector_and_large_sub(COMMON_BLOCK_SIZE); if (max_block != COMMON_BLOCK_SIZE) { test_group("vector and large, large block", TRUE); vector_and_large_sub(max_block); } } static void open_device(dev_t minor) { /* Open a partition or subpartition. Remember that it has been opened, * so that we can reopen it later in the event of a driver crash. */ message m; result_t res; memset(&m, 0, sizeof(m)); m.m_type = BDEV_OPEN; m.m_lbdev_lblockdriver_msg.minor = minor; m.m_lbdev_lblockdriver_msg.access = may_write ? (BDEV_R_BIT | BDEV_W_BIT) : BDEV_R_BIT; m.m_lbdev_lblockdriver_msg.id = lrand48(); sendrec_driver(&m, OK, &res); /* We assume that this call is supposed to succeed. We pretend it * always succeeds, so that close_device() won't get confused later. */ assert(nr_opened < NR_OPENED); opened[nr_opened++] = minor; got_result(&res, minor == driver_minor ? "opening the main partition" : "opening a subpartition"); } static void close_device(dev_t minor) { /* Close a partition or subpartition. Remove it from the list of opened * devices. */ message m; result_t res; int i; memset(&m, 0, sizeof(m)); m.m_type = BDEV_CLOSE; m.m_lbdev_lblockdriver_msg.minor = minor; m.m_lbdev_lblockdriver_msg.id = lrand48(); sendrec_driver(&m, OK, &res); assert(nr_opened > 0); for (i = 0; i < nr_opened; i++) { if (opened[i] == minor) { opened[i] = opened[--nr_opened]; break; } } got_result(&res, minor == driver_minor ? "closing the main partition" : "closing a subpartition"); } static int vir_ioctl(dev_t minor, int req, void *ptr, ssize_t exp, result_t *res) { /* Perform an I/O control request, using a local buffer. */ cp_grant_id_t grant; message m; int r, perm; assert(!_MINIX_IOCTL_BIG(req)); /* not supported */ perm = 0; if (_MINIX_IOCTL_IOR(req)) perm |= CPF_WRITE; if (_MINIX_IOCTL_IOW(req)) perm |= CPF_READ; if ((grant = cpf_grant_direct(driver_endpt, (vir_bytes) ptr, _MINIX_IOCTL_SIZE(req), perm)) == GRANT_INVALID) panic("unable to allocate grant"); memset(&m, 0, sizeof(m)); m.m_type = BDEV_IOCTL; m.m_lbdev_lblockdriver_msg.minor = minor; m.m_lbdev_lblockdriver_msg.request = req; m.m_lbdev_lblockdriver_msg.grant = grant; m.m_lbdev_lblockdriver_msg.user = NONE; m.m_lbdev_lblockdriver_msg.id = lrand48(); r = sendrec_driver(&m, exp, res); if (cpf_revoke(grant) != OK) panic("unable to revoke grant"); return r; } static void misc_ioctl(void) { /* Test some ioctls. */ result_t res; int openct; test_group("test miscellaneous ioctls", TRUE); /* Retrieve the main partition's base and size. Save for later. */ vir_ioctl(driver_minor, DIOCGETP, &part, OK, &res); got_result(&res, "ioctl to get partition"); /* The other tests do not check whether there is sufficient room. */ if (res.type == RESULT_OK && part.size < (u64_t)max_size * 2) output("WARNING: small partition, some tests may fail\n"); /* Test retrieving global driver open count. */ openct = 0x0badcafe; vir_ioctl(driver_minor, DIOCOPENCT, &openct, OK, &res); /* We assume that we're the only client to the driver right now. */ if (res.type == RESULT_OK && openct != 1) { res.type = RESULT_BADVALUE; res.value = openct; } got_result(&res, "ioctl to get open count"); /* Test increasing and re-retrieving open count. */ open_device(driver_minor); openct = 0x0badcafe; vir_ioctl(driver_minor, DIOCOPENCT, &openct, OK, &res); if (res.type == RESULT_OK && openct != 2) { res.type = RESULT_BADVALUE; res.value = openct; } got_result(&res, "increased open count after opening"); /* Test decreasing and re-retrieving open count. */ close_device(driver_minor); openct = 0x0badcafe; vir_ioctl(driver_minor, DIOCOPENCT, &openct, OK, &res); if (res.type == RESULT_OK && openct != 1) { res.type = RESULT_BADVALUE; res.value = openct; } got_result(&res, "decreased open count after closing"); } static void read_limits(dev_t sub0_minor, dev_t sub1_minor, size_t sub_size) { /* Test reads up to, across, and beyond partition limits. */ u8_t *buf_ptr; size_t buf_size; u32_t sum, sum2, sum3; result_t res; test_group("read around subpartition limits", TRUE); buf_size = sector_size * 3; buf_ptr = alloc_dma_memory(buf_size); /* Read one sector up to the partition limit. */ fill_rand(buf_ptr, buf_size); simple_xfer(sub0_minor, (u64_t)sub_size - sector_size, buf_ptr, sector_size, FALSE, sector_size, &res); sum = get_sum(buf_ptr, sector_size); got_result(&res, "one sector read up to partition end"); /* Read three sectors up to the partition limit. */ fill_rand(buf_ptr, buf_size); simple_xfer(sub0_minor, (u64_t)sub_size - buf_size, buf_ptr, buf_size, FALSE, buf_size, &res); test_sum(buf_ptr + sector_size * 2, sector_size, sum, TRUE, &res); sum2 = get_sum(buf_ptr + sector_size, sector_size * 2); got_result(&res, "multisector read up to partition end"); /* Read three sectors, two up to and one beyond the partition end. */ fill_rand(buf_ptr, buf_size); sum3 = get_sum(buf_ptr + sector_size * 2, sector_size); simple_xfer(sub0_minor, (u64_t)sub_size - sector_size * 2, buf_ptr, buf_size, FALSE, sector_size * 2, &res); test_sum(buf_ptr, sector_size * 2, sum2, TRUE, &res); test_sum(buf_ptr + sector_size * 2, sector_size, sum3, TRUE, &res); got_result(&res, "read somewhat across partition end"); /* Read three sectors, one up to and two beyond the partition end. */ fill_rand(buf_ptr, buf_size); sum2 = get_sum(buf_ptr + sector_size, sector_size * 2); simple_xfer(sub0_minor, (u64_t)sub_size - sector_size, buf_ptr, buf_size, FALSE, sector_size, &res); test_sum(buf_ptr, sector_size, sum, TRUE, &res); test_sum(buf_ptr + sector_size, sector_size * 2, sum2, TRUE, &res); got_result(&res, "read mostly across partition end"); /* Read one sector starting at the partition end. */ sum = fill_rand(buf_ptr, buf_size); sum2 = get_sum(buf_ptr, sector_size); simple_xfer(sub0_minor, (u64_t)sub_size, buf_ptr, sector_size, FALSE, 0, &res); test_sum(buf_ptr, sector_size, sum2, TRUE, &res); got_result(&res, "one sector read at partition end"); /* Read three sectors starting at the partition end. */ simple_xfer(sub0_minor, (u64_t)sub_size, buf_ptr, buf_size, FALSE, 0, &res); test_sum(buf_ptr, buf_size, sum, TRUE, &res); got_result(&res, "multisector read at partition end"); /* Read one sector beyond the partition end. */ simple_xfer(sub0_minor, (u64_t)sub_size + sector_size, buf_ptr, buf_size, FALSE, 0, &res); test_sum(buf_ptr, sector_size, sum2, TRUE, &res); got_result(&res, "single sector read beyond partition end"); /* Read three sectors way beyond the partition end. */ simple_xfer(sub0_minor, 0x1000000000000000ULL, buf_ptr, buf_size, FALSE, 0, &res); test_sum(buf_ptr, buf_size, sum, TRUE, &res); /* Test negative offsets. This request should return EOF or fail; we * assume that it return EOF here (because that is what the AHCI driver * does, to avoid producing errors for requests close to the 2^64 byte * position limit [yes, this will indeed never happen anyway]). This is * more or less a bad requests test, but we cannot do it without * setting up subpartitions first. */ simple_xfer(sub1_minor, 0xffffffffffffffffULL - sector_size + 1, buf_ptr, sector_size, FALSE, 0, &res); test_sum(buf_ptr, sector_size, sum2, TRUE, &res); got_result(&res, "read with negative offset"); /* Clean up. */ free_dma_memory(buf_ptr, buf_size); } static void write_limits(dev_t sub0_minor, dev_t sub1_minor, size_t sub_size) { /* Test writes up to, across, and beyond partition limits. Use the * first given subpartition to test, and the second to make sure there * are no overruns. The given size is the size of each of the * subpartitions. Note that the necessity to check the results using * readback, makes this more or less a superset of the read test. */ u8_t *buf_ptr; size_t buf_size; u32_t sum, sum2, sum3, sub1_sum; result_t res; test_group("write around subpartition limits", may_write); if (!may_write) return; buf_size = sector_size * 3; buf_ptr = alloc_dma_memory(buf_size); /* Write to the start of the second subpartition, so that we can * reliably check whether the contents have changed later. */ sub1_sum = fill_rand(buf_ptr, buf_size); simple_xfer(sub1_minor, 0ULL, buf_ptr, buf_size, TRUE, buf_size, &res); got_result(&res, "write to second subpartition"); /* Write one sector, up to the partition limit. */ sum = fill_rand(buf_ptr, sector_size); simple_xfer(sub0_minor, (u64_t)sub_size - sector_size, buf_ptr, sector_size, TRUE, sector_size, &res); got_result(&res, "write up to partition end"); /* Read back to make sure the results have persisted. */ fill_rand(buf_ptr, sector_size * 2); simple_xfer(sub0_minor, (u64_t)sub_size - sector_size * 2, buf_ptr, sector_size * 2, FALSE, sector_size * 2, &res); test_sum(buf_ptr + sector_size, sector_size, sum, TRUE, &res); got_result(&res, "read up to partition end"); /* Write three sectors, two up to and one beyond the partition end. */ fill_rand(buf_ptr, buf_size); sum = get_sum(buf_ptr + sector_size, sector_size); sum3 = get_sum(buf_ptr, sector_size); simple_xfer(sub0_minor, (u64_t)sub_size - sector_size * 2, buf_ptr, buf_size, TRUE, sector_size * 2, &res); got_result(&res, "write somewhat across partition end"); /* Read three sectors, one up to and two beyond the partition end. */ fill_rand(buf_ptr, buf_size); sum2 = get_sum(buf_ptr + sector_size, sector_size * 2); simple_xfer(sub0_minor, (u64_t)sub_size - sector_size, buf_ptr, buf_size, FALSE, sector_size, &res); test_sum(buf_ptr, sector_size, sum, TRUE, &res); test_sum(buf_ptr + sector_size, sector_size * 2, sum2, TRUE, &res); got_result(&res, "read mostly across partition end"); /* Repeat this but with write and read start positions swapped. */ fill_rand(buf_ptr, buf_size); sum = get_sum(buf_ptr, sector_size); simple_xfer(sub0_minor, (u64_t)sub_size - sector_size, buf_ptr, buf_size, TRUE, sector_size, &res); got_result(&res, "write mostly across partition end"); fill_rand(buf_ptr, buf_size); sum2 = get_sum(buf_ptr + sector_size * 2, sector_size); simple_xfer(sub0_minor, (u64_t)sub_size - sector_size * 2, buf_ptr, buf_size, FALSE, sector_size * 2, &res); test_sum(buf_ptr, sector_size, sum3, TRUE, &res); test_sum(buf_ptr + sector_size, sector_size, sum, TRUE, &res); test_sum(buf_ptr + sector_size * 2, sector_size, sum2, TRUE, &res); got_result(&res, "read somewhat across partition end"); /* Write one sector at the end of the partition. */ fill_rand(buf_ptr, sector_size); simple_xfer(sub0_minor, (u64_t)sub_size, buf_ptr, sector_size, TRUE, 0, &res); got_result(&res, "write at partition end"); /* Write one sector beyond the end of the partition. */ simple_xfer(sub0_minor, (u64_t)sub_size + sector_size, buf_ptr, sector_size, TRUE, 0, &res); got_result(&res, "write beyond partition end"); /* Read from the start of the second subpartition, and see if it * matches what we wrote into it earlier. */ fill_rand(buf_ptr, buf_size); simple_xfer(sub1_minor, 0ULL, buf_ptr, buf_size, FALSE, buf_size, &res); test_sum(buf_ptr, buf_size, sub1_sum, TRUE, &res); got_result(&res, "read from second subpartition"); /* Test offset wrapping, but this time for writes. */ fill_rand(buf_ptr, sector_size); simple_xfer(sub1_minor, 0xffffffffffffffffULL - sector_size + 1, buf_ptr, sector_size, TRUE, 0, &res); got_result(&res, "write with negative offset"); /* If the last request erroneously succeeded, it would have overwritten * the last sector of the first subpartition. */ simple_xfer(sub0_minor, (u64_t)sub_size - sector_size, buf_ptr, sector_size, FALSE, sector_size, &res); test_sum(buf_ptr, sector_size, sum, TRUE, &res); got_result(&res, "read up to partition end"); /* Clean up. */ free_dma_memory(buf_ptr, buf_size); } static void vir_limits(dev_t sub0_minor, dev_t sub1_minor, int part_secs) { /* Create virtual, temporary subpartitions through the DIOCSETP ioctl, * and perform tests on the resulting subpartitions. */ struct part_geom subpart, subpart2; size_t sub_size; result_t res; test_group("virtual subpartition limits", TRUE); /* Open the subpartitions. This is somewhat dodgy; we rely on the * driver allowing this even if no subpartitions exist. We cannot do * this test without doing a DIOCSETP on an open subdevice, though. */ open_device(sub0_minor); open_device(sub1_minor); sub_size = sector_size * part_secs; /* Set, and check, the size of the first subpartition. */ subpart = part; subpart.size = (u64_t)sub_size; vir_ioctl(sub0_minor, DIOCSETP, &subpart, OK, &res); got_result(&res, "ioctl to set first subpartition"); vir_ioctl(sub0_minor, DIOCGETP, &subpart2, OK, &res); if (res.type == RESULT_OK && (subpart.base != subpart2.base || subpart.size != subpart2.size)) { res.type = RESULT_BADVALUE; res.value = 0; } got_result(&res, "ioctl to get first subpartition"); /* Set, and check, the base and size of the second subpartition. */ subpart = part; subpart.base += sub_size; subpart.size = (u64_t)sub_size; vir_ioctl(sub1_minor, DIOCSETP, &subpart, OK, &res); got_result(&res, "ioctl to set second subpartition"); vir_ioctl(sub1_minor, DIOCGETP, &subpart2, OK, &res); if (res.type == RESULT_OK && (subpart.base != subpart2.base || subpart.size != subpart2.size)) { res.type = RESULT_BADVALUE; res.value = 0; } got_result(&res, "ioctl to get second subpartition"); /* Perform the actual I/O tests. */ read_limits(sub0_minor, sub1_minor, sub_size); write_limits(sub0_minor, sub1_minor, sub_size); /* Clean up. */ close_device(sub1_minor); close_device(sub0_minor); } static void real_limits(dev_t sub0_minor, dev_t sub1_minor, int part_secs) { /* Create our own subpartitions by writing a partition table, and * perform tests on the resulting real subpartitions. */ u8_t *buf_ptr; size_t buf_size, sub_size; struct part_geom subpart; struct part_entry *entry; result_t res; test_group("real subpartition limits", may_write); if (!may_write) return; sub_size = sector_size * part_secs; /* Technically, we should be using 512 instead of sector_size in * various places, because even on CD-ROMs, the partition tables are * 512 bytes and the sector counts are based on 512-byte sectors in it. * We ignore this subtlety because CD-ROMs are assumed to be read-only * anyway. */ buf_size = sector_size; buf_ptr = alloc_dma_memory(buf_size); memset(buf_ptr, 0, buf_size); /* Write an invalid partition table. */ simple_xfer(driver_minor, 0ULL, buf_ptr, buf_size, TRUE, buf_size, &res); got_result(&res, "write of invalid partition table"); /* Get the disk driver to reread the partition table. This should * happen (at least) when the device is fully closed and then reopened. * The ioctl test already made sure that we're the only client. */ close_device(driver_minor); open_device(driver_minor); /* See if our changes are visible. We expect the subpartitions to have * a size of zero now, indicating that they're not there. For actual * subpartitions (as opposed to normal partitions), this requires the * driver to zero them out, because the partition code does not do so. */ open_device(sub0_minor); open_device(sub1_minor); vir_ioctl(sub0_minor, DIOCGETP, &subpart, 0, &res); if (res.type == RESULT_OK && subpart.size != 0) { res.type = RESULT_BADVALUE; res.value = ex64lo(subpart.size); } got_result(&res, "ioctl to get first subpartition"); vir_ioctl(sub1_minor, DIOCGETP, &subpart, 0, &res); if (res.type == RESULT_OK && subpart.size != 0) { res.type = RESULT_BADVALUE; res.value = ex64lo(subpart.size); } got_result(&res, "ioctl to get second subpartition"); close_device(sub1_minor); close_device(sub0_minor); /* Now write a valid partition table. */ memset(buf_ptr, 0, buf_size); entry = (struct part_entry *) &buf_ptr[PART_TABLE_OFF]; entry[0].sysind = MINIX_PART; entry[0].lowsec = part.base / sector_size + 1; entry[0].size = part_secs; entry[1].sysind = MINIX_PART; entry[1].lowsec = entry[0].lowsec + entry[0].size; entry[1].size = part_secs; buf_ptr[510] = 0x55; buf_ptr[511] = 0xAA; simple_xfer(driver_minor, 0ULL, buf_ptr, buf_size, TRUE, buf_size, &res); got_result(&res, "write of valid partition table"); /* Same as above. */ close_device(driver_minor); open_device(driver_minor); /* Again, see if our changes are visible. This time the proper base and * size should be there. */ open_device(sub0_minor); open_device(sub1_minor); vir_ioctl(sub0_minor, DIOCGETP, &subpart, 0, &res); if (res.type == RESULT_OK && (subpart.base != part.base + sector_size || subpart.size != (u64_t)part_secs * sector_size)) { res.type = RESULT_BADVALUE; res.value = 0; } got_result(&res, "ioctl to get first subpartition"); vir_ioctl(sub1_minor, DIOCGETP, &subpart, 0, &res); if (res.type == RESULT_OK && (subpart.base != part.base + (1 + part_secs) * sector_size || subpart.size != (u64_t)part_secs * sector_size)) { res.type = RESULT_BADVALUE; res.value = 0; } got_result(&res, "ioctl to get second subpartition"); /* Now perform the actual I/O tests. */ read_limits(sub0_minor, sub1_minor, sub_size); write_limits(sub0_minor, sub1_minor, sub_size); /* Clean up. */ close_device(sub0_minor); close_device(sub1_minor); free_dma_memory(buf_ptr, buf_size); } static void part_limits(void) { /* Test reads and writes up to, across, and beyond partition limits. * As a side effect, test reading and writing partition sizes and * rereading partition tables. */ dev_t par, sub0_minor, sub1_minor; /* First determine the first two subpartitions of the partition that we * are operating on. If we are already operating on a subpartition, we * cannot conduct this test. */ if (driver_minor >= MINOR_d0p0s0) { output("WARNING: operating on subpartition, " "skipping partition tests\n"); return; } par = driver_minor % DEV_PER_DRIVE; if (par > 0) /* adapted from libdriver's drvlib code */ sub0_minor = MINOR_d0p0s0 + ((driver_minor / DEV_PER_DRIVE) * NR_PARTITIONS + par - 1) * NR_PARTITIONS; else sub0_minor = driver_minor + 1; sub1_minor = sub0_minor + 1; #define PART_SECS 9 /* sectors in each partition. must be >= 4. */ /* First try the test with temporarily specified subpartitions. */ vir_limits(sub0_minor, sub1_minor, PART_SECS); /* Then, if we're allowed to write, try the test with real, persisted * subpartitions. */ real_limits(sub0_minor, sub1_minor, PART_SECS - 1); } static void unaligned_size_io(u64_t base_pos, u8_t *buf_ptr, size_t buf_size, u8_t *sec_ptr[2], int sectors, int pattern, u32_t ssum[5]) { /* Perform a single small-element I/O read, write, readback test. * The number of sectors and the pattern varies with each call. * The ssum array has to be updated to reflect the five sectors' * checksums on disk, if writing is enabled. Note that for */ iovec_t iov[3], iovt[3]; u32_t rsum[3]; result_t res; size_t total_size; int i, nr_req; base_pos += sector_size; total_size = sector_size * sectors; /* If the limit is two elements per sector, we cannot test three * elements in a single sector. */ if (sector_size / element_size == 2 && sectors == 1 && pattern == 2) return; /* Set up the buffers and I/O vector. We use different buffers for the * elements to minimize the chance that something "accidentally" goes * right, but that means we have to do memory copying to do checksum * computation. */ fill_rand(sec_ptr[0], sector_size); rsum[0] = get_sum(sec_ptr[0] + element_size, sector_size - element_size); fill_rand(buf_ptr, buf_size); switch (pattern) { case 0: /* First pattern: a small element on the left. */ iovt[0].iov_addr = (vir_bytes) sec_ptr[0]; iovt[0].iov_size = element_size; iovt[1].iov_addr = (vir_bytes) buf_ptr; iovt[1].iov_size = total_size - element_size; rsum[1] = get_sum(buf_ptr + iovt[1].iov_size, element_size); nr_req = 2; break; case 1: /* Second pattern: a small element on the right. */ iovt[0].iov_addr = (vir_bytes) buf_ptr; iovt[0].iov_size = total_size - element_size; rsum[1] = get_sum(buf_ptr + iovt[0].iov_size, element_size); iovt[1].iov_addr = (vir_bytes) sec_ptr[0]; iovt[1].iov_size = element_size; nr_req = 2; break; case 2: /* Third pattern: a small element on each side. */ iovt[0].iov_addr = (vir_bytes) sec_ptr[0]; iovt[0].iov_size = element_size; iovt[1].iov_addr = (vir_bytes) buf_ptr; iovt[1].iov_size = total_size - element_size * 2; rsum[1] = get_sum(buf_ptr + iovt[1].iov_size, element_size * 2); fill_rand(sec_ptr[1], sector_size); iovt[2].iov_addr = (vir_bytes) sec_ptr[1]; iovt[2].iov_size = element_size; rsum[2] = get_sum(sec_ptr[1] + element_size, sector_size - element_size); nr_req = 3; break; default: assert(0); } /* Perform a read with small elements, and test whether the result is * as expected. */ memcpy(iov, iovt, sizeof(iov)); vir_xfer(driver_minor, base_pos, iov, nr_req, FALSE, total_size, &res); test_sum(sec_ptr[0] + element_size, sector_size - element_size, rsum[0], TRUE, &res); switch (pattern) { case 0: test_sum(buf_ptr + iovt[1].iov_size, element_size, rsum[1], TRUE, &res); memmove(buf_ptr + element_size, buf_ptr, iovt[1].iov_size); memcpy(buf_ptr, sec_ptr[0], element_size); break; case 1: test_sum(buf_ptr + iovt[0].iov_size, element_size, rsum[1], TRUE, &res); memcpy(buf_ptr + iovt[0].iov_size, sec_ptr[0], element_size); break; case 2: test_sum(buf_ptr + iovt[1].iov_size, element_size * 2, rsum[1], TRUE, &res); test_sum(sec_ptr[1] + element_size, sector_size - element_size, rsum[2], TRUE, &res); memmove(buf_ptr + element_size, buf_ptr, iovt[1].iov_size); memcpy(buf_ptr, sec_ptr[0], element_size); memcpy(buf_ptr + element_size + iovt[1].iov_size, sec_ptr[1], element_size); break; } for (i = 0; i < sectors; i++) test_sum(buf_ptr + sector_size * i, sector_size, ssum[1 + i], TRUE, &res); got_result(&res, "read with small elements"); /* In read-only mode, we have nothing more to do. */ if (!may_write) return; /* Use the same I/O vector to perform a write with small elements. * This will cause the checksums of the target sectors to change, * so we need to update those for both verification and later usage. */ for (i = 0; i < sectors; i++) ssum[1 + i] = fill_rand(buf_ptr + sector_size * i, sector_size); switch (pattern) { case 0: memcpy(sec_ptr[0], buf_ptr, element_size); memmove(buf_ptr, buf_ptr + element_size, iovt[1].iov_size); fill_rand(buf_ptr + iovt[1].iov_size, element_size); break; case 1: memcpy(sec_ptr[0], buf_ptr + iovt[0].iov_size, element_size); fill_rand(buf_ptr + iovt[0].iov_size, element_size); break; case 2: memcpy(sec_ptr[0], buf_ptr, element_size); memcpy(sec_ptr[1], buf_ptr + element_size + iovt[1].iov_size, element_size); memmove(buf_ptr, buf_ptr + element_size, iovt[1].iov_size); fill_rand(buf_ptr + iovt[1].iov_size, element_size * 2); break; } memcpy(iov, iovt, sizeof(iov)); vir_xfer(driver_minor, base_pos, iov, nr_req, TRUE, total_size, &res); got_result(&res, "write with small elements"); /* Now perform normal readback verification. */ fill_rand(buf_ptr, sector_size * 3); simple_xfer(driver_minor, base_pos, buf_ptr, sector_size * 3, FALSE, sector_size * 3, &res); for (i = 0; i < 3; i++) test_sum(buf_ptr + sector_size * i, sector_size, ssum[1 + i], TRUE, &res); got_result(&res, "readback verification"); } static void unaligned_size(void) { /* Test sector-unaligned sizes in I/O vector elements. The total size * of the request, however, has to add up to the sector size. */ u8_t *buf_ptr, *sec_ptr[2]; size_t buf_size; u32_t sum = 0L, ssum[5]; u64_t base_pos; result_t res; int i; test_group("sector-unaligned elements", sector_size != element_size); /* We can only do this test if the driver allows small elements. */ if (sector_size == element_size) return; /* Crashing on bad user input, terrible! */ assert(sector_size % element_size == 0); /* Establish a baseline by writing and reading back five sectors; or * by reading only, if writing is disabled. */ buf_size = sector_size * 5; base_pos = (u64_t)sector_size * 2; buf_ptr = alloc_dma_memory(buf_size); sec_ptr[0] = alloc_dma_memory(sector_size); sec_ptr[1] = alloc_dma_memory(sector_size); if (may_write) { sum = fill_rand(buf_ptr, buf_size); for (i = 0; i < 5; i++) ssum[i] = get_sum(buf_ptr + sector_size * i, sector_size); simple_xfer(driver_minor, base_pos, buf_ptr, buf_size, TRUE, buf_size, &res); got_result(&res, "write several sectors"); } fill_rand(buf_ptr, buf_size); simple_xfer(driver_minor, base_pos, buf_ptr, buf_size, FALSE, buf_size, &res); if (may_write) { test_sum(buf_ptr, buf_size, sum, TRUE, &res); } else { for (i = 0; i < 5; i++) ssum[i] = get_sum(buf_ptr + sector_size * i, sector_size); } got_result(&res, "read several sectors"); /* We do nine subtests. The first three involve only the second sector; * the second three involve the second and third sectors, and the third * three involve all of the middle sectors. Each triplet tests small * elements at the left, at the right, and at both the left and the * right of the area. For each operation, we first do an unaligned * read, and if writing is enabled, an unaligned write and an aligned * read. */ for (i = 0; i < 9; i++) { unaligned_size_io(base_pos, buf_ptr, buf_size, sec_ptr, i / 3 + 1, i % 3, ssum); } /* If writing was enabled, make sure that the first and fifth sector * have remained untouched. */ if (may_write) { fill_rand(buf_ptr, buf_size); simple_xfer(driver_minor, base_pos, buf_ptr, buf_size, FALSE, buf_size, &res); test_sum(buf_ptr, sector_size, ssum[0], TRUE, &res); test_sum(buf_ptr + sector_size * 4, sector_size, ssum[4], TRUE, &res); got_result(&res, "check first and last sectors"); } /* Clean up. */ free_dma_memory(sec_ptr[1], sector_size); free_dma_memory(sec_ptr[0], sector_size); free_dma_memory(buf_ptr, buf_size); } static void unaligned_pos1(void) { /* Test sector-unaligned positions and total sizes for requests. This * is a read-only test for now. Write support should be added later. * In the current context, the term "lead" means an unwanted first part * of a sector, and "trail" means an unwanted last part of a sector. */ u8_t *buf_ptr, *buf2_ptr; size_t buf_size, buf2_size, size; u32_t sum, sum2; u64_t base_pos; result_t res; test_group("sector-unaligned positions, part one", min_read != sector_size); /* We can only do this test if the driver allows small read requests. */ if (min_read == sector_size) return; assert(sector_size % min_read == 0); assert(min_read % element_size == 0); /* Establish a baseline by writing and reading back three sectors; or * by reading only, if writing is disabled. */ buf_size = buf2_size = sector_size * 3; base_pos = (u64_t)sector_size * 3; buf_ptr = alloc_dma_memory(buf_size); buf2_ptr = alloc_dma_memory(buf2_size); if (may_write) { sum = fill_rand(buf_ptr, buf_size); simple_xfer(driver_minor, base_pos, buf_ptr, buf_size, TRUE, buf_size, &res); got_result(&res, "write several sectors"); } fill_rand(buf_ptr, buf_size); simple_xfer(driver_minor, base_pos, buf_ptr, buf_size, FALSE, buf_size, &res); if (may_write) test_sum(buf_ptr, buf_size, sum, TRUE, &res); got_result(&res, "read several sectors"); /* Start with a simple test that operates within a single sector, * first using a lead. */ fill_rand(buf2_ptr, sector_size); sum = get_sum(buf2_ptr + min_read, sector_size - min_read); simple_xfer(driver_minor, base_pos + sector_size - min_read, buf2_ptr, min_read, FALSE, min_read, &res); test_sum(buf2_ptr, min_read, get_sum(buf_ptr + sector_size - min_read, min_read), TRUE, &res); test_sum(buf2_ptr + min_read, sector_size - min_read, sum, TRUE, &res); got_result(&res, "single sector read with lead"); /* Then a trail. */ fill_rand(buf2_ptr, sector_size); sum = get_sum(buf2_ptr, sector_size - min_read); simple_xfer(driver_minor, base_pos, buf2_ptr + sector_size - min_read, min_read, FALSE, min_read, &res); test_sum(buf2_ptr + sector_size - min_read, min_read, get_sum(buf_ptr, min_read), TRUE, &res); test_sum(buf2_ptr, sector_size - min_read, sum, TRUE, &res); got_result(&res, "single sector read with trail"); /* And then a lead and a trail, unless min_read is half the sector * size, in which case this will be another lead test. */ fill_rand(buf2_ptr, sector_size); sum = get_sum(buf2_ptr, min_read); sum2 = get_sum(buf2_ptr + min_read * 2, sector_size - min_read * 2); simple_xfer(driver_minor, base_pos + min_read, buf2_ptr + min_read, min_read, FALSE, min_read, &res); test_sum(buf2_ptr + min_read, min_read, get_sum(buf_ptr + min_read, min_read), TRUE, &res); test_sum(buf2_ptr, min_read, sum, TRUE, &res); test_sum(buf2_ptr + min_read * 2, sector_size - min_read * 2, sum2, TRUE, &res); got_result(&res, "single sector read with lead and trail"); /* Now do the same but with three sectors, and still only one I/O * vector element. First up: lead. */ size = min_read + sector_size * 2; fill_rand(buf2_ptr, buf2_size); sum = get_sum(buf2_ptr + size, buf2_size - size); simple_xfer(driver_minor, base_pos + sector_size - min_read, buf2_ptr, size, FALSE, size, &res); test_sum(buf2_ptr, size, get_sum(buf_ptr + sector_size - min_read, size), TRUE, &res); test_sum(buf2_ptr + size, buf2_size - size, sum, TRUE, &res); got_result(&res, "multisector read with lead"); /* Then trail. */ fill_rand(buf2_ptr, buf2_size); sum = get_sum(buf2_ptr + size, buf2_size - size); simple_xfer(driver_minor, base_pos, buf2_ptr, size, FALSE, size, &res); test_sum(buf2_ptr, size, get_sum(buf_ptr, size), TRUE, &res); test_sum(buf2_ptr + size, buf2_size - size, sum, TRUE, &res); got_result(&res, "multisector read with trail"); /* Then lead and trail. Use sector size as transfer unit to throw off * simplistic lead/trail detection. */ fill_rand(buf2_ptr, buf2_size); sum = get_sum(buf2_ptr + sector_size, buf2_size - sector_size); simple_xfer(driver_minor, base_pos + min_read, buf2_ptr, sector_size, FALSE, sector_size, &res); test_sum(buf2_ptr, sector_size, get_sum(buf_ptr + min_read, sector_size), TRUE, &res); test_sum(buf2_ptr + sector_size, buf2_size - sector_size, sum, TRUE, &res); got_result(&res, "multisector read with lead and trail"); /* Clean up. */ free_dma_memory(buf2_ptr, buf2_size); free_dma_memory(buf_ptr, buf_size); } static void unaligned_pos2(void) { /* Test sector-unaligned positions and total sizes for requests, second * part. This one tests the use of multiple I/O vector elements, and * tries to push the limits of the driver by completely filling an I/O * vector and going up to the maximum request size. */ u8_t *buf_ptr, *buf2_ptr; size_t buf_size, buf2_size, max_block; u32_t sum = 0L, sum2 = 0L, rsum[NR_IOREQS]; u64_t base_pos; iovec_t iov[NR_IOREQS]; result_t res; int i; test_group("sector-unaligned positions, part two", min_read != sector_size); /* We can only do this test if the driver allows small read requests. */ if (min_read == sector_size) return; buf_size = buf2_size = max_size + sector_size; base_pos = (u64_t)sector_size * 3; buf_ptr = alloc_dma_memory(buf_size); buf2_ptr = alloc_dma_memory(buf2_size); /* First establish a baseline. We need two requests for this, as the * total area intentionally exceeds the max request size. */ if (may_write) { sum = fill_rand(buf_ptr, max_size); simple_xfer(driver_minor, base_pos, buf_ptr, max_size, TRUE, max_size, &res); got_result(&res, "large baseline write"); sum2 = fill_rand(buf_ptr + max_size, sector_size); simple_xfer(driver_minor, base_pos + max_size, buf_ptr + max_size, sector_size, TRUE, sector_size, &res); got_result(&res, "small baseline write"); } fill_rand(buf_ptr, buf_size); simple_xfer(driver_minor, base_pos, buf_ptr, max_size, FALSE, max_size, &res); if (may_write) test_sum(buf_ptr, max_size, sum, TRUE, &res); got_result(&res, "large baseline read"); simple_xfer(driver_minor, base_pos + max_size, buf_ptr + max_size, sector_size, FALSE, sector_size, &res); if (may_write) test_sum(buf_ptr + max_size, sector_size, sum2, TRUE, &res); got_result(&res, "small baseline read"); /* First construct a full vector with minimal sizes. The resulting area * may well fall within a single sector, if min_read is small enough. */ fill_rand(buf2_ptr, buf2_size); for (i = 0; i < NR_IOREQS; i++) { iov[i].iov_addr = (vir_bytes) buf2_ptr + i * sector_size; iov[i].iov_size = min_read; rsum[i] = get_sum(buf2_ptr + i * sector_size + min_read, sector_size - min_read); } vir_xfer(driver_minor, base_pos + min_read, iov, NR_IOREQS, FALSE, min_read * NR_IOREQS, &res); for (i = 0; i < NR_IOREQS; i++) { test_sum(buf2_ptr + i * sector_size + min_read, sector_size - min_read, rsum[i], TRUE, &res); memmove(buf2_ptr + i * min_read, buf2_ptr + i * sector_size, min_read); } test_sum(buf2_ptr, min_read * NR_IOREQS, get_sum(buf_ptr + min_read, min_read * NR_IOREQS), TRUE, &res); got_result(&res, "small fully unaligned filled vector"); /* Sneak in a maximum sized request with a single I/O vector element, * unaligned. If the driver splits up such large requests into smaller * chunks, this tests whether it does so correctly in the presence of * leads and trails. */ fill_rand(buf2_ptr, buf2_size); simple_xfer(driver_minor, base_pos + min_read, buf2_ptr, max_size, FALSE, max_size, &res); test_sum(buf2_ptr, max_size, get_sum(buf_ptr + min_read, max_size), TRUE, &res); got_result(&res, "large fully unaligned single element"); /* Then try with a vector where each element is as large as possible. * We don't have room to do bounds integrity checking here (we could * make room, but this may be a lot of memory already). */ /* Compute the largest sector multiple which, when multiplied by * NR_IOREQS, is no more than the maximum transfer size. */ max_block = max_size / NR_IOREQS; max_block -= max_block % sector_size; fill_rand(buf2_ptr, buf2_size); for (i = 0; i < NR_IOREQS; i++) { iov[i].iov_addr = (vir_bytes) buf2_ptr + i * max_block; iov[i].iov_size = max_block; } vir_xfer(driver_minor, base_pos + min_read, iov, NR_IOREQS, FALSE, max_block * NR_IOREQS, &res); test_sum(buf2_ptr, max_block * NR_IOREQS, get_sum(buf_ptr + min_read, max_block * NR_IOREQS), TRUE, &res); got_result(&res, "large fully unaligned filled vector"); /* Clean up. */ free_dma_memory(buf2_ptr, buf2_size); free_dma_memory(buf_ptr, buf_size); } static void sweep_area(u64_t base_pos) { /* Go over an eight-sector area from left (low address) to right (high * address), reading and optionally writing in three-sector chunks, and * advancing one sector at a time. */ u8_t *buf_ptr; size_t buf_size; u32_t sum = 0L, ssum[8]; result_t res; int i, j; buf_size = sector_size * 8; buf_ptr = alloc_dma_memory(buf_size); /* First (write to, if allowed, and) read from the entire area in one * go, so that we know the (initial) contents of the area. */ if (may_write) { sum = fill_rand(buf_ptr, buf_size); simple_xfer(driver_minor, base_pos, buf_ptr, buf_size, TRUE, buf_size, &res); got_result(&res, "write to full area"); } fill_rand(buf_ptr, buf_size); simple_xfer(driver_minor, base_pos, buf_ptr, buf_size, FALSE, buf_size, &res); if (may_write) test_sum(buf_ptr, buf_size, sum, TRUE, &res); for (i = 0; i < 8; i++) ssum[i] = get_sum(buf_ptr + sector_size * i, sector_size); got_result(&res, "read from full area"); /* For each of the six three-sector subareas, first read from the * subarea, check its checksum, and then (if allowed) write new content * to it. */ for (i = 0; i < 6; i++) { fill_rand(buf_ptr, sector_size * 3); simple_xfer(driver_minor, base_pos + sector_size * i, buf_ptr, sector_size * 3, FALSE, sector_size * 3, &res); for (j = 0; j < 3; j++) test_sum(buf_ptr + sector_size * j, sector_size, ssum[i + j], TRUE, &res); got_result(&res, "read from subarea"); if (!may_write) continue; fill_rand(buf_ptr, sector_size * 3); simple_xfer(driver_minor, base_pos + sector_size * i, buf_ptr, sector_size * 3, TRUE, sector_size * 3, &res); for (j = 0; j < 3; j++) ssum[i + j] = get_sum(buf_ptr + sector_size * j, sector_size); got_result(&res, "write to subarea"); } /* Finally, if writing was enabled, do one final readback. */ if (may_write) { fill_rand(buf_ptr, buf_size); simple_xfer(driver_minor, base_pos, buf_ptr, buf_size, FALSE, buf_size, &res); for (i = 0; i < 8; i++) test_sum(buf_ptr + sector_size * i, sector_size, ssum[i], TRUE, &res); got_result(&res, "readback from full area"); } /* Clean up. */ free_dma_memory(buf_ptr, buf_size); } static void sweep_and_check(u64_t pos, int check_integ) { /* Perform an area sweep at the given position. If asked for, get an * integrity checksum over the beginning of the disk (first writing * known data into it if that is allowed) before doing the sweep, and * test the integrity checksum against the disk contents afterwards. */ u8_t *buf_ptr; size_t buf_size; u32_t sum = 0L; result_t res; if (check_integ) { buf_size = sector_size * 3; buf_ptr = alloc_dma_memory(buf_size); if (may_write) { sum = fill_rand(buf_ptr, buf_size); simple_xfer(driver_minor, 0ULL, buf_ptr, buf_size, TRUE, buf_size, &res); got_result(&res, "write integrity zone"); } fill_rand(buf_ptr, buf_size); simple_xfer(driver_minor, 0ULL, buf_ptr, buf_size, FALSE, buf_size, &res); if (may_write) test_sum(buf_ptr, buf_size, sum, TRUE, &res); else sum = get_sum(buf_ptr, buf_size); got_result(&res, "read integrity zone"); } sweep_area(pos); if (check_integ) { fill_rand(buf_ptr, buf_size); simple_xfer(driver_minor, 0ULL, buf_ptr, buf_size, FALSE, buf_size, &res); test_sum(buf_ptr, buf_size, sum, TRUE, &res); got_result(&res, "check integrity zone"); free_dma_memory(buf_ptr, buf_size); } } static void basic_sweep(void) { /* Perform a basic area sweep. */ test_group("basic area sweep", TRUE); sweep_area((u64_t)sector_size); } static void high_disk_pos(void) { /* Test 64-bit absolute disk positions. This means that after adding * partition base to the given position, the driver will be dealing * with a position above 32 bit. We want to test the transition area * only; if the entire partition base is above 32 bit, we have already * effectively performed this test many times over. In other words, for * this test, the partition must start below 4GB and end above 4GB, * with at least four sectors on each side. */ u64_t base_pos; base_pos = 0x100000000ULL | (sector_size * 4); base_pos -= base_pos % sector_size; /* The partition end must exceed 32 bits. */ if (part.base + part.size < base_pos) { test_group("high disk positions", FALSE); return; } base_pos -= sector_size * 8; /* The partition start must not. */ if (base_pos < part.base) { test_group("high disk positions", FALSE); return; } test_group("high disk positions", TRUE); base_pos -= part.base; sweep_and_check(base_pos, part.base == 0ULL); } static void high_part_pos(void) { /* Test 64-bit partition-relative disk positions. In other words, use * within the current partition a position that exceeds a 32-bit value. * This requires the partition to be more than 4GB in size; we need an * additional 4 sectors, to be exact. */ u64_t base_pos; /* If the partition starts at the beginning of the disk, this test is * no different from the high disk position test. */ if (part.base == 0ULL) { /* don't complain: the test is simply superfluous now */ return; } base_pos = 0x100000000ULL | (sector_size * 4); base_pos -= base_pos % sector_size; if (part.size < base_pos) { test_group("high partition positions", FALSE); return; } test_group("high partition positions", TRUE); base_pos -= sector_size * 8; sweep_and_check(base_pos, TRUE); } static void high_lba_pos1(void) { /* Test 48-bit LBA positions, as opposed to *24-bit*. Drivers that only * support 48-bit LBA ATA transfers, will treat the lower and upper 24 * bits differently. This is again relative to the disk start, not the * partition start. For 512-byte sectors, the lowest position exceeding * 24 bit is at 8GB. As usual, we need four sectors more, and fewer, on * the other side. The partition that we're operating on, must cover * this area. */ u64_t base_pos; base_pos = (1ULL << 24) * sector_size; /* The partition end must exceed the 24-bit sector point. */ if (part.base + part.size < base_pos) { test_group("high LBA positions, part one", FALSE); return; } base_pos -= sector_size * 8; /* The partition start must not. */ if (base_pos < part.base) { test_group("high LBA positions, part one", FALSE); return; } test_group("high LBA positions, part one", TRUE); base_pos -= part.base; sweep_and_check(base_pos, part.base == 0ULL); } static void high_lba_pos2(void) { /* Test 48-bit LBA positions, as opposed to *28-bit*. That means sector * numbers in excess of 28-bit values; the old ATA upper limit. The * same considerations as above apply, except that we now need a 128+GB * partition. */ u64_t base_pos; base_pos = (1ULL << 28) * sector_size; /* The partition end must exceed the 28-bit sector point. */ if (part.base + part.size < base_pos) { test_group("high LBA positions, part two", FALSE); return; } base_pos -= sector_size * 8; /* The partition start must not. */ if (base_pos < part.base) { test_group("high LBA positions, part two", FALSE); return; } test_group("high LBA positions, part two", TRUE); base_pos -= part.base; sweep_and_check(base_pos, part.base == 0ULL); } static void high_pos(void) { /* Check whether the driver deals well with 64-bit positions and * 48-bit LBA addresses. We test three cases: disk byte position beyond * what fits in 32 bit, in-partition byte position beyond what fits in * 32 bit, and disk sector position beyond what fits in 24 bit. With * the partition we've been given, we may not be able to test all of * them (or any, for that matter). */ /* In certain rare cases, we might be able to perform integrity * checking on the area that would be affected if a 32-bit/24-bit * counter were to wrap. More specifically: we can do that if we can * access the start of the disk. This is why we should be given the * entire disk as test area if at all possible. */ basic_sweep(); high_disk_pos(); high_part_pos(); high_lba_pos1(); high_lba_pos2(); } static void open_primary(void) { /* Open the primary device. This call has its own test group. */ test_group("device open", TRUE); open_device(driver_minor); } static void close_primary(void) { /* Close the primary device. This call has its own test group. */ test_group("device close", TRUE); close_device(driver_minor); assert(nr_opened == 0); } static void do_tests(void) { /* Perform all the tests. */ open_primary(); misc_ioctl(); bad_read1(); bad_read2(); /* It is assumed that the driver implementation uses shared * code paths for read and write for the basic checks, so we do * not repeat those for writes. */ bad_write(); vector_and_large(); part_limits(); unaligned_size(); unaligned_pos1(); unaligned_pos2(); high_pos(); close_primary(); } static int sef_cb_init_fresh(int UNUSED(type), sef_init_info_t *UNUSED(info)) { /* Initialize. */ int r; clock_t now; if (env_argc > 1) optset_parse(optset_table, env_argv[1]); if (driver_label[0] == '\0') panic("no driver label given"); if (ds_retrieve_label_endpt(driver_label, &driver_endpt)) panic("unable to resolve driver label"); if (driver_minor > 255) panic("invalid or no driver minor given"); if ((r = getticks(&now)) != OK) panic("unable to get uptime: %d", r); srand48(now); output("BLOCKTEST: driver label '%s' (endpt %d), minor %d\n", driver_label, driver_endpt, driver_minor); do_tests(); output("BLOCKTEST: summary: %d out of %d tests failed " "across %d group%s; %d driver deaths\n", failed_tests, total_tests, failed_groups, failed_groups == 1 ? "" : "s", driver_deaths); /* The returned code will determine the outcome of the RS call, and * thus the entire test. The actual error code does not matter. */ return (failed_tests) ? EINVAL : OK; } static void sef_local_startup(void) { /* Initialize the SEF framework. */ sef_setcb_init_fresh(sef_cb_init_fresh); sef_startup(); } int main(int argc, char **argv) { /* Driver task. */ env_setargs(argc, argv); sef_local_startup(); return 0; }