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 <new>
7 #include <stdio.h>
8 #include <stdlib.h>
9 
10 #include <fbl/algorithm.h>
11 #include <fbl/unique_fd.h>
12 #include <fuchsia/nand/c/fidl.h>
13 #include <lib/fdio/watcher.h>
14 #include <lib/fzl/fdio.h>
15 #include <lib/fzl/vmo-mapper.h>
16 #include <lib/zx/vmo.h>
17 #include <unittest/unittest.h>
18 #include <zircon/device/device.h>
19 #include <zircon/syscalls.h>
20 
21 #include "parent.h"
22 
23 namespace {
24 
25 constexpr uint32_t kMinOobSize = 4;
26 constexpr uint32_t kMinBlockSize = 4;
27 constexpr uint32_t kMinNumBlocks = 5;
28 constexpr uint32_t kInMemoryPages = 20;
29 
OpenBroker(const char * path)30 fbl::unique_fd OpenBroker(const char* path) {
31     fbl::unique_fd broker;
32 
33     auto callback = [](int dir_fd, int event, const char* filename, void* cookie) {
34         if (event != WATCH_EVENT_ADD_FILE || strcmp(filename, "broker") != 0) {
35             return ZX_OK;
36         }
37         fbl::unique_fd* broker = reinterpret_cast<fbl::unique_fd*>(cookie);
38         broker->reset(openat(dir_fd, filename, O_RDWR));
39         return ZX_ERR_STOP;
40     };
41 
42     fbl::unique_fd dir(open(path, O_DIRECTORY));
43     if (dir) {
44         zx_time_t deadline = zx_deadline_after(ZX_SEC(5));
45         fdio_watch_directory(dir.get(), callback, deadline, &broker);
46     }
47     return broker;
48 }
49 
50 // The device under test.
51 class NandDevice {
52 public:
53     NandDevice();
~NandDevice()54     ~NandDevice() {
55         if (linked_) {
56             fbl::unique_fd broker = caller_.release();
57             ioctl_device_unbind(broker.get());
58         }
59     }
60 
IsValid() const61     bool IsValid() const { return is_valid_; }
62 
63     // Provides a channel to issue fidl calls.
channel()64     zx_handle_t channel() { return caller_.borrow_channel(); }
65 
66     // Wrappers for "queue" operations that take care of preserving the vmo's handle
67     // and translating the request to the desired block range on the actual device.
68     bool Read(const zx::vmo& vmo, const fuchsia_nand_BrokerRequest& request,
69               zx_status_t* response = nullptr);
70     bool Write(const zx::vmo& vmo, const fuchsia_nand_BrokerRequest& request,
71                zx_status_t* response = nullptr);
72     bool Erase(const fuchsia_nand_BrokerRequest& request, zx_status_t* response = nullptr);
73 
74     // Erases a given block number.
75     bool EraseBlock(uint32_t block_num);
76 
77     // Verifies that the buffer pointed to by the operation's vmo contains the given
78     // pattern for the desired number of pages, skipping the pages before start.
79     bool CheckPattern(uint8_t expected, int start, int num_pages, const void* memory);
80 
Info() const81     const fuchsia_hardware_nand_Info& Info() const { return parent_->Info(); }
82 
PageSize() const83     uint32_t PageSize() const { return parent_->Info().page_size; }
OobSize() const84     uint32_t OobSize() const { return parent_->Info().oob_size; }
BlockSize() const85     uint32_t BlockSize() const { return parent_->Info().pages_per_block; }
NumBlocks() const86     uint32_t NumBlocks() const { return num_blocks_; }
NumPages() const87     uint32_t NumPages() const { return num_blocks_ * BlockSize(); }
MaxBufferSize() const88     uint32_t MaxBufferSize() const { return kInMemoryPages * (PageSize() + OobSize()); }
89 
90     // True when the whole device under test can be modified.
IsFullDevice() const91     bool IsFullDevice() const { return full_device_; }
92 
93 private:
94     bool ValidateNandDevice();
95 
96     ParentDevice* parent_ = g_parent_device_;
97     fzl::FdioCaller caller_;
98     uint32_t num_blocks_ = 0;
99     uint32_t first_block_ = 0;
100     bool full_device_ = true;
101     bool linked_ = false;
102     bool is_valid_ = false;
103 };
104 
NandDevice()105 NandDevice::NandDevice() {
106     ZX_ASSERT(parent_->IsValid());
107     if (parent_->IsBroker()) {
108         caller_.reset(fbl::unique_fd(open(parent_->Path(), O_RDWR)));
109     } else {
110         const char kBroker[] = "/boot/driver/nand-broker.so";
111         if (ioctl_device_bind(parent_->get(), kBroker, sizeof(kBroker) - 1) < 0) {
112             unittest_printf_critical("Failed to bind broker\n");
113             return;
114         }
115         linked_ = true;
116         caller_.reset(OpenBroker(parent_->Path()));
117     }
118     is_valid_ = ValidateNandDevice();
119 }
120 
Read(const zx::vmo & vmo,const fuchsia_nand_BrokerRequest & request,zx_status_t * response)121 bool NandDevice::Read(const zx::vmo& vmo, const fuchsia_nand_BrokerRequest& request,
122                       zx_status_t* response) {
123     BEGIN_TEST;
124     fuchsia_nand_BrokerRequest request_copy = request;
125     if (!full_device_) {
126         request_copy.offset_nand = request.offset_nand + first_block_ * BlockSize();
127         ZX_DEBUG_ASSERT(request.offset_nand < NumPages());
128         ZX_DEBUG_ASSERT(request.offset_nand + request.length <= NumPages());
129     }
130 
131     uint32_t bit_flips;
132     zx_status_t status;
133     zx::vmo dup;
134     ASSERT_EQ(ZX_OK, vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
135     request_copy.vmo = dup.release();
136     ASSERT_EQ(ZX_OK, fuchsia_nand_BrokerRead(channel(), &request_copy, &status, &bit_flips));
137     if (response) {
138         *response = status;
139     } else {
140         ASSERT_EQ(ZX_OK, status);
141     }
142     ASSERT_EQ(0, bit_flips);
143     END_TEST;
144 }
145 
Write(const zx::vmo & vmo,const fuchsia_nand_BrokerRequest & request,zx_status_t * response)146 bool NandDevice::Write(const zx::vmo& vmo, const fuchsia_nand_BrokerRequest& request,
147                        zx_status_t* response) {
148     BEGIN_TEST;
149     fuchsia_nand_BrokerRequest request_copy = request;
150     if (!full_device_) {
151         request_copy.offset_nand = request.offset_nand + first_block_ * BlockSize();
152         ZX_DEBUG_ASSERT(request.offset_nand < NumPages());
153         ZX_DEBUG_ASSERT(request.offset_nand + request.length <= NumPages());
154     }
155 
156     zx_status_t status;
157     zx::vmo dup;
158     ASSERT_EQ(ZX_OK, vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
159     request_copy.vmo = dup.release();
160     ASSERT_EQ(ZX_OK, fuchsia_nand_BrokerWrite(channel(), &request_copy, &status));
161     if (response) {
162         *response = status;
163     } else {
164         ASSERT_EQ(ZX_OK, status);
165     }
166     END_TEST;
167 }
168 
Erase(const fuchsia_nand_BrokerRequest & request,zx_status_t * response)169 bool NandDevice::Erase(const fuchsia_nand_BrokerRequest& request, zx_status_t* response) {
170     BEGIN_TEST;
171     fuchsia_nand_BrokerRequest request_copy = request;
172     if (!full_device_) {
173         request_copy.offset_nand = request.offset_nand + first_block_;
174         ZX_DEBUG_ASSERT(request.offset_nand < NumBlocks());
175         ZX_DEBUG_ASSERT(request.offset_nand + request.length <= NumBlocks());
176     }
177 
178     zx_status_t status;
179     ASSERT_EQ(ZX_OK, fuchsia_nand_BrokerErase(channel(), &request_copy, &status));
180     if (response) {
181         *response = status;
182     } else {
183         ASSERT_EQ(ZX_OK, status);
184     }
185     END_TEST;
186 }
187 
EraseBlock(uint32_t block_num)188 bool NandDevice::EraseBlock(uint32_t block_num) {
189     BEGIN_TEST;
190     fuchsia_nand_BrokerRequest request = {};
191     request.length = 1;
192     request.offset_nand = block_num;
193     ASSERT_TRUE(Erase(request));
194     END_TEST;
195 }
196 
CheckPattern(uint8_t expected,int start,int num_pages,const void * memory)197 bool NandDevice::CheckPattern(uint8_t expected, int start, int num_pages, const void* memory) {
198     const uint8_t* buffer = reinterpret_cast<const uint8_t*>(memory) + PageSize() * start;
199     for (uint32_t i = 0; i < PageSize() * num_pages; i++) {
200         if (buffer[i] != expected) {
201             return false;
202         }
203     }
204     return true;
205 }
206 
ValidateNandDevice()207 bool NandDevice::ValidateNandDevice() {
208     if (parent_->IsExternal()) {
209         // This looks like using code under test to setup the test, but this
210         // path is for external devices, not really the broker. The issue is that
211         // ParentDevice cannot query a nand device for the actual parameters.
212         fuchsia_hardware_nand_Info info;
213         zx_status_t status;
214         if (fuchsia_nand_BrokerGetInfo(channel(), &status, &info) != ZX_OK || status != ZX_OK) {
215             printf("Failed to query nand device\n");
216             return false;
217         }
218         parent_->SetInfo(info);
219     }
220 
221     num_blocks_ = parent_->NumBlocks();
222     first_block_ = parent_->FirstBlock();
223     if (OobSize() < kMinOobSize || BlockSize() < kMinBlockSize || num_blocks_ < kMinNumBlocks ||
224         num_blocks_ + first_block_ > parent_->Info().num_blocks) {
225         printf("Invalid nand device parameters\n");
226         return false;
227     }
228     if (num_blocks_ != parent_->Info().num_blocks) {
229         // Not using the whole device, don't need to test all limits.
230         num_blocks_ = fbl::min(num_blocks_, kMinNumBlocks);
231         full_device_ = false;
232     }
233     return true;
234 }
235 
TrivialLifetimeTest()236 bool TrivialLifetimeTest() {
237     BEGIN_TEST;
238     NandDevice device;
239 
240     ASSERT_TRUE(device.IsValid());
241     END_TEST;
242 }
243 
QueryTest()244 bool QueryTest() {
245     BEGIN_TEST;
246     NandDevice device;
247     ASSERT_TRUE(device.IsValid());
248 
249     fuchsia_hardware_nand_Info info;
250     zx_status_t status;
251     ASSERT_EQ(ZX_OK, fuchsia_nand_BrokerGetInfo(device.channel(), &status, &info));
252     ASSERT_EQ(ZX_OK, status);
253 
254     EXPECT_EQ(device.Info().page_size, info.page_size);
255     EXPECT_EQ(device.Info().oob_size, info.oob_size);
256     EXPECT_EQ(device.Info().pages_per_block, info.pages_per_block);
257     EXPECT_EQ(device.Info().num_blocks, info.num_blocks);
258     EXPECT_EQ(device.Info().ecc_bits, info.ecc_bits);
259     EXPECT_EQ(device.Info().nand_class, info.nand_class);
260 
261     END_TEST;
262 }
263 
ReadWriteLimitsTest()264 bool ReadWriteLimitsTest() {
265     BEGIN_TEST;
266     NandDevice device;
267     ASSERT_TRUE(device.IsValid());
268 
269     fzl::VmoMapper mapper;
270     zx::vmo vmo;
271     ASSERT_EQ(ZX_OK, mapper.CreateAndMap(device.MaxBufferSize(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
272                                          nullptr, &vmo));
273 
274     fuchsia_nand_BrokerRequest request = {};
275     zx_status_t status;
276     ASSERT_TRUE(device.Read(vmo, request, &status));
277     EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
278 
279     ASSERT_TRUE(device.Write(vmo, request, &status));
280     EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
281 
282     if (device.IsFullDevice()) {
283         request.length = 1;
284         request.offset_nand = device.NumPages();
285 
286         ASSERT_TRUE(device.Read(vmo, request, &status));
287         EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
288 
289         ASSERT_TRUE(device.Write(vmo, request, &status));
290         EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
291 
292         request.length = 2;
293         request.offset_nand = device.NumPages() - 1;
294 
295         ASSERT_TRUE(device.Read(vmo, request, &status));
296         EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
297 
298         ASSERT_TRUE(device.Write(vmo, request, &status));
299         EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
300     }
301 
302     request.length = 1;
303     request.offset_nand = device.NumPages() - 1;
304 
305     ASSERT_TRUE(device.Read(vmo, request, &status));
306     EXPECT_EQ(ZX_ERR_BAD_HANDLE, status);
307 
308     ASSERT_TRUE(device.Write(vmo, request, &status));
309     EXPECT_EQ(ZX_ERR_BAD_HANDLE, status);
310 
311     request.data_vmo = true;
312 
313     ASSERT_TRUE(device.Read(vmo, request, &status));
314     EXPECT_EQ(ZX_OK, status);
315 
316     ASSERT_TRUE(device.Write(vmo, request, &status));
317     EXPECT_EQ(ZX_OK, status);
318 
319     END_TEST;
320 }
321 
EraseLimitsTest()322 bool EraseLimitsTest() {
323     return true;
324     BEGIN_TEST;
325     NandDevice device;
326     ASSERT_TRUE(device.IsValid());
327 
328     fuchsia_nand_BrokerRequest request = {};
329     zx_status_t status;
330     ASSERT_TRUE(device.Erase(request, &status));
331     EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
332 
333     request.offset_nand = device.NumBlocks();
334 
335     if (device.IsFullDevice()) {
336         request.length = 1;
337         ASSERT_TRUE(device.Erase(request, &status));
338         EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
339 
340         request.length = 2;
341         request.offset_nand = device.NumBlocks() - 1;
342         ASSERT_TRUE(device.Erase(request, &status));
343         EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
344     }
345 
346     request.length = 1;
347     request.offset_nand = device.NumBlocks() - 1;
348     ASSERT_TRUE(device.Erase(request, &status));
349     EXPECT_EQ(ZX_OK, status);
350 
351     END_TEST;
352 }
353 
ReadWriteTest()354 bool ReadWriteTest() {
355     BEGIN_TEST;
356     NandDevice device;
357     ASSERT_TRUE(device.IsValid());
358     ASSERT_TRUE(device.EraseBlock(0));
359 
360     fzl::VmoMapper mapper;
361     zx::vmo vmo;
362     ASSERT_EQ(ZX_OK, mapper.CreateAndMap(device.MaxBufferSize(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
363                                          nullptr, &vmo));
364     memset(mapper.start(), 0x55, mapper.size());
365 
366     fuchsia_nand_BrokerRequest request = {};
367     request.length = 4;
368     request.offset_nand = 4;
369     request.data_vmo = true;
370 
371     ASSERT_TRUE(device.Write(vmo, request));
372 
373     memset(mapper.start(), 0, mapper.size());
374 
375     ASSERT_TRUE(device.Read(vmo, request));
376     ASSERT_TRUE(device.CheckPattern(0x55, 0, 4, mapper.start()));
377 
378     END_TEST;
379 }
380 
ReadWriteOobTest()381 bool ReadWriteOobTest() {
382     BEGIN_TEST;
383     NandDevice device;
384     ASSERT_TRUE(device.IsValid());
385     ASSERT_TRUE(device.EraseBlock(0));
386 
387     fzl::VmoMapper mapper;
388     zx::vmo vmo;
389     ASSERT_EQ(ZX_OK, mapper.CreateAndMap(device.MaxBufferSize(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
390                                          nullptr, &vmo));
391     const char desired[] = {'a', 'b', 'c', 'd'};
392     memcpy(mapper.start(), desired, sizeof(desired));
393 
394     fuchsia_nand_BrokerRequest request = {};
395     request.length = 1;
396     request.offset_nand = 2;
397     request.oob_vmo = true;
398 
399     ASSERT_TRUE(device.Write(vmo, request));
400 
401     request.length = 2;
402     request.offset_nand = 1;
403     memset(mapper.start(), 0, device.OobSize() * 2);
404 
405     ASSERT_TRUE(device.Read(vmo, request));
406 
407     // The "second page" has the data of interest.
408     ASSERT_EQ(0,
409               memcmp(reinterpret_cast<char*>(mapper.start()) + device.OobSize(), desired,
410                      sizeof(desired)));
411 
412     END_TEST;
413 }
414 
ReadWriteDataAndOobTest()415 bool ReadWriteDataAndOobTest() {
416     BEGIN_TEST;
417     NandDevice device;
418     ASSERT_TRUE(device.IsValid());
419     ASSERT_TRUE(device.EraseBlock(0));
420 
421     fzl::VmoMapper mapper;
422     zx::vmo vmo;
423     ASSERT_EQ(ZX_OK, mapper.CreateAndMap(device.MaxBufferSize(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
424                                          nullptr, &vmo));
425 
426     char* buffer = reinterpret_cast<char*>(mapper.start());
427     memset(buffer, 0x55, device.PageSize() * 2);
428     memset(buffer + device.PageSize() * 2, 0xaa, device.OobSize() * 2);
429 
430     fuchsia_nand_BrokerRequest request = {};
431     request.length = 2;
432     request.offset_nand = 2;
433     request.offset_oob_vmo = 2; // OOB is right after data.
434     request.data_vmo = true;
435     request.oob_vmo = true;
436 
437     ASSERT_TRUE(device.Write(vmo, request));
438 
439     memset(buffer, 0, device.PageSize() * 4);
440 
441     ASSERT_TRUE(device.Read(vmo, request));
442 
443     // Verify data.
444     ASSERT_TRUE(device.CheckPattern(0x55, 0, 2, buffer));
445 
446     // Verify OOB.
447     memset(buffer, 0xaa, device.PageSize());
448     ASSERT_EQ(0, memcmp(buffer + device.PageSize() * 2, buffer, device.OobSize() * 2));
449 
450     END_TEST;
451 }
452 
EraseTest()453 bool EraseTest() {
454     BEGIN_TEST;
455     NandDevice device;
456     ASSERT_TRUE(device.IsValid());
457 
458     fzl::VmoMapper mapper;
459     zx::vmo vmo;
460     ASSERT_EQ(ZX_OK, mapper.CreateAndMap(device.MaxBufferSize(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
461                                          nullptr, &vmo));
462 
463     memset(mapper.start(), 0x55, mapper.size());
464 
465     fuchsia_nand_BrokerRequest request = {};
466     request.length = kMinBlockSize;
467     request.data_vmo = true;
468     request.offset_nand = device.BlockSize();
469     ASSERT_TRUE(device.Write(vmo, request));
470 
471     request.offset_nand = device.BlockSize() * 2;
472     ASSERT_TRUE(device.Write(vmo, request));
473 
474     ASSERT_TRUE(device.EraseBlock(1));
475     ASSERT_TRUE(device.EraseBlock(2));
476 
477     ASSERT_TRUE(device.Read(vmo, request));
478     ASSERT_TRUE(device.CheckPattern(0xff, 0, kMinBlockSize, mapper.start()));
479 
480     request.offset_nand = device.BlockSize();
481     ASSERT_TRUE(device.Read(vmo, request));
482     ASSERT_TRUE(device.CheckPattern(0xff, 0, kMinBlockSize, mapper.start()));
483 
484     END_TEST;
485 }
486 
487 } // namespace
488 
489 BEGIN_TEST_CASE(NandBrokerTests)
490 RUN_TEST_SMALL(TrivialLifetimeTest)
491 RUN_TEST_SMALL(QueryTest)
492 RUN_TEST_SMALL(ReadWriteLimitsTest)
493 RUN_TEST_SMALL(EraseLimitsTest)
494 RUN_TEST_SMALL(ReadWriteTest)
495 RUN_TEST_SMALL(ReadWriteOobTest)
496 RUN_TEST_SMALL(ReadWriteDataAndOobTest)
497 RUN_TEST_SMALL(EraseTest)
498 END_TEST_CASE(NandBrokerTests)
499