diff options
Diffstat (limited to 'lib/x1000-installer/src/xf_nandio.c')
-rw-r--r-- | lib/x1000-installer/src/xf_nandio.c | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/lib/x1000-installer/src/xf_nandio.c b/lib/x1000-installer/src/xf_nandio.c new file mode 100644 index 0000000000..ba79cbbcbf --- /dev/null +++ b/lib/x1000-installer/src/xf_nandio.c | |||
@@ -0,0 +1,295 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2021 Aidan MacDonald | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #include "xf_nandio.h" | ||
23 | #include "xf_error.h" | ||
24 | #include "core_alloc.h" | ||
25 | #include "system.h" | ||
26 | #include <string.h> | ||
27 | #include <stdbool.h> | ||
28 | |||
29 | int xf_nandio_init(struct xf_nandio* nio) | ||
30 | { | ||
31 | int rc; | ||
32 | |||
33 | memset(nio, 0, sizeof(*nio)); | ||
34 | |||
35 | /* open NAND */ | ||
36 | nio->ndrv = nand_init(); | ||
37 | nand_lock(nio->ndrv); | ||
38 | rc = nand_open(nio->ndrv); | ||
39 | if(rc != NAND_SUCCESS) { | ||
40 | nio->nand_err = rc; | ||
41 | rc = XF_E_NAND; | ||
42 | goto out; | ||
43 | } | ||
44 | |||
45 | /* read chip parameters */ | ||
46 | nio->page_size = nio->ndrv->chip->page_size; | ||
47 | nio->block_size = nio->page_size << nio->ndrv->chip->log2_ppb; | ||
48 | |||
49 | /* allocate memory */ | ||
50 | size_t alloc_size = 0; | ||
51 | alloc_size += CACHEALIGN_SIZE - 1; | ||
52 | alloc_size += nio->block_size * 2; | ||
53 | |||
54 | nio->alloc_handle = core_alloc("xf_nandio", alloc_size); | ||
55 | if(nio->alloc_handle < 0) { | ||
56 | rc = XF_E_OUT_OF_MEMORY; | ||
57 | goto out_nclose; | ||
58 | } | ||
59 | |||
60 | uint8_t* buffer = core_get_data(nio->alloc_handle); | ||
61 | CACHEALIGN_BUFFER(buffer, alloc_size); | ||
62 | |||
63 | nio->old_buf = buffer; | ||
64 | nio->new_buf = &buffer[nio->block_size]; | ||
65 | |||
66 | rc = XF_E_SUCCESS; | ||
67 | goto out; | ||
68 | |||
69 | out_nclose: | ||
70 | nand_close(nio->ndrv); | ||
71 | out: | ||
72 | nand_unlock(nio->ndrv); | ||
73 | return rc; | ||
74 | } | ||
75 | |||
76 | void xf_nandio_destroy(struct xf_nandio* nio) | ||
77 | { | ||
78 | if(nio->alloc_handle > 0) { | ||
79 | core_free(nio->alloc_handle); | ||
80 | nio->alloc_handle = 0; | ||
81 | } | ||
82 | |||
83 | if(nio->ndrv) { | ||
84 | nand_lock(nio->ndrv); | ||
85 | nand_close(nio->ndrv); | ||
86 | nand_unlock(nio->ndrv); | ||
87 | nio->ndrv = NULL; | ||
88 | } | ||
89 | } | ||
90 | |||
91 | static bool is_page_blank(const uint8_t* buf, uint32_t length) | ||
92 | { | ||
93 | for(uint32_t i = 0; i < length; ++i) | ||
94 | if(buf[i] != 0xff) | ||
95 | return false; | ||
96 | |||
97 | return true; | ||
98 | } | ||
99 | |||
100 | static int flush_block(struct xf_nandio* nio, bool invalidate) | ||
101 | { | ||
102 | /* no block, or only reading - flush is a no-op */ | ||
103 | if(!nio->block_valid || nio->mode == XF_NANDIO_READ) | ||
104 | return XF_E_SUCCESS; | ||
105 | |||
106 | /* nothing to do if new data is same as old data */ | ||
107 | if(!memcmp(nio->old_buf, nio->new_buf, nio->block_size)) | ||
108 | return XF_E_SUCCESS; | ||
109 | |||
110 | /* data mismatch during verification - report the error */ | ||
111 | if(nio->mode == XF_NANDIO_VERIFY) | ||
112 | return XF_E_VERIFY_FAILED; | ||
113 | |||
114 | /* erase the block */ | ||
115 | int rc = nand_block_erase(nio->ndrv, nio->cur_block); | ||
116 | if(rc != NAND_SUCCESS) { | ||
117 | nio->block_valid = false; | ||
118 | nio->nand_err = rc; | ||
119 | return XF_E_NAND; | ||
120 | } | ||
121 | |||
122 | size_t oob_size = nio->ndrv->chip->oob_size; | ||
123 | |||
124 | unsigned page = 0; | ||
125 | nand_page_t page_addr = nio->cur_block; | ||
126 | for(; page < nio->ndrv->ppb; ++page, ++page_addr) { | ||
127 | /* skip programming blank pages to go faster & reduce wear */ | ||
128 | uint8_t* page_data = &nio->new_buf[page * nio->page_size]; | ||
129 | if(is_page_blank(page_data, nio->page_size)) | ||
130 | continue; | ||
131 | |||
132 | /* copy page and write blank OOB data */ | ||
133 | memcpy(nio->ndrv->page_buf, page_data, nio->page_size); | ||
134 | memset(&nio->ndrv->page_buf[nio->page_size], 0xff, oob_size); | ||
135 | |||
136 | /* program the page */ | ||
137 | rc = nand_page_program(nio->ndrv, page_addr, nio->ndrv->page_buf); | ||
138 | if(rc != NAND_SUCCESS) { | ||
139 | nio->block_valid = false; | ||
140 | nio->nand_err = rc; | ||
141 | return XF_E_NAND; | ||
142 | } | ||
143 | } | ||
144 | |||
145 | if(invalidate) | ||
146 | nio->block_valid = false; | ||
147 | else { | ||
148 | /* update our 'old' buffer so a subsequent flush | ||
149 | * will not reprogram the same block */ | ||
150 | memcpy(nio->old_buf, nio->new_buf, nio->block_size); | ||
151 | } | ||
152 | |||
153 | return XF_E_SUCCESS; | ||
154 | } | ||
155 | |||
156 | static int seek_to_block(struct xf_nandio* nio, nand_block_t block_addr, | ||
157 | size_t offset_in_block) | ||
158 | { | ||
159 | /* already on this block? */ | ||
160 | if(nio->block_valid && block_addr == nio->cur_block) { | ||
161 | nio->offset_in_block = offset_in_block; | ||
162 | return XF_E_SUCCESS; | ||
163 | } | ||
164 | |||
165 | /* ensure new block is within range */ | ||
166 | if(block_addr >= (nio->ndrv->chip->nr_blocks << nio->ndrv->chip->log2_ppb)) | ||
167 | return XF_E_OUT_OF_RANGE; | ||
168 | |||
169 | /* flush old block */ | ||
170 | int rc = flush_block(nio, true); | ||
171 | if(rc) | ||
172 | return rc; | ||
173 | |||
174 | nio->block_valid = false; | ||
175 | |||
176 | /* read the new block */ | ||
177 | unsigned page = 0; | ||
178 | nand_page_t page_addr = block_addr; | ||
179 | for(; page < nio->ndrv->ppb; ++page, ++page_addr) { | ||
180 | rc = nand_page_read(nio->ndrv, page_addr, nio->ndrv->page_buf); | ||
181 | if(rc != NAND_SUCCESS) { | ||
182 | nio->nand_err = rc; | ||
183 | return XF_E_NAND; | ||
184 | } | ||
185 | |||
186 | memcpy(&nio->old_buf[page * nio->page_size], nio->ndrv->page_buf, nio->page_size); | ||
187 | } | ||
188 | |||
189 | /* copy to 2nd buffer */ | ||
190 | memcpy(nio->new_buf, nio->old_buf, nio->block_size); | ||
191 | |||
192 | /* update position */ | ||
193 | nio->cur_block = block_addr; | ||
194 | nio->offset_in_block = offset_in_block; | ||
195 | nio->block_valid = true; | ||
196 | return XF_E_SUCCESS; | ||
197 | } | ||
198 | |||
199 | int xf_nandio_set_mode(struct xf_nandio* nio, enum xf_nandio_mode mode) | ||
200 | { | ||
201 | nand_lock(nio->ndrv); | ||
202 | |||
203 | /* flush the current block before switching to the new mode, | ||
204 | * to ensure consistency */ | ||
205 | int rc = flush_block(nio, false); | ||
206 | if(rc) | ||
207 | goto err; | ||
208 | |||
209 | nio->mode = mode; | ||
210 | rc = XF_E_SUCCESS; | ||
211 | |||
212 | err: | ||
213 | nand_unlock(nio->ndrv); | ||
214 | return rc; | ||
215 | } | ||
216 | |||
217 | static int nandio_rdwr(struct xf_nandio* nio, void* buf, size_t count, bool write) | ||
218 | { | ||
219 | while(count > 0) { | ||
220 | void* ptr; | ||
221 | size_t amount = count; | ||
222 | int rc = xf_nandio_get_buffer(nio, &ptr, &amount); | ||
223 | if(rc) | ||
224 | return rc; | ||
225 | |||
226 | if(write) | ||
227 | memcpy(ptr, buf, amount); | ||
228 | else | ||
229 | memcpy(buf, ptr, amount); | ||
230 | |||
231 | count -= amount; | ||
232 | } | ||
233 | |||
234 | return XF_E_SUCCESS; | ||
235 | } | ||
236 | |||
237 | int xf_nandio_seek(struct xf_nandio* nio, size_t offset) | ||
238 | { | ||
239 | uint32_t block_nr = offset / nio->block_size; | ||
240 | size_t offset_in_block = offset % nio->block_size; | ||
241 | nand_block_t block_addr = block_nr << nio->ndrv->chip->log2_ppb; | ||
242 | |||
243 | nand_lock(nio->ndrv); | ||
244 | int rc = seek_to_block(nio, block_addr, offset_in_block); | ||
245 | nand_unlock(nio->ndrv); | ||
246 | |||
247 | return rc; | ||
248 | } | ||
249 | |||
250 | int xf_nandio_read(struct xf_nandio* nio, void* buf, size_t count) | ||
251 | { | ||
252 | return nandio_rdwr(nio, buf, count, false); | ||
253 | } | ||
254 | |||
255 | int xf_nandio_write(struct xf_nandio* nio, const void* buf, size_t count) | ||
256 | { | ||
257 | return nandio_rdwr(nio, (void*)buf, count, true); | ||
258 | } | ||
259 | |||
260 | int xf_nandio_get_buffer(struct xf_nandio* nio, void** buf, size_t* count) | ||
261 | { | ||
262 | nand_lock(nio->ndrv); | ||
263 | |||
264 | /* make sure the current block data is read in */ | ||
265 | int rc = seek_to_block(nio, nio->cur_block, nio->offset_in_block); | ||
266 | if(rc) | ||
267 | goto err; | ||
268 | |||
269 | size_t amount_left = nio->block_size - nio->offset_in_block; | ||
270 | if(amount_left == 0) { | ||
271 | amount_left = nio->block_size; | ||
272 | rc = seek_to_block(nio, nio->cur_block + nio->ndrv->ppb, 0); | ||
273 | if(rc) | ||
274 | goto err; | ||
275 | } | ||
276 | |||
277 | *buf = &nio->new_buf[nio->offset_in_block]; | ||
278 | *count = MIN(*count, amount_left); | ||
279 | |||
280 | nio->offset_in_block += *count; | ||
281 | rc = XF_E_SUCCESS; | ||
282 | |||
283 | err: | ||
284 | nand_unlock(nio->ndrv); | ||
285 | return rc; | ||
286 | } | ||
287 | |||
288 | int xf_nandio_flush(struct xf_nandio* nio) | ||
289 | { | ||
290 | nand_lock(nio->ndrv); | ||
291 | int rc = flush_block(nio, false); | ||
292 | nand_unlock(nio->ndrv); | ||
293 | |||
294 | return rc; | ||
295 | } | ||