diff --git a/module/bdev/zone_block/vbdev_zone_block.c b/module/bdev/zone_block/vbdev_zone_block.c index ae5e972de..0f15bee47 100644 --- a/module/bdev/zone_block/vbdev_zone_block.c +++ b/module/bdev/zone_block/vbdev_zone_block.c @@ -73,6 +73,7 @@ static TAILQ_HEAD(, bdev_zone_block_config) g_bdev_configs = TAILQ_HEAD_INITIALI struct block_zone { struct spdk_bdev_zone_info zone_info; + pthread_spinlock_t lock; }; /* List of block vbdevs and associated info for each. */ @@ -92,7 +93,7 @@ struct zone_block_io_channel { }; struct zone_block_io { - /* bdev IO was issued to */ + /* vbdev to which IO was issued */ struct bdev_zone_block *bdev_zone_block; }; @@ -153,9 +154,13 @@ zone_block_config_json(struct spdk_json_write_ctx *w) static void _device_unregister_cb(void *io_device) { - struct bdev_zone_block *bdev_node = io_device; + struct bdev_zone_block *bdev_node = io_device; + uint64_t i; free(bdev_node->bdev.name); + for (i = 0; i < bdev_node->num_zones; i++) { + pthread_spin_destroy(&bdev_node->zones[i].lock); + } free(bdev_node->zones); free(bdev_node); } @@ -220,16 +225,99 @@ zone_block_get_zone_info(struct bdev_zone_block *bdev_node, struct spdk_bdev_io return 0; } +static int +zone_block_open_zone(struct block_zone *zone, struct spdk_bdev_io *bdev_io) +{ + pthread_spin_lock(&zone->lock); + + switch (zone->zone_info.state) { + case SPDK_BDEV_ZONE_STATE_EMPTY: + case SPDK_BDEV_ZONE_STATE_OPEN: + case SPDK_BDEV_ZONE_STATE_CLOSED: + zone->zone_info.state = SPDK_BDEV_ZONE_STATE_OPEN; + pthread_spin_unlock(&zone->lock); + spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_SUCCESS); + return 0; + default: + pthread_spin_unlock(&zone->lock); + return -EINVAL; + } +} + +static void +_zone_block_complete_unmap(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + struct spdk_bdev_io *orig_io = cb_arg; + int status = success ? SPDK_BDEV_IO_STATUS_SUCCESS : SPDK_BDEV_IO_STATUS_FAILED; + + /* Complete the original IO and then free the one that we created here + * as a result of issuing an IO via submit_reqeust. + */ + spdk_bdev_io_complete(orig_io, status); + spdk_bdev_free_io(bdev_io); +} + +static int +zone_block_reset_zone(struct bdev_zone_block *bdev_node, struct zone_block_io_channel *ch, + struct block_zone *zone, struct spdk_bdev_io *bdev_io) +{ + pthread_spin_lock(&zone->lock); + + switch (zone->zone_info.state) { + case SPDK_BDEV_ZONE_STATE_EMPTY: + pthread_spin_unlock(&zone->lock); + spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_SUCCESS); + return 0; + case SPDK_BDEV_ZONE_STATE_OPEN: + case SPDK_BDEV_ZONE_STATE_FULL: + case SPDK_BDEV_ZONE_STATE_CLOSED: + zone->zone_info.state = SPDK_BDEV_ZONE_STATE_EMPTY; + zone->zone_info.write_pointer = zone->zone_info.zone_id; + pthread_spin_unlock(&zone->lock); + return spdk_bdev_unmap_blocks(bdev_node->base_desc, ch->base_ch, + zone->zone_info.zone_id, zone->zone_info.capacity, + _zone_block_complete_unmap, bdev_io); + default: + pthread_spin_unlock(&zone->lock); + return -EINVAL; + } +} + +static int +zone_block_zone_management(struct bdev_zone_block *bdev_node, struct zone_block_io_channel *ch, + struct spdk_bdev_io *bdev_io) +{ + struct block_zone *zone; + + zone = zone_block_get_zone_by_slba(bdev_node, bdev_io->u.zone_mgmt.zone_id); + if (!zone) { + return -EINVAL; + } + + switch (bdev_io->u.zone_mgmt.zone_action) { + case SPDK_BDEV_ZONE_RESET: + return zone_block_reset_zone(bdev_node, ch, zone, bdev_io); + case SPDK_BDEV_ZONE_OPEN: + return zone_block_open_zone(zone, bdev_io); + default: + return -EINVAL; + } +} + static void zone_block_submit_request(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io) { struct bdev_zone_block *bdev_node = SPDK_CONTAINEROF(bdev_io->bdev, struct bdev_zone_block, bdev); + struct zone_block_io_channel *dev_ch = spdk_io_channel_get_ctx(ch); int rc = 0; switch (bdev_io->type) { case SPDK_BDEV_IO_TYPE_GET_ZONE_INFO: rc = zone_block_get_zone_info(bdev_node, bdev_io); break; + case SPDK_BDEV_IO_TYPE_ZONE_MANAGEMENT: + rc = zone_block_zone_management(bdev_node, dev_ch, bdev_io); + break; default: SPDK_ERRLOG("vbdev_block: unknown I/O type %u\n", bdev_io->type); rc = -ENOTSUP; @@ -373,11 +461,12 @@ zone_block_insert_name(const char *bdev_name, const char *vbdev_name, uint64_t z return 0; } -static void +static int zone_block_init_zone_info(struct bdev_zone_block *bdev_node) { size_t i; struct block_zone *zone; + int rc = 0; for (i = 0; i < bdev_node->num_zones; i++) { zone = &bdev_node->zones[i]; @@ -385,7 +474,20 @@ zone_block_init_zone_info(struct bdev_zone_block *bdev_node) zone->zone_info.capacity = bdev_node->zone_capacity; zone->zone_info.write_pointer = zone->zone_info.zone_id + zone->zone_info.capacity; zone->zone_info.state = SPDK_BDEV_ZONE_STATE_FULL; + if (pthread_spin_init(&zone->lock, PTHREAD_PROCESS_PRIVATE)) { + SPDK_ERRLOG("pthread_spin_init() failed\n"); + rc = -ENOMEM; + break; + } } + + if (rc) { + for (; i > 0; i--) { + pthread_spin_destroy(&bdev_node->zones[i - 1].lock); + } + } + + return rc; } static int @@ -479,7 +581,11 @@ zone_block_register(struct spdk_bdev *base_bdev) bdev_node->zone_capacity = name->zone_capacity; bdev_node->bdev.optimal_open_zones = name->optimal_open_zones; bdev_node->bdev.max_open_zones = 0; - zone_block_init_zone_info(bdev_node); + rc = zone_block_init_zone_info(bdev_node); + if (rc) { + SPDK_ERRLOG("could not init zone info\n"); + goto zone_info_failed; + } TAILQ_INSERT_TAIL(&g_bdev_nodes, bdev_node, link); @@ -516,6 +622,7 @@ claim_failed: open_failed: TAILQ_REMOVE(&g_bdev_nodes, bdev_node, link); spdk_io_device_unregister(bdev_node, NULL); +zone_info_failed: free(bdev_node->zones); calloc_failed: roundup_failed: diff --git a/test/unit/lib/bdev/vbdev_zone_block.c/vbdev_zone_block_ut.c b/test/unit/lib/bdev/vbdev_zone_block.c/vbdev_zone_block_ut.c index 909df23df..ab6a2a4cc 100644 --- a/test/unit/lib/bdev/vbdev_zone_block.c/vbdev_zone_block_ut.c +++ b/test/unit/lib/bdev/vbdev_zone_block.c/vbdev_zone_block_ut.c @@ -84,7 +84,13 @@ DEFINE_STUB_V(spdk_rpc_register_method, (const char *method, spdk_rpc_method_han DEFINE_STUB_V(spdk_jsonrpc_end_result, (struct spdk_jsonrpc_request *request, struct spdk_json_write_ctx *w)); DEFINE_STUB(spdk_bdev_get_io_channel, struct spdk_io_channel *, (struct spdk_bdev_desc *desc), - (void *)1); + (void *)0); + +static void +set_test_opts(void) +{ + g_max_io_size = 1024; +} static void init_test_globals(void) @@ -101,6 +107,12 @@ free_test_globals(void) g_io_output = NULL; } +void +spdk_bdev_free_io(struct spdk_bdev_io *bdev_io) +{ + free(bdev_io); +} + int spdk_bdev_open(struct spdk_bdev *bdev, bool write, spdk_bdev_remove_cb_t remove_cb, void *remove_ctx, struct spdk_bdev_desc **_desc) @@ -288,6 +300,41 @@ spdk_jsonrpc_send_error_response_fmt(struct spdk_jsonrpc_request *request, g_rpc_err = 1; } +static void +set_io_output(struct io_output *output, + struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg, + enum spdk_bdev_io_type iotype) +{ + output->desc = desc; + output->ch = ch; + output->offset_blocks = offset_blocks; + output->num_blocks = num_blocks; + output->cb = cb; + output->cb_arg = cb_arg; + output->iotype = iotype; +} + +int +spdk_bdev_unmap_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + struct io_output *output = &g_io_output[g_io_output_index]; + struct spdk_bdev_io *child_io; + + set_io_output(output, desc, ch, offset_blocks, num_blocks, cb, cb_arg, + SPDK_BDEV_IO_TYPE_UNMAP); + g_io_output_index++; + + child_io = calloc(1, sizeof(struct spdk_bdev_io)); + SPDK_CU_ASSERT_FATAL(child_io != NULL); + cb(child_io, true, cb_arg); + + return 0; +} + static void verify_config_present(const char *name, bool presence) { @@ -580,6 +627,17 @@ bdev_io_zone_info_initialize(struct spdk_bdev_io *bdev_io, struct spdk_bdev *bde } } +static void +bdev_io_zone_initialize(struct spdk_bdev_io *bdev_io, struct spdk_bdev *bdev, + uint64_t zone_id, uint32_t num_zones, uint8_t zone_action) +{ + bdev_io->bdev = bdev; + bdev_io->type = SPDK_BDEV_IO_TYPE_ZONE_MANAGEMENT; + + bdev_io->u.zone_mgmt.zone_action = zone_action; + bdev_io->u.zone_mgmt.zone_id = zone_id; +} + static void bdev_io_zone_cleanup(struct spdk_bdev_io *bdev_io) { @@ -746,6 +804,157 @@ test_get_zone_info(void) test_cleanup(); } +static void +send_zone_management(struct bdev_zone_block *bdev, struct spdk_io_channel *ch, uint64_t zone_id, + uint32_t output_index, enum spdk_bdev_zone_action action, bool success) +{ + struct spdk_bdev_io *bdev_io; + + bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct zone_block_io)); + SPDK_CU_ASSERT_FATAL(bdev_io != NULL); + bdev_io_zone_initialize(bdev_io, &bdev->bdev, zone_id, 1, action); + memset(g_io_output, 0, (g_max_io_size * sizeof(struct io_output))); + g_io_output_index = output_index; + + g_io_comp_status = !success; + zone_block_submit_request(ch, bdev_io); + + CU_ASSERT(g_io_comp_status == success); + bdev_io_zone_cleanup(bdev_io); +} + +static void +send_reset_zone(struct bdev_zone_block *bdev, struct spdk_io_channel *ch, uint64_t zone_id, + uint32_t output_index, bool success) +{ + send_zone_management(bdev, ch, zone_id, output_index, SPDK_BDEV_ZONE_RESET, success); +} + +static void +send_open_zone(struct bdev_zone_block *bdev, struct spdk_io_channel *ch, uint64_t zone_id, + uint32_t output_index, bool success) +{ + send_zone_management(bdev, ch, zone_id, output_index, SPDK_BDEV_ZONE_OPEN, success); +} + +static void +test_reset_zone(void) +{ + struct spdk_io_channel *ch; + struct bdev_zone_block *bdev; + char *name = "Nvme0n1"; + uint32_t num_zones = 16; + uint64_t zone_id; + uint32_t output_index = 0; + + init_test_globals(); + CU_ASSERT(zone_block_init() == 0); + + /* Create zone dev */ + bdev = create_and_get_vbdev("zone_dev1", name, num_zones, 1, true); + + ch = calloc(1, sizeof(struct spdk_io_channel) + sizeof(struct zone_block_io_channel)); + SPDK_CU_ASSERT_FATAL(ch != NULL); + + /* Send reset to zone 0 */ + zone_id = 0; + send_reset_zone(bdev, ch, zone_id, output_index, true); + send_zone_info(bdev, ch, zone_id, zone_id, SPDK_BDEV_ZONE_STATE_EMPTY, output_index, true); + + /* Send reset to last zone */ + zone_id = (num_zones - 1) * bdev->bdev.zone_size; + send_reset_zone(bdev, ch, zone_id, output_index, true); + send_zone_info(bdev, ch, zone_id, zone_id, SPDK_BDEV_ZONE_STATE_EMPTY, output_index, true); + + /* Send reset with misaligned LBA */ + zone_id = 1; + send_reset_zone(bdev, ch, zone_id, output_index, false); + + /* Send reset to non-existing zone */ + zone_id = num_zones * bdev->bdev.zone_size; + send_reset_zone(bdev, ch, zone_id, output_index, false); + + /* Send reset to already resetted zone */ + zone_id = 0; + send_reset_zone(bdev, ch, zone_id, output_index, true); + send_zone_info(bdev, ch, zone_id, zone_id, SPDK_BDEV_ZONE_STATE_EMPTY, output_index, true); + + /* Delete zone dev */ + send_delete_vbdev("zone_dev1", true); + + while (spdk_thread_poll(g_thread, 0, 0) > 0) {} + free(ch); + + test_cleanup(); +} + +static void +test_open_zone(void) +{ + struct spdk_io_channel *ch; + struct bdev_zone_block *bdev; + char *name = "Nvme0n1"; + uint32_t num_zones = 16; + uint64_t zone_id; + uint32_t output_index = 0, i; + + init_test_globals(); + CU_ASSERT(zone_block_init() == 0); + + /* Create zone dev */ + bdev = create_and_get_vbdev("zone_dev1", name, num_zones, 1, true); + + ch = calloc(1, sizeof(struct spdk_io_channel) + sizeof(struct zone_block_io_channel)); + SPDK_CU_ASSERT_FATAL(ch != NULL); + + /* Try to open full zone */ + zone_id = 0; + send_open_zone(bdev, ch, zone_id, output_index, false); + + /* Open all zones */ + for (i = 0; i < num_zones; i++) { + zone_id = i * bdev->bdev.zone_size; + send_reset_zone(bdev, ch, zone_id, output_index, true); + send_zone_info(bdev, ch, zone_id, zone_id, SPDK_BDEV_ZONE_STATE_EMPTY, output_index, true); + } + for (i = 0; i < num_zones; i++) { + zone_id = i * bdev->bdev.zone_size; + send_open_zone(bdev, ch, zone_id, output_index, true); + send_zone_info(bdev, ch, zone_id, zone_id, SPDK_BDEV_ZONE_STATE_OPEN, output_index, true); + } + + /* Reset one of the zones and open it again */ + zone_id = 0; + send_reset_zone(bdev, ch, zone_id, output_index, true); + send_zone_info(bdev, ch, zone_id, zone_id, SPDK_BDEV_ZONE_STATE_EMPTY, output_index, true); + send_open_zone(bdev, ch, zone_id, output_index, true); + send_zone_info(bdev, ch, zone_id, zone_id, SPDK_BDEV_ZONE_STATE_OPEN, output_index, true); + + /* Send open with misaligned LBA */ + zone_id = 0; + send_reset_zone(bdev, ch, zone_id, output_index, true); + zone_id = 1; + send_open_zone(bdev, ch, zone_id, output_index, false); + + /* Send open to non-existing zone */ + zone_id = num_zones * bdev->bdev.zone_size; + send_open_zone(bdev, ch, zone_id, output_index, false); + + /* Send open to already opened zone */ + zone_id = bdev->bdev.zone_size; + send_zone_info(bdev, ch, zone_id, zone_id, SPDK_BDEV_ZONE_STATE_OPEN, output_index, true); + send_open_zone(bdev, ch, zone_id, output_index, true); + send_zone_info(bdev, ch, zone_id, zone_id, SPDK_BDEV_ZONE_STATE_OPEN, output_index, true); + + /* Delete zone dev */ + send_delete_vbdev("zone_dev1", true); + + while (spdk_thread_poll(g_thread, 0, 0) > 0) {} + free(ch); + + test_cleanup(); +} + int main(int argc, char **argv) { CU_pSuite suite = NULL; @@ -765,7 +974,9 @@ int main(int argc, char **argv) CU_add_test(suite, "test_zone_block_create", test_zone_block_create) == NULL || CU_add_test(suite, "test_zone_block_create_invalid", test_zone_block_create_invalid) == NULL || CU_add_test(suite, "test_get_zone_info", test_get_zone_info) == NULL || - CU_add_test(suite, "test_supported_io_types", test_supported_io_types) == NULL + CU_add_test(suite, "test_supported_io_types", test_supported_io_types) == NULL || + CU_add_test(suite, "test_reset_zone", test_reset_zone) == NULL || + CU_add_test(suite, "test_open_zone", test_open_zone) == NULL ) { CU_cleanup_registry(); return CU_get_error(); @@ -775,6 +986,7 @@ int main(int argc, char **argv) spdk_set_thread(g_thread); CU_basic_set_mode(CU_BRM_VERBOSE); + set_test_opts(); CU_basic_run_tests(); num_failures = CU_get_number_of_failures();