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