Skip to content

Commit a4c59a3

Browse files
tmon-nordiccarlescufi
authored andcommitted
drivers: flashdisk: implement basic write caching
Cache written data to avoid rewriting same flash page multiple times when writing subsequent flash pages. The cache is used for reads to account for reading not yet committed (i.e. dirty) page data. Speeding up reads is not intention of this patch and therefore the read path does not modify cache state. Fixes: zephyrproject-rtos#30212 Signed-off-by: Tomasz Moń <[email protected]>
1 parent 04059ec commit a4c59a3

File tree

1 file changed

+86
-75
lines changed

1 file changed

+86
-75
lines changed

drivers/disk/flashdisk.c

+86-75
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,11 @@ struct flashdisk_data {
2828
const size_t size;
2929
const size_t sector_size;
3030
size_t page_size;
31+
off_t cached_addr;
32+
bool cache_valid;
33+
bool cache_dirty;
3134
};
3235

33-
/* calculate number of blocks required for a given size */
34-
#define GET_NUM_BLOCK(total_size, block_size) \
35-
((total_size + block_size - 1) / block_size)
36-
3736
#define GET_SIZE_TO_BOUNDARY(start, block_size) \
3837
(block_size - (start & (block_size - 1)))
3938

@@ -83,6 +82,11 @@ static int disk_flash_access_init(struct disk_info *disk)
8382
return -ENOMEM;
8483
}
8584

85+
if (ctx->cache_valid && ctx->cache_dirty) {
86+
LOG_ERR("Discarding %s dirty cache", disk->name);
87+
ctx->cache_valid = false;
88+
}
89+
8690
return 0;
8791
}
8892

@@ -109,8 +113,8 @@ static int disk_flash_access_read(struct disk_info *disk, uint8_t *buff,
109113
struct flashdisk_data *ctx;
110114
off_t fl_addr;
111115
uint32_t remaining;
116+
uint32_t offset;
112117
uint32_t len;
113-
uint32_t num_read;
114118

115119
ctx = CONTAINER_OF(disk, struct flashdisk_data, info);
116120

@@ -120,118 +124,125 @@ static int disk_flash_access_read(struct disk_info *disk, uint8_t *buff,
120124

121125
fl_addr = ctx->offset + start_sector * ctx->sector_size;
122126
remaining = (sector_count * ctx->sector_size);
123-
len = ctx->page_size;
124-
num_read = GET_NUM_BLOCK(remaining, ctx->page_size);
125127

126-
for (uint32_t i = 0; i < num_read; i++) {
127-
if (remaining < ctx->page_size) {
128+
/* Operate on page addresses to easily check for cached data */
129+
offset = fl_addr & (ctx->page_size - 1);
130+
fl_addr = ROUND_DOWN(fl_addr, ctx->page_size);
131+
132+
/* Read up to page boundary on first iteration */
133+
len = ctx->page_size - offset;
134+
while (remaining) {
135+
if (remaining < len) {
128136
len = remaining;
129137
}
130138

131-
if (flash_read(disk->dev, fl_addr, buff, len) < 0) {
139+
if (ctx->cache_valid && ctx->cached_addr == fl_addr) {
140+
memcpy(buff, &ctx->cache[offset], len);
141+
} else if (flash_read(disk->dev, fl_addr + offset, buff, len) < 0) {
132142
return -EIO;
133143
}
134144

135-
fl_addr += len;
136-
buff += len;
145+
fl_addr += ctx->page_size;
137146
remaining -= len;
147+
buff += len;
148+
149+
/* Try to read whole page on next iteration */
150+
len = ctx->page_size;
151+
offset = 0;
138152
}
139153

140154
return 0;
141155
}
142156

143-
/* This performs read-copy into an output buffer */
144-
static int read_copy_flash_block(struct disk_info *disk,
145-
off_t start_addr,
146-
uint32_t size,
147-
const void *src_buff,
148-
uint8_t *dest_buff)
157+
static int flashdisk_cache_commit(struct flashdisk_data *ctx)
149158
{
150-
struct flashdisk_data *ctx;
151-
off_t fl_addr;
152-
uint32_t offset;
153-
uint32_t remaining;
154-
155-
ctx = CONTAINER_OF(disk, struct flashdisk_data, info);
156-
157-
/* adjust offset if starting address is not erase-aligned address */
158-
offset = start_addr & (ctx->page_size - 1);
159-
160-
/* align starting address to page boundary */
161-
fl_addr = ROUND_DOWN(start_addr, ctx->page_size);
162-
163-
if (offset > 0) {
164-
/* read page contents prior to user data */
165-
if (flash_read(disk->dev, fl_addr, dest_buff, offset) < 0) {
166-
return -EIO;
167-
}
159+
if (!ctx->cache_valid || !ctx->cache_dirty) {
160+
/* Either no cached data or cache matches flash data */
161+
return 0;
168162
}
169163

170-
/* write user data in page buffer */
171-
memcpy(dest_buff + offset, src_buff, size);
164+
if (flash_erase(ctx->info.dev, ctx->cached_addr, ctx->page_size) < 0) {
165+
return -EIO;
166+
}
172167

173-
remaining = ctx->page_size - (offset + size);
174-
if (remaining) {
175-
/* read page contents after user data */
176-
if (flash_read(disk->dev, start_addr + size,
177-
&dest_buff[offset + size], remaining) < 0) {
178-
return -EIO;
179-
}
168+
/* write data to flash */
169+
if (flash_write(ctx->info.dev, ctx->cached_addr, ctx->cache, ctx->page_size) < 0) {
170+
return -EIO;
180171
}
181172

173+
ctx->cache_dirty = false;
182174
return 0;
183175
}
184176

185-
static int overwrite_flash_block(struct disk_info *disk, off_t fl_addr,
186-
const void *buff)
177+
static int flashdisk_cache_load(struct flashdisk_data *ctx, off_t fl_addr)
187178
{
188-
struct flashdisk_data *ctx;
179+
int rc;
189180

190-
ctx = CONTAINER_OF(disk, struct flashdisk_data, info);
181+
__ASSERT_NO_MSG((fl_addr & (ctx->page_size - 1)) == 0);
191182

192-
if (flash_erase(disk->dev, fl_addr, ctx->page_size) < 0) {
193-
return -EIO;
183+
if (ctx->cache_valid) {
184+
if (ctx->cached_addr == fl_addr) {
185+
/* Page is already cached */
186+
return 0;
187+
}
188+
/* Different page is in cache, commit it first */
189+
rc = flashdisk_cache_commit(ctx);
190+
if (rc < 0) {
191+
/* Failed to commit dirty page, abort */
192+
return rc;
193+
}
194194
}
195195

196-
/* write data to flash */
197-
if (flash_write(disk->dev, fl_addr, buff, ctx->page_size) < 0) {
198-
return -EIO;
196+
/* Load page into cache */
197+
ctx->cache_valid = false;
198+
ctx->cache_dirty = false;
199+
ctx->cached_addr = fl_addr;
200+
rc = flash_read(ctx->info.dev, fl_addr, ctx->cache, ctx->page_size);
201+
if (rc == 0) {
202+
/* Successfully loaded into cache, mark as valid */
203+
ctx->cache_valid = true;
204+
return 0;
199205
}
200206

201-
return 0;
207+
return -EIO;
202208
}
203209

204210
/* input size is either less or equal to a block size (ctx->page_size)
205211
* and write data never spans across adjacent blocks.
206212
*/
207-
static int update_flash_block(struct disk_info *disk, off_t start_addr,
208-
uint32_t size, const void *buff)
213+
static int flashdisk_cache_write(struct flashdisk_data *ctx, off_t start_addr,
214+
uint32_t size, const void *buff)
209215
{
210-
struct flashdisk_data *ctx;
211-
off_t fl_addr;
212216
int rc;
217+
off_t fl_addr;
218+
uint32_t offset;
213219

214-
ctx = CONTAINER_OF(disk, struct flashdisk_data, info);
220+
/* adjust offset if starting address is not erase-aligned address */
221+
offset = start_addr & (ctx->page_size - 1);
215222

216-
/* always align starting address for flash write operation */
223+
/* always align starting address for flash cache operations */
217224
fl_addr = ROUND_DOWN(start_addr, ctx->page_size);
218225

219226
/* when writing full page the address must be page aligned
220227
* when writing partial page user data must be within a single page
221228
*/
222229
__ASSERT_NO_MSG(fl_addr + ctx->page_size >= start_addr + size);
223230

224-
if (size == ctx->page_size) {
225-
rc = overwrite_flash_block(disk, fl_addr, buff);
226-
} else {
227-
/* partial block, perform read-copy with user data */
228-
rc = read_copy_flash_block(disk, start_addr, size, buff, ctx->cache);
229-
if (rc == 0) {
230-
rc = overwrite_flash_block(disk, fl_addr, ctx->cache);
231-
}
231+
rc = flashdisk_cache_load(ctx, fl_addr);
232+
if (rc < 0) {
233+
return rc;
232234
}
233235

234-
return rc == 0 ? 0 : -EIO;
236+
/* Do not mark cache as dirty if data to be written matches cache.
237+
* If cache is already dirty, copy data to cache without compare.
238+
*/
239+
if (ctx->cache_dirty || memcmp(&ctx->cache[offset], buff, size)) {
240+
/* Update cache and mark it as dirty */
241+
memcpy(&ctx->cache[offset], buff, size);
242+
ctx->cache_dirty = true;
243+
}
244+
245+
return 0;
235246
}
236247

237248
static int disk_flash_access_write(struct disk_info *disk, const uint8_t *buff,
@@ -261,7 +272,7 @@ static int disk_flash_access_write(struct disk_info *disk, const uint8_t *buff,
261272
block_bnd = block_bnd & ~(ctx->page_size - 1);
262273
if ((fl_addr + remaining) <= block_bnd) {
263274
/* not over block boundary (a partial block also) */
264-
if (update_flash_block(disk, fl_addr, remaining, buff) < 0) {
275+
if (flashdisk_cache_write(ctx, fl_addr, remaining, buff) < 0) {
265276
return -EIO;
266277
}
267278
return 0;
@@ -271,7 +282,7 @@ static int disk_flash_access_write(struct disk_info *disk, const uint8_t *buff,
271282
size = GET_SIZE_TO_BOUNDARY(fl_addr, ctx->page_size);
272283

273284
/* write first partial block */
274-
if (update_flash_block(disk, fl_addr, size, buff) < 0) {
285+
if (flashdisk_cache_write(ctx, fl_addr, size, buff) < 0) {
275286
return -EIO;
276287
}
277288

@@ -286,7 +297,7 @@ static int disk_flash_access_write(struct disk_info *disk, const uint8_t *buff,
286297
break;
287298
}
288299

289-
if (update_flash_block(disk, fl_addr, ctx->page_size, buff) < 0) {
300+
if (flashdisk_cache_write(ctx, fl_addr, ctx->page_size, buff) < 0) {
290301
return -EIO;
291302
}
292303

@@ -297,7 +308,7 @@ static int disk_flash_access_write(struct disk_info *disk, const uint8_t *buff,
297308

298309
/* remaining partial block */
299310
if (remaining) {
300-
if (update_flash_block(disk, fl_addr, remaining, buff) < 0) {
311+
if (flashdisk_cache_write(ctx, fl_addr, remaining, buff) < 0) {
301312
return -EIO;
302313
}
303314
}
@@ -313,7 +324,7 @@ static int disk_flash_access_ioctl(struct disk_info *disk, uint8_t cmd, void *bu
313324

314325
switch (cmd) {
315326
case DISK_IOCTL_CTRL_SYNC:
316-
return 0;
327+
return flashdisk_cache_commit(ctx);
317328
case DISK_IOCTL_GET_SECTOR_COUNT:
318329
*(uint32_t *)buff = ctx->size / ctx->sector_size;
319330
return 0;

0 commit comments

Comments
 (0)