1 /*
2 * Copyright (c) 2016 Intel Corporation.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <string.h>
8 #include <zephyr/types.h>
9 #include <sys/__assert.h>
10 #include <sys/util.h>
11 #include <drivers/disk.h>
12 #include <errno.h>
13 #include <init.h>
14 #include <device.h>
15 #include <drivers/flash.h>
16
17 #define SECTOR_SIZE CONFIG_DISK_FLASH_SECTOR_SIZE
18
19 static const struct device *flash_dev;
20
21 /* flash read-copy-erase-write operation */
22 static uint8_t __aligned(4) read_copy_buf[CONFIG_DISK_ERASE_BLOCK_SIZE];
23 static uint8_t *fs_buff = read_copy_buf;
24
25 /* calculate number of blocks required for a given size */
26 #define GET_NUM_BLOCK(total_size, block_size) \
27 ((total_size + block_size - 1) / block_size)
28
29 #define GET_SIZE_TO_BOUNDARY(start, block_size) \
30 (block_size - (start & (block_size - 1)))
31
lba_to_address(uint32_t sector_num)32 static off_t lba_to_address(uint32_t sector_num)
33 {
34 off_t flash_addr;
35
36 flash_addr = CONFIG_DISK_FLASH_START + sector_num * SECTOR_SIZE;
37
38 __ASSERT(flash_addr < (CONFIG_DISK_FLASH_START +
39 CONFIG_DISK_VOLUME_SIZE), "FS bound error");
40
41 return flash_addr;
42 }
43
disk_flash_access_status(struct disk_info * disk)44 static int disk_flash_access_status(struct disk_info *disk)
45 {
46 if (!flash_dev) {
47 return DISK_STATUS_NOMEDIA;
48 }
49
50 return DISK_STATUS_OK;
51 }
52
disk_flash_access_init(struct disk_info * disk)53 static int disk_flash_access_init(struct disk_info *disk)
54 {
55 if (flash_dev) {
56 return 0;
57 }
58
59 flash_dev = device_get_binding(CONFIG_DISK_FLASH_DEV_NAME);
60 if (!flash_dev) {
61 return -ENODEV;
62 }
63
64 return 0;
65 }
66
disk_flash_access_read(struct disk_info * disk,uint8_t * buff,uint32_t start_sector,uint32_t sector_count)67 static int disk_flash_access_read(struct disk_info *disk, uint8_t *buff,
68 uint32_t start_sector, uint32_t sector_count)
69 {
70 off_t fl_addr;
71 uint32_t remaining;
72 uint32_t len;
73 uint32_t num_read;
74
75 fl_addr = lba_to_address(start_sector);
76 remaining = (sector_count * SECTOR_SIZE);
77 len = CONFIG_DISK_FLASH_MAX_RW_SIZE;
78
79 num_read = GET_NUM_BLOCK(remaining, CONFIG_DISK_FLASH_MAX_RW_SIZE);
80
81 for (uint32_t i = 0; i < num_read; i++) {
82 if (remaining < CONFIG_DISK_FLASH_MAX_RW_SIZE) {
83 len = remaining;
84 }
85
86 if (flash_read(flash_dev, fl_addr, buff, len) != 0) {
87 return -EIO;
88 }
89
90 fl_addr += len;
91 buff += len;
92 remaining -= len;
93 }
94
95 return 0;
96 }
97
98 /* This performs read-copy into an output buffer */
read_copy_flash_block(off_t start_addr,uint32_t size,const void * src_buff,uint8_t * dest_buff)99 static int read_copy_flash_block(off_t start_addr, uint32_t size,
100 const void *src_buff,
101 uint8_t *dest_buff)
102 {
103 off_t fl_addr;
104 uint32_t num_read;
105 uint32_t offset = 0U;
106
107 /* adjust offset if starting address is not erase-aligned address */
108 if (start_addr & (CONFIG_DISK_FLASH_ERASE_ALIGNMENT - 1)) {
109 offset = start_addr & (CONFIG_DISK_FLASH_ERASE_ALIGNMENT - 1);
110 }
111
112 /* align starting address to an aligned address for flash erase-write */
113 fl_addr = ROUND_DOWN(start_addr, CONFIG_DISK_FLASH_ERASE_ALIGNMENT);
114
115 num_read = GET_NUM_BLOCK(CONFIG_DISK_ERASE_BLOCK_SIZE,
116 CONFIG_DISK_FLASH_MAX_RW_SIZE);
117
118 /* read one block from flash */
119 for (uint32_t i = 0; i < num_read; i++) {
120 int rc;
121
122 rc = flash_read(flash_dev,
123 fl_addr + (CONFIG_DISK_FLASH_MAX_RW_SIZE * i),
124 dest_buff + (CONFIG_DISK_FLASH_MAX_RW_SIZE * i),
125 CONFIG_DISK_FLASH_MAX_RW_SIZE);
126 if (rc != 0) {
127 return -EIO;
128 }
129 }
130
131 /* overwrite with user data */
132 memcpy(dest_buff + offset, src_buff, size);
133
134 return 0;
135 }
136
137 /* input size is either less or equal to a block size,
138 * CONFIG_DISK_ERASE_BLOCK_SIZE.
139 */
update_flash_block(off_t start_addr,uint32_t size,const void * buff)140 static int update_flash_block(off_t start_addr, uint32_t size, const void *buff)
141 {
142 off_t fl_addr;
143 uint8_t *src = (uint8_t *)buff;
144 uint32_t num_write;
145
146 /* if size is a partial block, perform read-copy with user data */
147 if (size < CONFIG_DISK_ERASE_BLOCK_SIZE) {
148 int rc;
149
150 rc = read_copy_flash_block(start_addr, size, buff, fs_buff);
151 if (rc != 0) {
152 return -EIO;
153 }
154
155 /* now use the local buffer as the source */
156 src = (uint8_t *)fs_buff;
157 }
158
159 /* always align starting address for flash write operation */
160 fl_addr = ROUND_DOWN(start_addr, CONFIG_DISK_FLASH_ERASE_ALIGNMENT);
161
162 if (flash_erase(flash_dev, fl_addr, CONFIG_DISK_ERASE_BLOCK_SIZE)
163 != 0) {
164 return -EIO;
165 }
166
167 /* write data to flash */
168 num_write = GET_NUM_BLOCK(CONFIG_DISK_ERASE_BLOCK_SIZE,
169 CONFIG_DISK_FLASH_MAX_RW_SIZE);
170
171 for (uint32_t i = 0; i < num_write; i++) {
172 if (flash_write(flash_dev, fl_addr, src,
173 CONFIG_DISK_FLASH_MAX_RW_SIZE) != 0) {
174 return -EIO;
175 }
176
177 fl_addr += CONFIG_DISK_FLASH_MAX_RW_SIZE;
178 src += CONFIG_DISK_FLASH_MAX_RW_SIZE;
179 }
180
181 return 0;
182 }
183
disk_flash_access_write(struct disk_info * disk,const uint8_t * buff,uint32_t start_sector,uint32_t sector_count)184 static int disk_flash_access_write(struct disk_info *disk, const uint8_t *buff,
185 uint32_t start_sector, uint32_t sector_count)
186 {
187 off_t fl_addr;
188 uint32_t remaining;
189 uint32_t size;
190
191 fl_addr = lba_to_address(start_sector);
192 remaining = (sector_count * SECTOR_SIZE);
193
194 /* check if start address is erased-aligned address */
195 if (fl_addr & (CONFIG_DISK_FLASH_ERASE_ALIGNMENT - 1)) {
196 off_t block_bnd;
197
198 /* not aligned */
199 /* check if the size goes over flash block boundary */
200 block_bnd = fl_addr + CONFIG_DISK_ERASE_BLOCK_SIZE;
201 block_bnd = block_bnd & ~(CONFIG_DISK_ERASE_BLOCK_SIZE - 1);
202 if ((fl_addr + remaining) < block_bnd) {
203 /* not over block boundary (a partial block also) */
204 if (update_flash_block(fl_addr, remaining, buff) != 0) {
205 return -EIO;
206 }
207 return 0;
208 }
209
210 /* write goes over block boundary */
211 size = GET_SIZE_TO_BOUNDARY(fl_addr,
212 CONFIG_DISK_ERASE_BLOCK_SIZE);
213
214 /* write first partial block */
215 if (update_flash_block(fl_addr, size, buff) != 0) {
216 return -EIO;
217 }
218
219 fl_addr += size;
220 remaining -= size;
221 buff += size;
222 }
223
224 /* start is an erase-aligned address */
225 while (remaining) {
226 int rc;
227
228 if (remaining < CONFIG_DISK_ERASE_BLOCK_SIZE) {
229 break;
230 }
231
232 rc = update_flash_block(fl_addr, CONFIG_DISK_ERASE_BLOCK_SIZE,
233 buff);
234 if (rc != 0) {
235 return -EIO;
236 }
237
238 fl_addr += CONFIG_DISK_ERASE_BLOCK_SIZE;
239 remaining -= CONFIG_DISK_ERASE_BLOCK_SIZE;
240 buff += CONFIG_DISK_ERASE_BLOCK_SIZE;
241 }
242
243 /* remaining partial block */
244 if (remaining) {
245 if (update_flash_block(fl_addr, remaining, buff) != 0) {
246 return -EIO;
247 }
248 }
249
250 return 0;
251 }
252
disk_flash_access_ioctl(struct disk_info * disk,uint8_t cmd,void * buff)253 static int disk_flash_access_ioctl(struct disk_info *disk, uint8_t cmd, void *buff)
254 {
255 switch (cmd) {
256 case DISK_IOCTL_CTRL_SYNC:
257 return 0;
258 case DISK_IOCTL_GET_SECTOR_COUNT:
259 *(uint32_t *)buff = CONFIG_DISK_VOLUME_SIZE / SECTOR_SIZE;
260 return 0;
261 case DISK_IOCTL_GET_SECTOR_SIZE:
262 *(uint32_t *) buff = SECTOR_SIZE;
263 return 0;
264 case DISK_IOCTL_GET_ERASE_BLOCK_SZ: /* in sectors */
265 *(uint32_t *)buff = CONFIG_DISK_ERASE_BLOCK_SIZE / SECTOR_SIZE;
266 return 0;
267 default:
268 break;
269 }
270
271 return -EINVAL;
272 }
273
274 static const struct disk_operations flash_disk_ops = {
275 .init = disk_flash_access_init,
276 .status = disk_flash_access_status,
277 .read = disk_flash_access_read,
278 .write = disk_flash_access_write,
279 .ioctl = disk_flash_access_ioctl,
280 };
281
282 static struct disk_info flash_disk = {
283 .name = CONFIG_DISK_FLASH_VOLUME_NAME,
284 .ops = &flash_disk_ops,
285 };
286
disk_flash_init(const struct device * dev)287 static int disk_flash_init(const struct device *dev)
288 {
289 ARG_UNUSED(dev);
290
291 return disk_access_register(&flash_disk);
292 }
293
294 SYS_INIT(disk_flash_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
295