#include #include #include #include #include #include #include #include // #define BLOCK_DRIVER_PERFORMANCE #ifdef BLOCK_DRIVER_PERFORMANCE #include struct performance_context pcontext; #endif struct block_driver_state { struct ump_send_chan *send_chan; struct ump_recv_chan *recv_chan; struct block_driver_request *current_request; struct block_driver_state *next; }; static struct sdhc_s *sdhc; static void *dma_buffer; static genpaddr_t dma_buffer_phys; static bool locked = false; static struct block_driver_state *lock_queue_head = NULL; static struct block_driver_state *lock_queue_tail = NULL; // list of open file handles because we have to synchronize on open dirs/files vs deletion struct handle { uint64_t directory_entry_id; size_t count; struct handle *next; struct handle *prev; }; struct handle *handle_list_head = NULL; struct handle *handle_list_tail = NULL; #define BLOCK_DRIVER_CACHE #ifdef BLOCK_DRIVER_CACHE uint32_t current_block_in_buffer = UINT32_MAX; #endif static errval_t read_buffer(int block_number) { assert(block_number != UINT32_MAX); errval_t err; // debug_printf("[block_driver_server] read block %d\n", block_number); #ifdef BLOCK_DRIVER_CACHE // very primitive cache for the last read block if(current_block_in_buffer == block_number) { return SYS_ERR_OK; } #endif err = sdhc_read_block(sdhc, block_number, (lpaddr_t)dma_buffer_phys); if(err_is_fail(err)) { #ifdef BLOCK_DRIVER_CACHE // maybe there was a partial modification so invalidate the cache current_block_in_buffer = UINT32_MAX; #endif return err; } #ifdef BLOCK_DRIVER_CACHE current_block_in_buffer = block_number; #endif __asm volatile ( "dmb sy\n" ); cpu_dcache_wbinv_range((genvaddr_t)dma_buffer, SDHC_BLOCK_SIZE); return SYS_ERR_OK; } static errval_t write_buffer(int block_number) { assert(block_number != UINT32_MAX); errval_t err; // debug_printf("[block_driver_server] write block %d\n", block_number); __asm volatile ( "dmb sy\n" ); cpu_dcache_wbinv_range((genvaddr_t)dma_buffer, SDHC_BLOCK_SIZE); err = sdhc_write_block(sdhc, block_number, (lpaddr_t)dma_buffer_phys); if(err_is_fail(err)) return err; return SYS_ERR_OK; } static struct handle *search_element(uint64_t directory_entry_id) { struct handle *cur_handle = handle_list_head; while(cur_handle != NULL && cur_handle->directory_entry_id != directory_entry_id) { cur_handle = cur_handle->next; } return cur_handle; } static void insert_element(uint64_t directory_entry_id) { struct handle *element_handle = calloc(1, sizeof(struct handle)); if (element_handle == NULL) USER_PANIC("Ran out of memory"); element_handle->count = 1; element_handle->directory_entry_id = directory_entry_id; element_handle->prev = handle_list_tail; element_handle->next = NULL; if(handle_list_head == NULL) { handle_list_head = element_handle; } else { handle_list_tail->next = element_handle; } handle_list_tail = element_handle; } static void remove_element(struct handle *element_handle) { if (element_handle->prev == NULL) { handle_list_head = element_handle->next; } else { element_handle->prev->next = element_handle->next; } if (element_handle->next == NULL) { handle_list_tail = element_handle->prev; } else { element_handle->next->prev = element_handle->prev; } } __attribute__((__unused__)) static void print_handles(void) { struct handle *element_handle = handle_list_head; debug_printf("Handle List\n"); while (element_handle != NULL) { debug_printf("Handle %lu: %lu\n", element_handle->directory_entry_id, element_handle->count); element_handle = element_handle->next; } } static errval_t register_handle(uint64_t directory_entry_id) { // debug_printf("Register %lu\n", directory_entry_id); struct handle *element_handle = search_element(directory_entry_id); if (element_handle == NULL) { insert_element(directory_entry_id); } else if(element_handle->count == SIZE_MAX) { return FAT_ERR_TOO_MANY_HANDLES; }else { ++element_handle->count; } // print_handles(); return SYS_ERR_OK; } static errval_t unregister_handle(uint64_t directory_entry_id) { // debug_printf("Unegister %lu\n", directory_entry_id); struct handle *element_handle = search_element(directory_entry_id); if (element_handle == NULL) { return ERR_INVALID_ARGS; } else if (element_handle->count == 1) { remove_element(element_handle); } else { --element_handle->count; } // print_handles(); return SYS_ERR_OK; } static size_t count_handles(uint64_t directory_entry_id) { struct handle *element_handle = search_element(directory_entry_id); if (element_handle == NULL) { return 0; } else { return element_handle->count; } } static errval_t read_object(uint32_t block_number, size_t offset, size_t size, void *dst) { errval_t err; // printf("[block_driver_server] reading %lu bytes from %lu@%u to %p\n", size, offset, block_number, dst); // sector bounds check if (offset + size > SDHC_BLOCK_SIZE) { return BLOCK_ERR_OVERFLOW_BLOCK; } #ifdef BLOCK_DRIVER_PERFORMANCE perf_init(&pcontext, "block_driver_read_object"); perf_add_now(&pcontext, "start"); #endif err = read_buffer(block_number); if (err_is_fail(err)) return err; #ifdef BLOCK_DRIVER_PERFORMANCE perf_add_now(&pcontext, "memcpy"); #endif memcpy(dst, dma_buffer + offset, size); // debug_printf("Read start: '%.5s'\n", dst); #ifdef BLOCK_DRIVER_PERFORMANCE perf_add_now(&pcontext, "done"); perf_print(&pcontext); #endif return SYS_ERR_OK; } static errval_t write_object(uint32_t block_number, size_t offset, size_t size, void *src) { errval_t err; // printf("[block_driver_server] writing %lu bytes from %p to %lu@%u\n", size, src, offset, block_number); // sector bounds check if (offset + size > SDHC_BLOCK_SIZE) { return BLOCK_ERR_OVERFLOW_BLOCK; } // we never modify the bpb and boot sector if (block_number == 0) { return BLOCK_ERR_WRITE_BOOTSECTOR; } #ifdef BLOCK_DRIVER_PERFORMANCE perf_init(&pcontext, "block_driver_write_object"); perf_add_now(&pcontext, "start"); #endif // if we are not writing the full block then we need to copy the current block if (size < SDHC_BLOCK_SIZE) { err = read_buffer(block_number); if (err_is_fail(err)) return err; } #ifdef BLOCK_DRIVER_PERFORMANCE perf_add_now(&pcontext, "read"); #endif // debug_printf("Write start: '%.5s'\n", src); memcpy(dma_buffer + offset, src, size); #ifdef BLOCK_DRIVER_CACHE // update the cache metadata current_block_in_buffer = block_number; #endif #ifdef BLOCK_DRIVER_PERFORMANCE perf_add_now(&pcontext, "memcpy"); #endif err = write_buffer(block_number); if (err_is_fail(err)) return err; #ifdef BLOCK_DRIVER_PERFORMANCE perf_add_now(&pcontext, "done"); perf_print(&pcontext); #endif return SYS_ERR_OK; } static void handle_send_completed(void *arg, struct ump_send_queue_entry *entry) { free((void *)entry->header); free((void *)entry->payload); free(entry); } static void send_response(struct block_driver_state *state, errval_t err, size_t payload_size, void *payload) { assert(state != NULL); struct block_driver_result *result = malloc(sizeof(struct block_driver_result)); if (result == NULL) USER_PANIC("Ran out of memory"); struct ump_send_queue_entry *entry = malloc(sizeof(struct ump_send_queue_entry)); if (entry == NULL) USER_PANIC("Ran out of memory"); result->err = err; ump_send( state->send_chan, entry, sizeof(struct block_driver_result), result, payload_size, payload, handle_send_completed, NULL ); } static void send_error_response(struct block_driver_state *state, errval_t err) { assert(state != NULL); send_response(state, err, 0, NULL); } static void handle_payload(void *arg, size_t payload_size, void *payload); static void handle_request(void *arg, size_t header_size, void *header, size_t payload_size) { assert(arg != NULL); errval_t err; struct block_driver_state *state = arg; struct block_driver_request *request = header; state->current_request = request; if (request->bytes > SDHC_BLOCK_SIZE) { send_error_response(state, ERR_INVALID_ARGS); } else if (request->action == BLOCK_DRIVER_ACTION_READ) { // perform read directly void *response_buffer = malloc(request->bytes); if (response_buffer == NULL) USER_PANIC("Ran out of memory"); err = read_object(request->block_number, request->offset, request->bytes, response_buffer); if (err_is_fail(err)) { send_error_response(state, err); } else { send_response(state, SYS_ERR_OK, request->bytes, response_buffer); } } else if (request->action == BLOCK_DRIVER_ACTION_WRITE) { if (request->bytes != payload_size) { send_error_response(state, ERR_INVALID_ARGS); ump_recv_payload(state->recv_chan, NULL, handle_payload, arg); return; } // we first need to receive the payload before we can handle the request void *payload_buffer = malloc(request->bytes); if (payload_buffer == NULL) USER_PANIC("Ran out of memory"); ump_recv_payload(state->recv_chan, payload_buffer, handle_payload, arg); return; } else if (request->action == BLOCK_DRIVER_ACTION_LOCK) { if(locked) { // enqueue the request if (lock_queue_tail != NULL) { lock_queue_tail->next = state; } else { lock_queue_head = state; } lock_queue_tail = state; } else { locked = true; send_error_response(state, SYS_ERR_OK); } } else if (request->action == BLOCK_DRIVER_ACTION_UNLOCK) { if (!locked) USER_PANIC("Received unlock command but was not locked"); send_error_response(state, SYS_ERR_OK); // send response to first thread on the lock queue if there is one if (lock_queue_head != NULL) { send_error_response(lock_queue_head, SYS_ERR_OK); // dequeue the head if (lock_queue_head->next == NULL) { lock_queue_tail = NULL; } lock_queue_head = lock_queue_head->next; } else { locked = false; } } else if (request->action == BLOCK_DRIVER_ACTION_REGISTER_HANDLE) { err = register_handle(request->directory_entry_id); send_error_response(state, err); } else if (request->action == BLOCK_DRIVER_ACTION_UNREGISTER_HANDLE) { err = unregister_handle(request->directory_entry_id); send_error_response(state, err); } else if (request->action == BLOCK_DRIVER_ACTION_COUNT_HANDLES) { size_t *count = malloc(sizeof(size_t)); if (count == NULL) { send_error_response(state, LIB_ERR_MALLOC_FAIL); } else { *count = count_handles(request->directory_entry_id); send_response(state, SYS_ERR_OK, sizeof(size_t), count); } } else { send_error_response(state, ERR_INVALID_ARGS); } // skip payload ump_recv_payload(state->recv_chan, NULL, handle_payload, arg); } static void handle_payload(void *arg, size_t payload_size, void *payload) { assert(arg != NULL); errval_t err; struct block_driver_state *state = arg; struct block_driver_request *request = state->current_request; // we are guaranteed to have a valid structure request here if (request->action == BLOCK_DRIVER_ACTION_WRITE) { // we got the payload, write it to disk err = write_object(request->block_number, request->offset, request->bytes, payload); send_response(state, err, 0, NULL); // free the payload buffer, we don't use it anymore free(payload); } // listen for the next request on this channel ump_recv_header(state->recv_chan, handle_request, arg); } static errval_t connect_callback(void *arg, struct capref cap) { errval_t err; printf("[block_driver_server] incoming connection\n"); struct ump_send_chan *send_chan; struct ump_recv_chan *recv_chan; // we can run the server on the default waitset since we are dispatching on // it for listening to connections anyways err = ump_chan_init(UMP_ROLE_SERVER, &send_chan, &recv_chan, sizeof(struct block_driver_request), cap, get_default_waitset()); if (err_is_fail(err)) return err; struct block_driver_state *state = malloc(sizeof(struct block_driver_state)); if (state == NULL) return LIB_ERR_MALLOC_FAIL; state->recv_chan = recv_chan; state->send_chan = send_chan; ump_recv_header(recv_chan, handle_request, state); printf("[block_driver_server] connection ready to receive requests\n"); return SYS_ERR_OK; } int main(int argc, char *argv[]) { errval_t err; // get the device capability from the arg cnode struct capref cap_arg0 = { .cnode = cnode_arg, .slot = 0 }; // map device registers uncachable void *device_register_vaddr; err = paging_map_frame_attr( get_current_paging_state(), &device_register_vaddr, IMX8X_SDHC_SIZE, cap_arg0, VREGION_FLAGS_READ_WRITE_NOCACHE ); if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to map device registers"); // initialize the sdhc driver printf("[block_driver_server] initializing sdhc driver\n"); err = sdhc_init(&sdhc, device_register_vaddr); if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to initialize sdhc driver"); // create the dma buffer struct capref dma_buffer_frame; size_t size; err = frame_alloc(&dma_buffer_frame, SDHC_BLOCK_SIZE, &size); if(err_is_fail(err)) return err_push(err, LIB_ERR_FRAME_ALLOC); // we need the physical address for the DMA read operation struct frame_identity dma_buffer_frame_id; err = frame_identify(dma_buffer_frame, &dma_buffer_frame_id); if (err_is_fail(err)) return err_push(err, LIB_ERR_FRAME_IDENTIFY); dma_buffer_phys = dma_buffer_frame_id.base; // map the scratchpad frame for us err = paging_map_frame_attr(get_current_paging_state(), &dma_buffer, size, dma_buffer_frame, VREGION_FLAGS_READ_WRITE); if (err_is_fail(err)) return err_push(err, LIB_ERR_PMAP_MAP); printf("[block_driver_server] registering\n"); struct ump_binding_server server; err = ump_binding_register(&server, UMP_SERVER_BLOCK_DRIVER, connect_callback, NULL); if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to register UMP server"); // wait for connections to the server printf("[block_driver_server] listening\n"); struct waitset *default_ws = get_default_waitset(); while (true) { err = event_dispatch(default_ws); if (err_is_fail(err)) { DEBUG_ERR(err, "in event_dispatch"); abort(); } } return EXIT_SUCCESS; }