| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "nand.h" |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <bits/limits.h> |
| #include <ddk/debug.h> |
| |
| #include <lib/sync/completion.h> |
| #include <zircon/assert.h> |
| #include <zircon/status.h> |
| |
| #include <string.h> |
| |
| // The code in this file is only used for testing, the ioctl() is the entry |
| // point into this code, called by the nand driver's unit test. |
| static void nandtest_complete(void* cookie, zx_status_t status, nand_operation_t* nand_op) { |
| nand_op->command = status; |
| sync_completion_signal((sync_completion_t*)cookie); |
| } |
| |
| static zx_status_t nand_test_get_info(nand_device_t* dev, void* reply, size_t max, |
| size_t* out_actual) { |
| nandtest_resp_t* resp_hdr; |
| |
| if (max < sizeof(nand_info_t) + sizeof(resp_hdr)) { |
| zxlogf(ERROR, "%s: Bad response length\n", __func__); |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| resp_hdr = (nandtest_resp_t*)reply; |
| resp_hdr->status = ZX_OK; |
| nand_info_t nand_info; |
| size_t nand_op_size_out; |
| |
| dev->nand_proto.ops->query(dev, &nand_info, &nand_op_size_out); |
| memcpy((uint8_t*)reply + sizeof(nandtest_resp_t), &nand_info, sizeof(nand_info_t)); |
| *out_actual = sizeof(nand_info_t) + sizeof(resp_hdr); |
| return ZX_OK; |
| } |
| |
| static zx_status_t nand_test_read(nand_device_t* dev, const void* cmd, size_t cmd_length, |
| void* reply, size_t max_reply_sz, size_t* out_actual) { |
| nand_info_t nand_info; |
| size_t nand_op_size_out; |
| nand_io_t nand_io; |
| nand_operation_t* nand_op = &nand_io.nand_op; |
| nandtest_rw_page_data_oob_t* cmd_read_page; |
| nandtest_resp_t resp_hdr; |
| zx_handle_t vmo_data; |
| zx_handle_t vmo_oob; |
| zx_status_t status; |
| bool do_data = false; |
| bool do_oob = false; |
| |
| // Sanity check sizes of cmd and resp buffers. |
| dev->nand_proto.ops->query(dev, &nand_info, &nand_op_size_out); |
| if (cmd_length < sizeof(nandtest_rw_page_data_oob_t)) { |
| zxlogf(ERROR, "%s: Bad cmd length\n", __func__); |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| if (max_reply_sz < sizeof(resp_hdr)) { |
| zxlogf(ERROR, "%s: Bad response length\n", __func__); |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| cmd_read_page = (nandtest_rw_page_data_oob_t*)cmd; |
| vmo_data = cmd_read_page->data; |
| vmo_oob = cmd_read_page->oob; |
| if (cmd_read_page->data_len > 0) { |
| if (cmd_read_page->data_len != 1) { |
| zxlogf(ERROR, "%s: Bad cmd data_len %u\n", __func__, cmd_read_page->data_len); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| do_data = true; |
| } |
| if (cmd_read_page->oob_len > 0) { |
| if (cmd_read_page->oob_len != nand_info.oob_size) { |
| zxlogf(ERROR, "%s: Bad cmd oob_len %u\n", __func__, cmd_read_page->oob_len); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| do_oob = true; |
| } |
| |
| sync_completion_t completion = SYNC_COMPLETION_INIT; |
| |
| nand_op->command = NAND_OP_READ; |
| nand_op->rw.offset_nand = cmd_read_page->nand_page; |
| nand_op->rw.length = 1; |
| nand_op->rw.offset_data_vmo = 0; |
| nand_op->rw.offset_oob_vmo = 0; |
| |
| nand_op->rw.data_vmo = do_data ? vmo_data : ZX_HANDLE_INVALID; |
| nand_op->rw.oob_vmo = do_oob ? vmo_oob : ZX_HANDLE_INVALID; |
| |
| // Queue the data read op and wait for response. |
| dev->nand_proto.ops->queue(dev, nand_op, nandtest_complete, &completion); |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| |
| resp_hdr.status = nand_op->command; // Status stored here by callback. |
| status = resp_hdr.status; |
| memcpy(reply, &resp_hdr, sizeof(resp_hdr)); |
| *out_actual = sizeof(resp_hdr); |
| if (do_data) { |
| zx_handle_close(vmo_data); |
| } |
| if (do_oob) { |
| zx_handle_close(vmo_oob); |
| } |
| return status; |
| } |
| |
| static zx_status_t nand_test_write(nand_device_t* dev, const void* cmd, size_t cmd_length, |
| void* reply, size_t max_reply_sz, size_t* out_actual) { |
| nand_info_t nand_info; |
| size_t nand_op_size_out; |
| nand_io_t nand_io; |
| nand_operation_t* nand_op = &nand_io.nand_op; |
| nandtest_rw_page_data_oob_t* cmd_write_page; |
| nandtest_resp_t resp_hdr; |
| zx_handle_t vmo_data, vmo_oob; |
| zx_status_t status; |
| bool do_data = false; |
| bool do_oob = false; |
| |
| // Sanity check sizes of cmd and resp buffers. |
| dev->nand_proto.ops->query(dev, &nand_info, &nand_op_size_out); |
| if (cmd_length < sizeof(nandtest_rw_page_data_oob_t)) { |
| zxlogf(ERROR, "%s: Bad cmd length\n", __func__); |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| cmd_write_page = (nandtest_rw_page_data_oob_t*)cmd; |
| if (cmd_write_page->data_len > 0) { |
| if (cmd_write_page->data_len != 1) { |
| zxlogf(ERROR, "%s: Bad cmd data_len %u\n", __func__, cmd_write_page->data_len); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| do_data = true; |
| } |
| vmo_data = cmd_write_page->data; |
| vmo_oob = cmd_write_page->oob; |
| if (cmd_write_page->oob_len > 0) { |
| if (cmd_write_page->oob_len != nand_info.oob_size) { |
| zxlogf(ERROR, "%s: Bad cmd oob_len %u\n", __func__, cmd_write_page->oob_len); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| do_oob = true; |
| } |
| if (cmd_length < sizeof(nandtest_rw_page_data_oob_t)) { |
| zxlogf(ERROR, "%s: Bad cmd length\n", __func__); |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| if (max_reply_sz < sizeof(resp_hdr)) { |
| zxlogf(ERROR, "%s: Bad response length\n", __func__); |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| *out_actual = sizeof(resp_hdr); |
| |
| sync_completion_t completion = SYNC_COMPLETION_INIT; |
| |
| // Create nand_op. |
| nand_op->command = NAND_OP_WRITE; |
| nand_op->rw.offset_nand = cmd_write_page->nand_page; |
| nand_op->rw.length = 1; |
| nand_op->rw.offset_data_vmo = 0; |
| nand_op->rw.offset_oob_vmo = 0; |
| |
| nand_op->rw.data_vmo = do_data ? vmo_data : ZX_HANDLE_INVALID; |
| nand_op->rw.oob_vmo = do_oob ? vmo_oob : ZX_HANDLE_INVALID; |
| |
| // Queue the data read op and wait for response. |
| dev->nand_proto.ops->queue(dev, nand_op, nandtest_complete, &completion); |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| |
| resp_hdr.status = nand_op->command; // Status stored here by callback. |
| status = resp_hdr.status; |
| memcpy(reply, &resp_hdr, sizeof(resp_hdr)); |
| *out_actual = sizeof(resp_hdr); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Got error back from PAGE write (%d)\n", __func__, status); |
| status = ZX_OK; // Error code is in response header. |
| } |
| if (do_data) { |
| zx_handle_close(vmo_data); |
| } |
| if (do_oob) { |
| zx_handle_close(vmo_oob); |
| } |
| return status; |
| } |
| |
| static zx_status_t nand_test_erase_block(nand_device_t* dev, const void* cmd, |
| size_t cmd_length, void* reply, size_t max_reply_sz, |
| size_t* out_actual) { |
| nand_info_t nand_info; |
| size_t nand_op_size_out; |
| nand_io_t nand_io; |
| nand_operation_t* nand_op = &nand_io.nand_op; |
| nandtest_cmd_erase_block_t* cmd_erase_block; |
| nandtest_resp_t resp_hdr; |
| |
| // Sanity check sizes of cmd and resp buffers. |
| dev->nand_proto.ops->query(dev, &nand_info, &nand_op_size_out); |
| if (cmd_length < sizeof(nandtest_cmd_erase_block_t)) { |
| zxlogf(ERROR, "%s: Bad cmd length\n", __func__); |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| cmd_erase_block = (nandtest_cmd_erase_block_t*)cmd; |
| if (max_reply_sz < sizeof(resp_hdr)) { |
| zxlogf(ERROR, "%s: Bad response buffer length\n", __func__); |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| |
| sync_completion_t completion = SYNC_COMPLETION_INIT; |
| |
| // Create nand_op. |
| nand_op->command = NAND_OP_ERASE; |
| nand_op->erase.first_block = cmd_erase_block->nandblock; |
| nand_op->erase.num_blocks = 1; |
| |
| // Queue the data read op and wait for response. |
| dev->nand_proto.ops->queue(dev, nand_op, nandtest_complete, &completion); |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| |
| resp_hdr.status = nand_op->command; // Status stored here by callback. |
| memcpy(reply, &resp_hdr, sizeof(resp_hdr)); |
| *out_actual = sizeof(resp_hdr); |
| return ZX_OK; |
| } |
| |
| // nand_ioctl() is *only* for testing purposes. This allows a userspace process |
| // to test reads/writes/erases down into the top level nand driver. |
| // |
| // The nand_page/length/data buffer are passed in via the ioctl. The ioctl code |
| // will create and prep a vmo, allocate a nand_op and queue the nand_op to the |
| // nand device. For read/write/erase, it will block until signalled by the |
| // completion callback and then return back status from the ioctl. |
| zx_status_t nand_ioctl(void* ctx, uint32_t op, const void* cmd, size_t cmd_length, void* reply, |
| size_t max_reply_sz, size_t* out_actual) { |
| nand_device_t* dev = ctx; |
| |
| switch (op) { |
| // Construct and send a query command to the nand driver, |
| // and report back the nand_info_t and nand_op_size_out. |
| case IOCTL_NAND_GET_NAND_INFO: |
| return nand_test_get_info(dev, reply, max_reply_sz, out_actual); |
| |
| // Read data + oob for a single page. |
| case IOCTL_NAND_READ_PAGE_DATA_OOB: |
| return nand_test_read(dev, cmd, cmd_length, reply, max_reply_sz, out_actual); |
| |
| // Write data + oob for a single page. |
| case IOCTL_NAND_WRITE_PAGE_DATA_OOB: |
| return nand_test_write(dev, cmd, cmd_length, reply, max_reply_sz, out_actual); |
| |
| // Construct and queue a ERASE command (for the block range) to the |
| // nand driver, and send back the status. |
| case IOCTL_NAND_ERASE_BLOCK: |
| return nand_test_erase_block(dev, cmd, cmd_length, reply, max_reply_sz, out_actual); |
| |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| return ZX_OK; |
| } |