1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <fcntl.h>
6#include <getopt.h>
7#include <stdio.h>
8#include <stdlib.h>
9
10#include <fbl/algorithm.h>
11#include <fbl/unique_fd.h>
12#include <lib/fzl/mapped-vmo.h>
13#include <lib/cksum.h>
14#include <pretty/hexdump.h>
15#include <zircon/assert.h>
16#include <zircon/device/nand.h>
17#include <zircon/device/nand-broker.h>
18#include <zircon/status.h>
19#include <zircon/syscalls.h>
20#include <zxcpp/new.h>
21
22#include "aml.h"
23
24namespace {
25
26constexpr char kUsageMessage[] = R"""(
27Low level access tool for a NAND device.
28WARNING: This tool may overwrite the NAND device.
29
30./nand-util --device /dev/sys/platform/05:00:d/aml-raw_nand/nand/broker --info
31
32Note that to use this tool the driver binding rules have to be adjusted so that
33the broker driver is loaded for the desired NAND device.
34
35Options:
36  --device (-d) path : Specifies the broker device to use.
37  --info (-i) : Show basic NAND information.
38  --bbt (-t) : Display bad block info.
39  --read (-r) --absolute xxx : Read the page number xxx (0-based).
40  --erase (-e) --block xxx : Erase the block number xxx (0-based).
41  --check (-c) : Looks for read errors on the device.
42  --absolute (-a) xxx : Use an absolute page number.
43  --page (-p) xxx : Use the xxx page number (from within a block).
44  --block (-b) xxx : Use the xxx block number.
45  --count (-n) xxx : Limit the operation to xxx blocks.
46                     Only supported with --check.
47)""";
48
49// Configuration info (what to do).
50struct Config {
51    const char* path;
52    uint32_t page_num;
53    uint32_t block_num;
54    uint32_t abs_page;
55    uint32_t count;
56    int actions;
57    bool info;
58    bool bbt;
59    bool read;
60    bool erase;
61    bool read_check;
62};
63
64// Broker device wrapper.
65class NandBroker {
66  public:
67    explicit NandBroker(const char* path) : device_(open(path, O_RDWR)) {}
68    ~NandBroker() {}
69
70    // Returns true on success.
71    bool Initialize();
72
73    // Returns a file descriptor for the device.
74    int get() const { return device_.get(); }
75
76    // The internal buffer can access a block at a time.
77    const char* data() const { return reinterpret_cast<char*>(vmo_->GetData()); }
78    const char* oob() const { return data() + info_.page_size * info_.pages_per_block; }
79
80    const nand_info_t& Info() const { return info_; }
81
82    // The operations to perform:
83    bool Query();
84    void ShowInfo() const;
85    bool ReadPages(uint32_t first_page, uint32_t count) const;
86    bool DumpPage(uint32_t page) const;
87    bool EraseBlock(uint32_t block) const;
88
89  private:
90    fbl::unique_fd device_;
91    nand_info_t info_ = {};
92    fbl::unique_ptr<fzl::MappedVmo> vmo_;
93};
94
95bool NandBroker::Initialize()  {
96    if (!Query()) {
97        printf("Failed to open or query the device\n");
98        return false;
99    }
100    const uint32_t size = (info_.page_size + info_.oob_size) * info_.pages_per_block;
101    if (fzl::MappedVmo::Create(size, nullptr, &vmo_) != ZX_OK) {
102        printf("Failed to allocate VMO\n");
103        return false;
104    }
105    return true;
106}
107
108bool NandBroker::Query() {
109    if (!device_) {
110        return false;
111    }
112
113    return ioctl_nand_broker_get_info(device_.get(), &info_) == sizeof(info_);
114}
115
116void NandBroker::ShowInfo() const {
117    printf("Page size: %d\nPages per block: %d\nTotal Blocks: %d\nOOB size: %d\nECC bits: %d\n"
118           "Nand class: %d\n", info_.page_size, info_.pages_per_block, info_.num_blocks,
119           info_.oob_size, info_.ecc_bits, info_.nand_class);
120}
121
122bool NandBroker::ReadPages(uint32_t first_page, uint32_t count) const {
123    ZX_DEBUG_ASSERT(count <= info_.pages_per_block);
124    nand_broker_request_t request = {};
125    nand_broker_response_t response = {};
126
127    request.length = count;
128    request.offset_nand = first_page;
129    request.offset_oob_vmo = info_.pages_per_block;  // OOB is at the end of the VMO.
130    request.data_vmo = true;
131    request.oob_vmo = true;
132
133    if (zx_handle_duplicate(vmo_->GetVmo(), ZX_RIGHT_SAME_RIGHTS, &request.vmo) != ZX_OK) {
134        printf("Failed to duplicate VMO\n");
135        return false;
136    }
137
138    if (ioctl_nand_broker_read(get(), &request, &response) != sizeof(response)) {
139        printf("Failed to issue command to driver\n");
140        return false;
141    }
142
143    if (response.status != ZX_OK) {
144        printf("Read to %d pages starting at %d failed with %s\n", count, first_page,
145               zx_status_get_string(response.status));
146        return false;
147    }
148
149    if (response.corrected_bit_flips > info_.ecc_bits) {
150        printf("Read to %d pages starting at %d unable to correct all bit flips\n", count,
151               first_page);
152    } else if (response.corrected_bit_flips) {
153        // If the nand protocol is modified to provide more info, we could display something
154        // like average bit flips.
155        printf("Read to %d pages starting at %d corrected %d errors\n", count, first_page,
156               response.corrected_bit_flips);
157    }
158
159    return true;
160}
161
162bool NandBroker::DumpPage(uint32_t page) const {
163    if (!ReadPages(page, 1)) {
164        return false;
165    }
166    ZX_DEBUG_ASSERT(info_.page_size % 16 == 0);
167
168    uint32_t address = page * info_.page_size;
169    hexdump8_ex(data(), 16, address);
170    int skip = 0;
171
172    for (uint32_t line = 16; line < info_.page_size; line += 16) {
173        if (memcmp(data() + line, data() + line - 16, 16) == 0) {
174            skip++;
175            if (skip < 50) {
176                printf(".");
177            }
178            continue;
179        }
180        if (skip) {
181            printf("\n");
182            skip = 0;
183        }
184        hexdump8_ex(data() + line, 16, address + line);
185    }
186
187    if (skip) {
188        printf("\n");
189    }
190
191    printf("OOB:\n");
192    hexdump8_ex(oob(), info_.oob_size, address + info_.page_size);
193    return true;
194}
195
196bool NandBroker::EraseBlock(uint32_t block) const {
197    nand_broker_request_t request = {};
198    nand_broker_response_t response = {};
199
200    request.length = 1;
201    request.offset_nand = block;
202
203    if (ioctl_nand_broker_erase(get(), &request, &response) != sizeof(response)) {
204        printf("Failed to issue command to driver\n");
205        return false;
206    }
207
208    if (response.status != ZX_OK) {
209        printf("Erase block %d failed with %s\n", block, zx_status_get_string(response.status));
210        return false;
211    }
212
213    return true;
214}
215
216bool GetOptions(int argc, char** argv, Config* config) {
217    while (true) {
218        struct option options[] = {
219            {"device", required_argument, nullptr, 'd'},
220            {"info", no_argument, nullptr, 'i'},
221            {"bbt", no_argument, nullptr, 't'},
222            {"read", no_argument, nullptr, 'r'},
223            {"erase", no_argument, nullptr, 'e'},
224            {"check", no_argument, nullptr, 'c'},
225            {"page", required_argument, nullptr, 'p'},
226            {"block", required_argument, nullptr, 'b'},
227            {"absolute", required_argument, nullptr, 'a'},
228            {"count", required_argument, nullptr, 'n'},
229            {"help", no_argument, nullptr, 'h'},
230            {nullptr, 0, nullptr, 0},
231        };
232        int opt_index;
233        int c = getopt_long(argc, argv, "d:irtecp:b:a:n:h", options, &opt_index);
234        if (c < 0) {
235            break;
236        }
237        switch (c) {
238        case 'd':
239            config->path = optarg;
240            break;
241        case 'i':
242            config->info = true;
243            break;
244        case 't':
245            config->bbt = true;
246            config->actions++;
247            break;
248        case 'r':
249            config->read = true;
250            config->actions++;
251            break;
252        case 'e':
253            config->erase = true;
254            config->actions++;
255            break;
256        case 'c':
257            config->read_check = true;
258            config->actions++;
259            break;
260        case 'p':
261            config->page_num = static_cast<uint32_t>(strtoul(optarg, NULL, 0));
262            break;
263        case 'b':
264            config->block_num = static_cast<uint32_t>(strtoul(optarg, NULL, 0));
265            break;
266        case 'a':
267            config->abs_page = static_cast<uint32_t>(strtoul(optarg, NULL, 0));
268            break;
269        case 'n':
270            config->count = static_cast<uint32_t>(strtoul(optarg, NULL, 0));
271            break;
272        case 'h':
273            printf("%s\n", kUsageMessage);
274            return 0;
275        }
276    }
277    return argc == optind;
278}
279
280bool ValidateOptions(const Config& config) {
281    if (!config.path) {
282        printf("Device needed\n");
283        printf("%s\n", kUsageMessage);
284        return false;
285    }
286
287    if (config.actions > 1) {
288        printf("Only one action allowed\n");
289        return false;
290    }
291
292    if (config.abs_page && config.page_num) {
293        printf("Provide either a block + page or an absolute page number\n");
294        return false;
295    }
296
297    if (config.erase && (config.page_num || config.abs_page)) {
298        printf("Erase works with blocks, not pages\n");
299        return false;
300    }
301
302    if (config.erase && config.block_num < 24) {
303        printf("Erasing the restricted area is not a good idea, sorry\n");
304        return false;
305    }
306
307    if (!config.info && !config.actions) {
308        printf("Nothing to do\n");
309        return false;
310    }
311
312    if (config.count && !config.read_check) {
313        printf("Count only supported for --check\n");
314        return false;
315    }
316    return true;
317}
318
319bool ValidateOptionsWithNand(const NandBroker& nand, const Config& config) {
320    if (config.page_num >= nand.Info().pages_per_block) {
321        printf("Page not within a block:\n");
322        return false;
323    }
324
325    if (config.block_num >= nand.Info().num_blocks) {
326        printf("Block not within device:\n");
327        return false;
328    }
329
330    if (config.abs_page >= nand.Info().num_blocks * nand.Info().pages_per_block) {
331        printf("Page not within device:\n");
332        return false;
333    }
334
335    return true;
336}
337
338bool FindBadBlocks(const NandBroker& nand) {
339    if (!nand.ReadPages(0, 1)) {
340        return false;
341    }
342
343    uint32_t first_block;
344    uint32_t num_blocks;
345    GetBbtLocation(nand.data(), &first_block, &num_blocks);
346    bool found = false;
347    for (uint32_t block = 0; block < num_blocks; block++) {
348        uint32_t start = (first_block + block) * nand.Info().pages_per_block;
349        if (!nand.ReadPages(start, nand.Info().pages_per_block)) {
350            return false;
351        }
352        if (!DumpBbt(nand.data(), nand.oob(), nand.Info())) {
353            break;
354        }
355        found = true;
356    }
357    if (!found) {
358        printf("Unable to find any table\n");
359    }
360    return found;
361}
362
363// Verifies that reads always return the same data.
364bool ReadCheck(const NandBroker& nand, uint32_t first_block, uint32_t count) {
365    constexpr int kNumReads = 10;
366    uint32_t num_blocks = fbl::min(nand.Info().num_blocks, first_block + count);
367    size_t size = (nand.Info().page_size + nand.Info().oob_size) * nand.Info().pages_per_block;
368    for (uint32_t block = first_block; block < num_blocks; block++) {
369        uint32_t first_crc;
370        for (int i = 0; i < kNumReads; i++) {
371            const uint32_t start = block * nand.Info().pages_per_block;
372            if (!nand.ReadPages(start, nand.Info().pages_per_block)) {
373                printf("\nRead failed for block %u\n", block);
374                return false;
375            }
376            const uint32_t crc = crc32(0, reinterpret_cast<const uint8_t*>(nand.data()), size);
377            if (!i) {
378                first_crc = crc;
379            } else if (first_crc != crc) {
380                printf("\nMismatched reads on block %u\n", block);
381                return false;
382            }
383        }
384        printf("Block %u\r", block);
385    }
386    printf("\ndone\n");
387    return true;
388}
389
390}  // namespace
391
392int main(int argc, char** argv) {
393    Config config = {};
394    if (!GetOptions(argc, argv, &config)) {
395        printf("%s\n", kUsageMessage);
396        return -1;
397    }
398
399    if (!ValidateOptions(config)) {
400        return -1;
401    }
402
403    NandBroker nand(config.path);
404    if (!nand.Initialize()) {
405        printf("Unable to open the nand device\n");
406        return -1;
407    }
408
409    if (config.info) {
410        nand.ShowInfo();
411        if (!nand.ReadPages(0, 1)) {
412            return -1;
413        }
414        DumpPage0(nand.data());
415    }
416
417    if (config.bbt) {
418        return FindBadBlocks(nand) ? 0 : -1;
419    }
420
421    if (!ValidateOptionsWithNand(nand, config)) {
422        nand.ShowInfo();
423        return -1;
424    }
425
426    if (config.read) {
427        if (!config.abs_page) {
428            config.abs_page = config.block_num * nand.Info().pages_per_block + config.page_num;
429        }
430        printf("To read page %d\n", config.abs_page);
431        return nand.DumpPage(config.abs_page) ? 0 : -1;
432    }
433
434    if (config.erase) {
435        printf("About to erase block %d. Press y to confirm\n", config.block_num);
436        if (getchar() != 'y') {
437            return -1;
438        }
439        return nand.EraseBlock(config.block_num) ? 0 : -1;
440    }
441
442    if (config.read_check) {
443        printf("Checking blocks...\n");
444        return ReadCheck(nand, config.block_num, config.count) ? 0 : -1;
445    }
446
447    return 0;
448}
449