64 lines
9.3 KiB
C
64 lines
9.3 KiB
C
#include "flash_storage.h"
|
||
#include "flash_storage_config.h"
|
||
#include <stddef.h>
|
||
#include <string.h>
|
||
|
||
#define NO_ADDRESS 0U
|
||
#define V2_PAYLOAD 512U
|
||
#define V1_PAYLOAD 256U
|
||
|
||
typedef struct { uint32_t magic;uint16_t version,header_size;uint32_t sequence,length,crc32,reserved;uint8_t payload[V2_PAYLOAD];uint32_t pad,commit; } record_v2_t;
|
||
typedef struct { uint32_t magic;uint16_t version,header_size;uint32_t sequence,length,crc32,reserved;uint8_t payload[V1_PAYLOAD];uint32_t pad,commit; } record_v1_t;
|
||
typedef char v2_size_check[(sizeof(record_v2_t)==FLASH_STORAGE_RECORD_SIZE)?1:-1];
|
||
typedef char v1_size_check[(sizeof(record_v1_t)==FLASH_STORAGE_V1_RECORD_SIZE)?1:-1];
|
||
|
||
static uint32_t crc_update(uint32_t crc,const uint8_t *p,size_t n)
|
||
{ size_t i;uint8_t bit;for(i=0;i<n;++i){crc^=p[i];for(bit=0;bit<8U;++bit)crc=(crc>>1U)^((0U-(crc&1U))&0xEDB88320UL);}return crc; }
|
||
static uint32_t fields_crc(uint16_t v,uint32_t seq,uint32_t len,const uint8_t *p)
|
||
{ uint32_t c=0xFFFFFFFFUL;c=crc_update(c,(uint8_t*)&v,sizeof(v));c=crc_update(c,(uint8_t*)&seq,sizeof(seq));c=crc_update(c,(uint8_t*)&len,sizeof(len));c=crc_update(c,p,len);return c^0xFFFFFFFFUL; }
|
||
static int newer(uint32_t a,uint32_t b){return (int32_t)(a-b)>0;}
|
||
static uint32_t count_v2(const flash_storage_t *s){return ((s->layout.bank_size*(100U-s->layout.reserve_percent))/100U)/FLASH_STORAGE_RECORD_SIZE;}
|
||
static int read_at(flash_storage_t*s,uint32_t a,void*p,size_t n){return s->ops->read(s->platform_context,a,p,n)==FLASH_STORAGE_OK;}
|
||
static int erased(flash_storage_t*s,uint32_t a,size_t n)
|
||
{ uint32_t w;size_t i;for(i=0;i<n;i+=4U){if(!read_at(s,a+(uint32_t)i,&w,4U)||w!=0xFFFFFFFFUL)return 0;}return 1; }
|
||
static int valid_v2(const record_v2_t*r){return r->commit==FLASH_STORAGE_COMMIT&&r->magic==FLASH_STORAGE_MAGIC&&r->version==2U&&r->header_size==offsetof(record_v2_t,payload)&&r->length&&r->length<=V2_PAYLOAD&&r->crc32==fields_crc(r->version,r->sequence,r->length,r->payload);}
|
||
static int valid_v1(const record_v1_t*r){return r->commit==FLASH_STORAGE_COMMIT&&r->magic==FLASH_STORAGE_MAGIC&&r->version==1U&&r->header_size==offsetof(record_v1_t,payload)&&r->length&&r->length<=V1_PAYLOAD&&r->crc32==fields_crc(r->version,r->sequence,r->length,r->payload);}
|
||
|
||
static flash_storage_status_t scan(flash_storage_t*s)
|
||
{ uint32_t b,i,a,active=0U,n2=count_v2(s),n1=(s->layout.bank_size*90U/100U)/FLASH_STORAGE_V1_RECORD_SIZE;int have=0;record_v2_t *r2=(record_v2_t *)s->workspace;record_v1_t *r1=(record_v1_t *)s->workspace;
|
||
s->latest_address=NO_ADDRESS;s->latest_sequence=0;s->latest_version=0;s->next_address=NO_ADDRESS;s->valid_records=0;s->programmed_slots=0;
|
||
for(b=0;b<2U;++b){for(i=0;i<n2;++i){a=s->layout.bank_address[b]+i*FLASH_STORAGE_RECORD_SIZE;if(!erased(s,a,FLASH_STORAGE_RECORD_SIZE))++s->programmed_slots;if(read_at(s,a,r2,sizeof(*r2))&&valid_v2(r2)){++s->valid_records;if(!have||newer(r2->sequence,s->latest_sequence)){have=1;s->latest_address=a;s->latest_sequence=r2->sequence;s->latest_version=2U;active=b;}}}
|
||
/* Старую сетку сканируем отдельно: CRC/version исключают ложное совпадение с v2. */
|
||
for(i=0;i<n1;++i){a=s->layout.bank_address[b]+i*FLASH_STORAGE_V1_RECORD_SIZE;if(read_at(s,a,r1,sizeof(*r1))&&valid_v1(r1)){++s->valid_records;if(!have||newer(r1->sequence,s->latest_sequence)){have=1;s->latest_address=a;s->latest_sequence=r1->sequence;s->latest_version=1U;active=b;}}}}
|
||
if(have){for(i=0;i<n2;++i){a=s->layout.bank_address[active]+i*FLASH_STORAGE_RECORD_SIZE;if(a>s->latest_address&&erased(s,a,FLASH_STORAGE_RECORD_SIZE)){s->next_address=a;break;}}}
|
||
else for(b=0;b<2U&&!s->next_address;++b)for(i=0;i<n2;++i){a=s->layout.bank_address[b]+i*FLASH_STORAGE_RECORD_SIZE;if(erased(s,a,FLASH_STORAGE_RECORD_SIZE)){s->next_address=a;break;}}
|
||
return FLASH_STORAGE_OK; }
|
||
|
||
static flash_storage_status_t validate_layout(const flash_storage_ops_t*o,const flash_storage_layout_t*l)
|
||
{ if(!o||!l||!o->read||!o->program||!o->erase||!l->bank_address[0]||!l->bank_address[1]||l->bank_address[0]==l->bank_address[1]||l->bank_size<FLASH_STORAGE_RECORD_SIZE||l->reserve_percent<10U||l->reserve_percent>=100U||!l->program_alignment||!l->erase_alignment||FLASH_STORAGE_RECORD_SIZE%l->program_alignment||l->bank_address[0]%l->erase_alignment||l->bank_address[1]%l->erase_alignment||l->bank_size%l->erase_alignment)return FLASH_STORAGE_E_LAYOUT;return FLASH_STORAGE_OK; }
|
||
flash_storage_status_t flash_storage_init(flash_storage_t*s,const flash_storage_ops_t*o,void*c,const flash_storage_layout_t*l)
|
||
{ flash_storage_status_t st;if(!s)return FLASH_STORAGE_E_ARGUMENT;st=validate_layout(o,l);if(st)return st;memset(s,0,sizeof(*s));s->ops=o;s->platform_context=c;s->layout=*l;st=scan(s);if(!st)s->initialized=1U;return st; }
|
||
|
||
flash_storage_status_t flash_storage_read_latest(flash_storage_t*s,void*d,size_t cap,size_t*len,uint32_t*seq)
|
||
{ record_v2_t *r;if(!s||!s->initialized||!d)return FLASH_STORAGE_E_ARGUMENT;r=(record_v2_t *)s->workspace;if(!s->latest_address)return FLASH_STORAGE_E_NOT_FOUND;if(!read_at(s,s->latest_address,r,sizeof(*r)))return FLASH_STORAGE_E_IO;if(s->latest_version==2U){if(!valid_v2(r))return FLASH_STORAGE_E_CORRUPT;}else{record_v1_t*r1=(record_v1_t*)r;if(!valid_v1(r1))return FLASH_STORAGE_E_CORRUPT;}if(cap<r->length)return FLASH_STORAGE_E_BOUNDS;memcpy(d,r->payload,r->length);if(len)*len=r->length;if(seq)*seq=r->sequence;return FLASH_STORAGE_OK; }
|
||
|
||
flash_storage_status_t flash_storage_write(flash_storage_t*s,const void*d,size_t len)
|
||
{ record_v2_t *r;uint32_t now=0,target,bank;flash_storage_status_t st;if(!s||!s->initialized||!d||!len||len>V2_PAYLOAD)return FLASH_STORAGE_E_ARGUMENT;r=(record_v2_t *)s->workspace;if(s->busy)return FLASH_STORAGE_E_BUSY;if(s->latest_address&&read_at(s,s->latest_address,r,sizeof(*r))&&r->length==len&&!memcmp(r->payload,d,len))return FLASH_STORAGE_OK;if(s->ops->tick_ms)now=s->ops->tick_ms(s->platform_context);if(s->wrote_this_boot&&s->ops->tick_ms&&(uint32_t)(now-s->last_write_tick)<s->layout.minimum_write_ms)return FLASH_STORAGE_E_BUSY;s->busy=1U;target=s->next_address;
|
||
if(!target){bank=(s->latest_address>=s->layout.bank_address[1])?0U:1U;st=s->ops->erase(s->platform_context,s->layout.bank_address[bank],s->layout.bank_size);if(st){s->busy=0;return FLASH_STORAGE_E_IO;}target=s->layout.bank_address[bank];}
|
||
memset(r,0xFF,sizeof(*r));r->magic=FLASH_STORAGE_MAGIC;r->version=2U;r->header_size=(uint16_t)offsetof(record_v2_t,payload);r->sequence=s->latest_sequence+1U;r->length=(uint32_t)len;memcpy(r->payload,d,len);r->crc32=fields_crc(r->version,r->sequence,r->length,r->payload);r->commit=FLASH_STORAGE_COMMIT;
|
||
/* commit записывается последним: оборванная запись никогда не вытеснит старую. */
|
||
st=s->ops->program(s->platform_context,target,r,offsetof(record_v2_t,commit));if(!st)st=s->ops->program(s->platform_context,target+offsetof(record_v2_t,commit),&r->commit,4U);s->busy=0;scan(s);if(st)return FLASH_STORAGE_E_IO;if(s->latest_address!=target)return FLASH_STORAGE_E_CORRUPT;s->wrote_this_boot=1U;s->last_write_tick=now;return FLASH_STORAGE_OK; }
|
||
flash_storage_status_t flash_storage_format(flash_storage_t*s)
|
||
{ flash_storage_status_t st;if(!s||!s->initialized)return FLASH_STORAGE_E_ARGUMENT;if(s->busy)return FLASH_STORAGE_E_BUSY;s->busy=1;st=s->ops->erase(s->platform_context,s->layout.bank_address[0],s->layout.bank_size);if(!st)st=s->ops->erase(s->platform_context,s->layout.bank_address[1],s->layout.bank_size);s->busy=0;scan(s);s->wrote_this_boot=0;return st?FLASH_STORAGE_E_IO:FLASH_STORAGE_OK; }
|
||
flash_storage_status_t flash_storage_get_info(flash_storage_t*s,flash_storage_info_t*i)
|
||
{ uint32_t usable;if(!s||!s->initialized||!i)return FLASH_STORAGE_E_ARGUMENT;usable=count_v2(s)*FLASH_STORAGE_RECORD_SIZE;i->sequence=s->latest_sequence;i->valid_records=s->valid_records;i->programmed_slots=s->programmed_slots;i->allocated_bytes=2U*s->layout.bank_size;i->reserved_bytes=2U*(s->layout.bank_size-usable);return FLASH_STORAGE_OK; }
|
||
|
||
flash_storage_status_t eeprom_store_init(eeprom_store_t*e,const flash_storage_ops_t*o,void*c,const flash_storage_layout_t*l)
|
||
{ size_t n=0;flash_storage_status_t s;if(!e)return FLASH_STORAGE_E_ARGUMENT;memset(e,0,sizeof(*e));memset(e->image,0xFF,sizeof(e->image));s=flash_storage_init(&e->journal,o,c,l);if(s)return s;s=flash_storage_read_latest(&e->journal,e->image,sizeof(e->image),&n,NULL);if(s==FLASH_STORAGE_E_NOT_FOUND){e->initialized=1;return FLASH_STORAGE_OK;}if(s||!(n==128U||n==256U||n==512U)){memset(e->image,0xFF,sizeof(e->image));e->initialized=1;return FLASH_STORAGE_OK;}e->initialized=1;return FLASH_STORAGE_OK; }
|
||
flash_storage_status_t eeprom_store_read(eeprom_store_t*e,size_t off,void*d,size_t n)
|
||
{ if(!e||!e->initialized||!d||!n||off>sizeof(e->image)||n>sizeof(e->image)-off)return FLASH_STORAGE_E_BOUNDS;memcpy(d,e->image+off,n);return FLASH_STORAGE_OK; }
|
||
flash_storage_status_t eeprom_store_write(eeprom_store_t*e,size_t off,const void*d,size_t n)
|
||
{ flash_storage_status_t s;if(!e||!e->initialized||!d||!n||off>512U||n>512U-off)return FLASH_STORAGE_E_BOUNDS;memcpy(e->next_image,e->image,512U);memcpy(e->next_image+off,d,n);s=flash_storage_write(&e->journal,e->next_image,512U);if(!s)memcpy(e->image,e->next_image,512U);return s; }
|
||
flash_storage_status_t eeprom_store_format(eeprom_store_t*e)
|
||
{ flash_storage_status_t s;if(!e)return FLASH_STORAGE_E_ARGUMENT;s=flash_storage_format(&e->journal);if(!s){memset(e->image,0xFF,512U);e->initialized=1;}return s; }
|