16 changed files with 11887 additions and 0 deletions
@ -0,0 +1,336 @@ |
|||
#include "w25qxx.h" |
|||
#ifdef USE_W25QXX |
|||
#include "spi.h" |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
|
|||
//ALIENTEK STM32F429开发板
|
|||
//W25QXX驱动代码
|
|||
//正点原子@ALIENTEK
|
|||
//技术论坛:www.openedv.com
|
|||
//创建日期:2016/1/16
|
|||
//版本:V1.0
|
|||
//版权所有,盗版必究。
|
|||
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
|
|||
//All rights reserved
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
uint16_t W25QXX_TYPE=W25Q80; //默认是W25Q80
|
|||
|
|||
//4Kbytes为一个Sector
|
|||
//16个扇区为1个Block
|
|||
//W25Q80
|
|||
//容量为1M字节,共有16个Block,256个Sector
|
|||
|
|||
uint8_t SPI2_ReadWriteByte(uint8_t TxData) |
|||
{ |
|||
uint8_t Rxdata; |
|||
HAL_SPI_TransmitReceive(&W25QXXSPI,&TxData,&Rxdata,1, 1000); |
|||
return Rxdata; //返回收到的数据
|
|||
} |
|||
|
|||
//初始化SPI FLASH的IO口
|
|||
void W25QXX_Init(void) |
|||
{ |
|||
uint8_t temp; |
|||
|
|||
SPI2_ReadWriteByte(0Xff); //启动传输
|
|||
|
|||
W25QXX_CS(1); //SPI FLASH不选中
|
|||
|
|||
W25QXX_TYPE=W25QXX_ReadID(); //读取FLASH ID
|
|||
|
|||
if(W25QXX_TYPE==W25Q256) |
|||
{ |
|||
temp=W25QXX_ReadSR(3); //读取状态寄存器3,判断地址模式
|
|||
if((temp&0X01)==0) //如果不是4字节地址模式,则进入4字节地址模式
|
|||
{ |
|||
W25QXX_CS(0); //选中
|
|||
SPI2_ReadWriteByte(W25X_Enable4ByteAddr);//发送进入4字节地址模式指令
|
|||
W25QXX_CS(1); //取消片选
|
|||
} |
|||
} |
|||
} |
|||
|
|||
//读取W25QXX的状态寄存器,W25QXX一共有3个状态寄存器
|
|||
//状态寄存器1:
|
|||
//BIT7 6 5 4 3 2 1 0
|
|||
//SPR RV TB BP2 BP1 BP0 WEL BUSY
|
|||
//SPR:默认0,状态寄存器保护位,配合WP使用
|
|||
//TB,BP2,BP1,BP0:FLASH区域写保护设置
|
|||
//WEL:写使能锁定
|
|||
//BUSY:忙标记位(1,忙;0,空闲)
|
|||
//默认:0x00
|
|||
//状态寄存器2:
|
|||
//BIT7 6 5 4 3 2 1 0
|
|||
//SUS CMP LB3 LB2 LB1 (R) QE SRP1
|
|||
//状态寄存器3:
|
|||
//BIT7 6 5 4 3 2 1 0
|
|||
//HOLD/RST DRV1 DRV0 (R) (R) WPS ADP ADS
|
|||
//regno:状态寄存器号,范:1~3
|
|||
//返回值:状态寄存器值
|
|||
uint8_t W25QXX_ReadSR(uint8_t regno) |
|||
{ |
|||
uint8_t byte=0,command=0; |
|||
switch(regno) |
|||
{ |
|||
case 1: |
|||
command=W25X_ReadStatusReg1; //读状态寄存器1指令
|
|||
break; |
|||
case 2: |
|||
command=W25X_ReadStatusReg2; //读状态寄存器2指令
|
|||
break; |
|||
case 3: |
|||
command=W25X_ReadStatusReg3; //读状态寄存器3指令
|
|||
break; |
|||
default: |
|||
command=W25X_ReadStatusReg1; |
|||
break; |
|||
} |
|||
W25QXX_CS(0); //使能器件
|
|||
SPI2_ReadWriteByte(command); //发送读取状态寄存器命令
|
|||
byte=SPI2_ReadWriteByte(0Xff); //读取一个字节
|
|||
W25QXX_CS(1); //取消片选
|
|||
return byte; |
|||
} |
|||
//写W25QXX状态寄存器
|
|||
void W25QXX_Write_SR(uint8_t regno,uint8_t sr) |
|||
{ |
|||
uint8_t command=0; |
|||
switch(regno) |
|||
{ |
|||
case 1: |
|||
command=W25X_WriteStatusReg1; //写状态寄存器1指令
|
|||
break; |
|||
case 2: |
|||
command=W25X_WriteStatusReg2; //写状态寄存器2指令
|
|||
break; |
|||
case 3: |
|||
command=W25X_WriteStatusReg3; //写状态寄存器3指令
|
|||
break; |
|||
default: |
|||
command=W25X_WriteStatusReg1; |
|||
break; |
|||
} |
|||
W25QXX_CS(0); //使能器件
|
|||
SPI2_ReadWriteByte(command); //发送写取状态寄存器命令
|
|||
SPI2_ReadWriteByte(sr); //写入一个字节
|
|||
W25QXX_CS(1); //取消片选
|
|||
} |
|||
//W25QXX写使能
|
|||
//将WEL置位
|
|||
void W25QXX_Write_Enable(void) |
|||
{ |
|||
W25QXX_CS(0); //使能器件
|
|||
SPI2_ReadWriteByte(W25X_WriteEnable); //发送写使能
|
|||
W25QXX_CS(1); //取消片选
|
|||
} |
|||
//W25QXX写禁止
|
|||
//将WEL清零
|
|||
void W25QXX_Write_Disable(void) |
|||
{ |
|||
W25QXX_CS(0); //使能器件
|
|||
SPI2_ReadWriteByte(W25X_WriteDisable); //发送写禁止指令
|
|||
W25QXX_CS(1); //取消片选
|
|||
} |
|||
|
|||
//读取芯片ID
|
|||
//返回值如下:
|
|||
//0XEF13,表示芯片型号为W25Q80
|
|||
//0XEF14,表示芯片型号为W25Q16
|
|||
//0XEF15,表示芯片型号为W25Q32
|
|||
//0XEF16,表示芯片型号为W25Q64
|
|||
//0XEF17,表示芯片型号为W25Q128
|
|||
//0XEF18,表示芯片型号为W25Q256
|
|||
uint16_t W25QXX_ReadID(void) |
|||
{ |
|||
uint16_t Temp = 0; |
|||
W25QXX_CS(0); |
|||
SPI2_ReadWriteByte(0x90);//发送读取ID命令
|
|||
SPI2_ReadWriteByte(0x00); |
|||
SPI2_ReadWriteByte(0x00); |
|||
SPI2_ReadWriteByte(0x00); |
|||
Temp|=SPI2_ReadWriteByte(0xFF)<<8; |
|||
Temp|=SPI2_ReadWriteByte(0xFF); |
|||
W25QXX_CS(1); |
|||
return Temp; |
|||
} |
|||
//读取SPI FLASH
|
|||
//在指定地址开始读取指定长度的数据
|
|||
//pBuffer:数据存储区
|
|||
//ReadAddr:开始读取的地址(24bit)
|
|||
//NumByteToRead:要读取的字节数(最大65535)
|
|||
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead) |
|||
{ |
|||
uint16_t i; |
|||
W25QXX_CS(0); //使能器件
|
|||
SPI2_ReadWriteByte(W25X_ReadData); //发送读取命令
|
|||
if(W25QXX_TYPE==W25Q256) //如果是W25Q256的话地址为4字节的,要发送最高8位
|
|||
{ |
|||
SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>24)); |
|||
} |
|||
SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>16)); //发送24bit地址
|
|||
SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>8)); |
|||
SPI2_ReadWriteByte((uint8_t)ReadAddr); |
|||
for(i=0;i<NumByteToRead;i++) |
|||
{ |
|||
pBuffer[i]=SPI2_ReadWriteByte(0XFF); //循环读数
|
|||
} |
|||
W25QXX_CS(1); |
|||
} |
|||
//SPI在一页(0~65535)内写入少于256个字节的数据
|
|||
//在指定地址开始写入最大256字节的数据
|
|||
//pBuffer:数据存储区
|
|||
//WriteAddr:开始写入的地址(24bit)
|
|||
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
|
|||
void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite) |
|||
{ |
|||
uint16_t i; |
|||
W25QXX_Write_Enable(); //SET WEL
|
|||
W25QXX_CS(0); //使能器件
|
|||
SPI2_ReadWriteByte(W25X_PageProgram); //发送写页命令
|
|||
if(W25QXX_TYPE==W25Q256) //如果是W25Q256的话地址为4字节的,要发送最高8位
|
|||
{ |
|||
SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>24)); |
|||
} |
|||
SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>16)); //发送24bit地址
|
|||
SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>8)); |
|||
SPI2_ReadWriteByte((uint8_t)WriteAddr); |
|||
for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);//循环写数
|
|||
W25QXX_CS(1); //取消片选
|
|||
W25QXX_Wait_Busy(); //等待写入结束
|
|||
} |
|||
//无检验写SPI FLASH
|
|||
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
|
|||
//具有自动换页功能
|
|||
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
|
|||
//pBuffer:数据存储区
|
|||
//WriteAddr:开始写入的地址(24bit)
|
|||
//NumByteToWrite:要写入的字节数(最大65535)
|
|||
//CHECK OK
|
|||
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite) |
|||
{ |
|||
uint16_t pageremain; |
|||
pageremain=256-WriteAddr%256; //单页剩余的字节数
|
|||
if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节
|
|||
while(1) |
|||
{ |
|||
W25QXX_Write_Page(pBuffer,WriteAddr,pageremain); |
|||
if(NumByteToWrite==pageremain)break;//写入结束了
|
|||
else //NumByteToWrite>pageremain
|
|||
{ |
|||
pBuffer+=pageremain; |
|||
WriteAddr+=pageremain; |
|||
|
|||
NumByteToWrite-=pageremain; //减去已经写入了的字节数
|
|||
if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节
|
|||
else pageremain=NumByteToWrite; //不够256个字节了
|
|||
} |
|||
}; |
|||
} |
|||
//写SPI FLASH
|
|||
//在指定地址开始写入指定长度的数据
|
|||
//该函数带擦除操作!
|
|||
//pBuffer:数据存储区
|
|||
//WriteAddr:开始写入的地址(24bit)
|
|||
//NumByteToWrite:要写入的字节数(最大65535)
|
|||
uint8_t W25QXX_BUFFER[4096]; |
|||
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite) |
|||
{ |
|||
uint32_t secpos; |
|||
uint16_t secoff; |
|||
uint16_t secremain; |
|||
uint16_t i; |
|||
uint8_t * W25QXX_BUF; |
|||
W25QXX_BUF=W25QXX_BUFFER; |
|||
secpos=WriteAddr/4096;//扇区地址
|
|||
secoff=WriteAddr%4096;//在扇区内的偏移
|
|||
secremain=4096-secoff;//扇区剩余空间大小
|
|||
//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用
|
|||
if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节
|
|||
while(1) |
|||
{ |
|||
W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容
|
|||
for(i=0;i<secremain;i++)//校验数据
|
|||
{ |
|||
if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除
|
|||
} |
|||
if(i<secremain)//需要擦除
|
|||
{ |
|||
W25QXX_Erase_Sector(secpos);//擦除这个扇区
|
|||
for(i=0;i<secremain;i++) //复制
|
|||
{ |
|||
W25QXX_BUF[i+secoff]=pBuffer[i]; |
|||
} |
|||
W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区
|
|||
|
|||
}else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间.
|
|||
if(NumByteToWrite==secremain)break;//写入结束了
|
|||
else//写入未结束
|
|||
{ |
|||
secpos++;//扇区地址增1
|
|||
secoff=0;//偏移位置为0
|
|||
|
|||
pBuffer+=secremain; //指针偏移
|
|||
WriteAddr+=secremain;//写地址偏移
|
|||
NumByteToWrite-=secremain; //字节数递减
|
|||
if(NumByteToWrite>4096)secremain=4096; //下一个扇区还是写不完
|
|||
else secremain=NumByteToWrite; //下一个扇区可以写完了
|
|||
} |
|||
}; |
|||
} |
|||
//擦除整个芯片
|
|||
//等待时间超长...
|
|||
void W25QXX_Erase_Chip(void) |
|||
{ |
|||
W25QXX_Write_Enable(); //SET WEL
|
|||
W25QXX_Wait_Busy(); |
|||
W25QXX_CS(0); //使能器件
|
|||
SPI2_ReadWriteByte(W25X_ChipErase); //发送片擦除命令
|
|||
W25QXX_CS(1); //取消片选
|
|||
W25QXX_Wait_Busy(); //等待芯片擦除结束
|
|||
} |
|||
//擦除一个扇区
|
|||
//Dst_Addr:扇区地址 根据实际容量设置
|
|||
//擦除一个扇区的最少时间:150ms
|
|||
void W25QXX_Erase_Sector(uint32_t Dst_Addr) |
|||
{ |
|||
//监视falsh擦除情况,测试用
|
|||
//printf("fe:%x\r\n",Dst_Addr);
|
|||
Dst_Addr*=4096; |
|||
W25QXX_Write_Enable(); //SET WEL
|
|||
W25QXX_Wait_Busy(); |
|||
W25QXX_CS(0); //使能器件
|
|||
SPI2_ReadWriteByte(W25X_SectorErase); //发送扇区擦除指令
|
|||
if(W25QXX_TYPE==W25Q256) //如果是W25Q256的话地址为4字节的,要发送最高8位
|
|||
{ |
|||
SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>24)); |
|||
} |
|||
SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>16)); //发送24bit地址
|
|||
SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>8)); |
|||
SPI2_ReadWriteByte((uint8_t)Dst_Addr); |
|||
W25QXX_CS(1); //取消片选
|
|||
W25QXX_Wait_Busy(); //等待擦除完成
|
|||
} |
|||
//等待空闲
|
|||
void W25QXX_Wait_Busy(void) |
|||
{ |
|||
while((W25QXX_ReadSR(1)&0x01)==0x01); // 等待BUSY位清空
|
|||
} |
|||
//进入掉电模式
|
|||
void W25QXX_PowerDown(void) |
|||
{ |
|||
W25QXX_CS(0); //使能器件
|
|||
SPI2_ReadWriteByte(W25X_PowerDown); //发送掉电命令
|
|||
W25QXX_CS(1); //取消片选
|
|||
HAL_Delay(3); //等待TPD
|
|||
} |
|||
//唤醒
|
|||
void W25QXX_WAKEUP(void) |
|||
{ |
|||
W25QXX_CS(0); //使能器件
|
|||
SPI2_ReadWriteByte(W25X_ReleasePowerDown); // send W25X_PowerDown command 0xAB
|
|||
W25QXX_CS(1); //取消片选
|
|||
HAL_Delay(3); //等待TRES1
|
|||
} |
|||
#endif |
|||
@ -0,0 +1,77 @@ |
|||
#ifndef __W25QXX_H |
|||
#define __W25QXX_H |
|||
#include "common.h" |
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
|
|||
//ALIENTEK STM32F429开发板
|
|||
//W25QXX驱动代码
|
|||
//正点原子@ALIENTEK
|
|||
//技术论坛:www.openedv.com
|
|||
//创建日期:2016/1/16
|
|||
//版本:V1.0
|
|||
//版权所有,盗版必究。
|
|||
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
|
|||
//All rights reserved
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
//W25X系列/Q系列芯片列表
|
|||
//W25Q80 ID 0XEF13
|
|||
//W25Q16 ID 0XEF14
|
|||
//W25Q32 ID 0XEF15
|
|||
//W25Q64 ID 0XEF16
|
|||
//W25Q128 ID 0XEF17
|
|||
//W25Q256 ID 0XEF18
|
|||
#define W25Q80 0XEF13 |
|||
#define W25Q16 0XEF14 |
|||
#define W25Q32 0XEF15 |
|||
#define W25Q64 0XEF16 |
|||
#define W25Q128 0XEF17 |
|||
#define W25Q256 0XEF18 |
|||
|
|||
extern uint16_t W25QXX_TYPE; //定义W25QXX芯片型号
|
|||
|
|||
//W25QXX的片选信号
|
|||
#define W25QXX_CS(n) (n?HAL_GPIO_WritePin(W25QXX_CS_GPIO_Port,W25QXX_CS_Pin,GPIO_PIN_SET):HAL_GPIO_WritePin(W25QXX_CS_GPIO_Port,W25QXX_CS_Pin,GPIO_PIN_RESET)) |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
|||
//指令表
|
|||
#define W25X_WriteEnable 0x06 |
|||
#define W25X_WriteDisable 0x04 |
|||
#define W25X_ReadStatusReg1 0x05 |
|||
#define W25X_ReadStatusReg2 0x35 |
|||
#define W25X_ReadStatusReg3 0x15 |
|||
#define W25X_WriteStatusReg1 0x01 |
|||
#define W25X_WriteStatusReg2 0x31 |
|||
#define W25X_WriteStatusReg3 0x11 |
|||
#define W25X_ReadData 0x03 |
|||
#define W25X_FastReadData 0x0B |
|||
#define W25X_FastReadDual 0x3B |
|||
#define W25X_PageProgram 0x02 |
|||
#define W25X_BlockErase 0xD8 |
|||
#define W25X_SectorErase 0x20 |
|||
#define W25X_ChipErase 0xC7 |
|||
#define W25X_PowerDown 0xB9 |
|||
#define W25X_ReleasePowerDown 0xAB |
|||
#define W25X_DeviceID 0xAB |
|||
#define W25X_ManufactDeviceID 0x90 |
|||
#define W25X_JedecDeviceID 0x9F |
|||
#define W25X_Enable4ByteAddr 0xB7 |
|||
#define W25X_Exit4ByteAddr 0xE9 |
|||
|
|||
void W25QXX_Init(void); |
|||
uint16_t W25QXX_ReadID(void); //读取FLASH ID
|
|||
uint8_t W25QXX_ReadSR(uint8_t regno); //读取状态寄存器
|
|||
void W25QXX_4ByteAddr_Enable(void); //使能4字节地址模式
|
|||
void W25QXX_Write_SR(uint8_t regno,uint8_t sr); //写状态寄存器
|
|||
void W25QXX_Write_Enable(void); //写使能
|
|||
void W25QXX_Write_Disable(void); //写保护
|
|||
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite); |
|||
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead); //读取flash
|
|||
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);//写入flash
|
|||
void W25QXX_Erase_Chip(void); //整片擦除
|
|||
void W25QXX_Erase_Sector(uint32_t Dst_Addr); //扇区擦除
|
|||
void W25QXX_Wait_Busy(void); //等待空闲
|
|||
void W25QXX_PowerDown(void); //进入掉电模式
|
|||
void W25QXX_WAKEUP(void); //唤醒
|
|||
|
|||
#endif |
|||
@ -0,0 +1,16 @@ |
|||
cmake_minimum_required(VERSION 3.10) |
|||
|
|||
get_filename_component(TARGET_NAME "${CMAKE_CURRENT_SOURCE_DIR}" NAME) |
|||
set(COMMON_CMAKE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../CMakeLists.txt") |
|||
|
|||
if(EXISTS ${COMMON_CMAKE_PATH}) |
|||
include(${COMMON_CMAKE_PATH}) |
|||
else() |
|||
message(FATAL_ERROR "Cannot find common build logic at ${COMMON_CMAKE_PATH}") |
|||
endif() |
|||
|
|||
if(TARGET common) |
|||
target_link_libraries(${TARGET_NAME} PUBLIC common) |
|||
else() |
|||
message(FATAL_ERROR "[${TARGET_NAME}] Dependency 'common' not found.") |
|||
endif() |
|||
File diff suppressed because it is too large
@ -0,0 +1,25 @@ |
|||
Copyright (c) 2022, The littlefs authors. |
|||
Copyright (c) 2017, Arm Limited. All rights reserved. |
|||
|
|||
Redistribution and use in source and binary forms, with or without modification, |
|||
are permitted provided that the following conditions are met: |
|||
|
|||
- Redistributions of source code must retain the above copyright notice, this |
|||
list of conditions and the following disclaimer. |
|||
- Redistributions in binary form must reproduce the above copyright notice, this |
|||
list of conditions and the following disclaimer in the documentation and/or |
|||
other materials provided with the distribution. |
|||
- Neither the name of ARM nor the names of its contributors may be used to |
|||
endorse or promote products derived from this software without specific prior |
|||
written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR |
|||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
|||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
@ -0,0 +1,867 @@ |
|||
## littlefs technical specification |
|||
|
|||
This is the technical specification of the little filesystem with on-disk |
|||
version lfs2.1. This document covers the technical details of how the littlefs |
|||
is stored on disk for introspection and tooling. This document assumes you are |
|||
familiar with the design of the littlefs, for more info on how littlefs works |
|||
check out [DESIGN.md](DESIGN.md). |
|||
|
|||
``` |
|||
| | | .---._____ |
|||
.-----. | | |
|||
--|o |---| littlefs | |
|||
--| |---| | |
|||
'-----' '----------' |
|||
| | | |
|||
``` |
|||
|
|||
## Some quick notes |
|||
|
|||
- littlefs is a block-based filesystem. The disk is divided into an array of |
|||
evenly sized blocks that are used as the logical unit of storage. |
|||
|
|||
- Block pointers are stored in 32 bits, with the special value `0xffffffff` |
|||
representing a null block address. |
|||
|
|||
- In addition to the logical block size (which usually matches the erase |
|||
block size), littlefs also uses a program block size and read block size. |
|||
These determine the alignment of block device operations, but don't need |
|||
to be consistent for portability. |
|||
|
|||
- By default, all values in littlefs are stored in little-endian byte order. |
|||
|
|||
## Directories / Metadata pairs |
|||
|
|||
Metadata pairs form the backbone of littlefs and provide a system for |
|||
distributed atomic updates. Even the superblock is stored in a metadata pair. |
|||
|
|||
As their name suggests, a metadata pair is stored in two blocks, with one block |
|||
providing a backup during erase cycles in case power is lost. These two blocks |
|||
are not necessarily sequential and may be anywhere on disk, so a "pointer" to a |
|||
metadata pair is stored as two block pointers. |
|||
|
|||
On top of this, each metadata block behaves as an appendable log, containing a |
|||
variable number of commits. Commits can be appended to the metadata log in |
|||
order to update the metadata without requiring an erase cycles. Note that |
|||
successive commits may supersede the metadata in previous commits. Only the |
|||
most recent metadata should be considered valid. |
|||
|
|||
The high-level layout of a metadata block is fairly simple: |
|||
|
|||
``` |
|||
.---------------------------------------. |
|||
.-| revision count | entries | \ |
|||
| |-------------------+ | | |
|||
| | | | |
|||
| | | +-- 1st commit |
|||
| | | | |
|||
| | +-------------------| | |
|||
| | | CRC | / |
|||
| |-------------------+-------------------| |
|||
| | entries | \ |
|||
| | | | |
|||
| | | +-- 2nd commit |
|||
| | +-------------------+--------------| | |
|||
| | | CRC | padding | / |
|||
| |----+-------------------+--------------| |
|||
| | entries | \ |
|||
| | | | |
|||
| | | +-- 3rd commit |
|||
| | +-------------------+---------| | |
|||
| | | CRC | | / |
|||
| |---------+-------------------+ | |
|||
| | unwritten storage | more commits |
|||
| | | | |
|||
| | | v |
|||
| | | |
|||
| | | |
|||
| '---------------------------------------' |
|||
'---------------------------------------' |
|||
``` |
|||
|
|||
Each metadata block contains a 32-bit revision count followed by a number of |
|||
commits. Each commit contains a variable number of metadata entries followed |
|||
by a 32-bit CRC. |
|||
|
|||
Note also that entries aren't necessarily word-aligned. This allows us to |
|||
store metadata more compactly, however we can only write to addresses that are |
|||
aligned to our program block size. This means each commit may have padding for |
|||
alignment. |
|||
|
|||
Metadata block fields: |
|||
|
|||
1. **Revision count (32-bits)** - Incremented every erase cycle. If both blocks |
|||
contain valid commits, only the block with the most recent revision count |
|||
should be used. Sequence comparison must be used to avoid issues with |
|||
integer overflow. |
|||
|
|||
2. **CRC (32-bits)** - Detects corruption from power-loss or other write |
|||
issues. Uses a CRC-32 with a polynomial of `0x04c11db7` initialized |
|||
with `0xffffffff`. |
|||
|
|||
Entries themselves are stored as a 32-bit tag followed by a variable length |
|||
blob of data. But exactly how these tags are stored is a little bit tricky. |
|||
|
|||
Metadata blocks support both forward and backward iteration. In order to do |
|||
this without duplicating the space for each tag, neighboring entries have their |
|||
tags XORed together, starting with `0xffffffff`. |
|||
|
|||
``` |
|||
Forward iteration Backward iteration |
|||
|
|||
.-------------------. 0xffffffff .-------------------. |
|||
| revision count | | | revision count | |
|||
|-------------------| v |-------------------| |
|||
| tag ~A |---> xor -> tag A | tag ~A |---> xor -> 0xffffffff |
|||
|-------------------| | |-------------------| ^ |
|||
| data A | | | data A | | |
|||
| | | | | | |
|||
| | | | | | |
|||
|-------------------| v |-------------------| | |
|||
| tag AxB |---> xor -> tag B | tag AxB |---> xor -> tag A |
|||
|-------------------| | |-------------------| ^ |
|||
| data B | | | data B | | |
|||
| | | | | | |
|||
| | | | | | |
|||
|-------------------| v |-------------------| | |
|||
| tag BxC |---> xor -> tag C | tag BxC |---> xor -> tag B |
|||
|-------------------| |-------------------| ^ |
|||
| data C | | data C | | |
|||
| | | | tag C |
|||
| | | | |
|||
| | | | |
|||
'-------------------' '-------------------' |
|||
``` |
|||
|
|||
Here's a more complete example of metadata block containing 4 entries: |
|||
|
|||
``` |
|||
.---------------------------------------. |
|||
.-| revision count | tag ~A | \ |
|||
| |-------------------+-------------------| | |
|||
| | data A | | |
|||
| | | | |
|||
| |-------------------+-------------------| | |
|||
| | tag AxB | data B | <--. | |
|||
| |-------------------+ | | | |
|||
| | | | +-- 1st commit |
|||
| | +-------------------+---------| | | |
|||
| | | tag BxC | | <-.| | |
|||
| |---------+-------------------+ | || | |
|||
| | data C | || | |
|||
| | | || | |
|||
| |-------------------+-------------------| || | |
|||
| | tag CxCRC | CRC | || / |
|||
| |-------------------+-------------------| || |
|||
| | tag CRCxA' | data A' | || \ |
|||
| |-------------------+ | || | |
|||
| | | || | |
|||
| | +-------------------+----| || +-- 2nd commit |
|||
| | | tag CRCxA' | | || | |
|||
| |--------------+-------------------+----| || | |
|||
| | CRC | padding | || / |
|||
| |--------------+----+-------------------| || |
|||
| | tag CRCxA'' | data A'' | <---. \ |
|||
| |-------------------+ | ||| | |
|||
| | | ||| | |
|||
| | +-------------------+---------| ||| | |
|||
| | | tag A''xD | | < ||| | |
|||
| |---------+-------------------+ | |||| +-- 3rd commit |
|||
| | data D | |||| | |
|||
| | +---------| |||| | |
|||
| | | tag Dx| |||| | |
|||
| |---------+-------------------+---------| |||| | |
|||
| |CRC | CRC | | |||| / |
|||
| |---------+-------------------+ | |||| |
|||
| | unwritten storage | |||| more commits |
|||
| | | |||| | |
|||
| | | |||| v |
|||
| | | |||| |
|||
| | | |||| |
|||
| '---------------------------------------' |||| |
|||
'---------------------------------------' |||'- most recent A |
|||
||'-- most recent B |
|||
|'--- most recent C |
|||
'---- most recent D |
|||
``` |
|||
|
|||
Two things to note before we get into the details around tag encoding: |
|||
|
|||
1. Each tag contains a valid bit used to indicate if the tag and containing |
|||
commit is valid. After XORing, this bit should always be zero. |
|||
|
|||
At the end of each commit, the valid bit of the previous tag is XORed |
|||
with the lowest bit in the type field of the CRC tag. This allows |
|||
the CRC tag to force the next commit to fail the valid bit test if it |
|||
has not yet been written to. |
|||
|
|||
2. The valid bit alone is not enough info to know if the next commit has been |
|||
erased. We don't know the order bits will be programmed in a program block, |
|||
so it's possible that the next commit had an attempted program that left the |
|||
valid bit unchanged. |
|||
|
|||
To ensure we only ever program erased bytes, each commit can contain an |
|||
optional forward-CRC (FCRC). An FCRC contains a checksum of some amount of |
|||
bytes in the next commit at the time it was erased. |
|||
|
|||
``` |
|||
.-------------------. \ \ |
|||
| revision count | | | |
|||
|-------------------| | | |
|||
| metadata | | | |
|||
| | +---. +-- current commit |
|||
| | | | | |
|||
|-------------------| | | | |
|||
| FCRC ---|-. | | |
|||
|-------------------| / | | | |
|||
| CRC -----|-' / |
|||
|-------------------| | |
|||
| padding | | padding (does't need CRC) |
|||
| | | |
|||
|-------------------| \ | \ |
|||
| erased? | +-' | |
|||
| | | | +-- next commit |
|||
| v | / | |
|||
| | / |
|||
| | |
|||
'-------------------' |
|||
``` |
|||
|
|||
If the FCRC is missing or the checksum does not match, we must assume a |
|||
commit was attempted but failed due to power-loss. |
|||
|
|||
Note that end-of-block commits do not need an FCRC. |
|||
|
|||
## Metadata tags |
|||
|
|||
So in littlefs, 32-bit tags describe every type of metadata. And this means |
|||
_every_ type of metadata, including file entries, directory fields, and |
|||
global state. Even the CRCs used to mark the end of commits get their own tag. |
|||
|
|||
Because of this, the tag format contains some densely packed information. Note |
|||
that there are multiple levels of types which break down into more info: |
|||
|
|||
``` |
|||
[---- 32 ----] |
|||
[1|-- 11 --|-- 10 --|-- 10 --] |
|||
^. ^ . ^ ^- length |
|||
|. | . '------------ id |
|||
|. '-----.------------------ type (type3) |
|||
'.-----------.------------------ valid bit |
|||
[-3-|-- 8 --] |
|||
^ ^- chunk |
|||
'------- type (type1) |
|||
``` |
|||
|
|||
|
|||
Before we go further, there's one important thing to note. These tags are |
|||
**not** stored in little-endian. Tags stored in commits are actually stored |
|||
in big-endian (and is the only thing in littlefs stored in big-endian). This |
|||
little bit of craziness comes from the fact that the valid bit must be the |
|||
first bit in a commit, and when converted to little-endian, the valid bit finds |
|||
itself in byte 4. We could restructure the tag to store the valid bit lower, |
|||
but, because none of the fields are byte-aligned, this would be more |
|||
complicated than just storing the tag in big-endian. |
|||
|
|||
Another thing to note is that both the tags `0x00000000` and `0xffffffff` are |
|||
invalid and can be used for null values. |
|||
|
|||
Metadata tag fields: |
|||
|
|||
1. **Valid bit (1-bit)** - Indicates if the tag is valid. |
|||
|
|||
2. **Type3 (11-bits)** - Type of the tag. This field is broken down further |
|||
into a 3-bit abstract type and an 8-bit chunk field. Note that the value |
|||
`0x000` is invalid and not assigned a type. |
|||
|
|||
1. **Type1 (3-bits)** - Abstract type of the tag. Groups the tags into |
|||
8 categories that facilitate bitmasked lookups. |
|||
|
|||
2. **Chunk (8-bits)** - Chunk field used for various purposes by the different |
|||
abstract types. type1+chunk+id form a unique identifier for each tag in the |
|||
metadata block. |
|||
|
|||
3. **Id (10-bits)** - File id associated with the tag. Each file in a metadata |
|||
block gets a unique id which is used to associate tags with that file. The |
|||
special value `0x3ff` is used for any tags that are not associated with a |
|||
file, such as directory and global metadata. |
|||
|
|||
4. **Length (10-bits)** - Length of the data in bytes. The special value |
|||
`0x3ff` indicates that this tag has been deleted. |
|||
|
|||
## Metadata types |
|||
|
|||
What follows is an exhaustive list of metadata in littlefs. |
|||
|
|||
--- |
|||
#### `0x401` LFS_TYPE_CREATE |
|||
|
|||
Creates a new file with this id. Note that files in a metadata block |
|||
don't necessarily need a create tag. All a create does is move over any |
|||
files using this id. In this sense a create is similar to insertion into |
|||
an imaginary array of files. |
|||
|
|||
The create and delete tags allow littlefs to keep files in a directory |
|||
ordered alphabetically by filename. |
|||
|
|||
--- |
|||
#### `0x4ff` LFS_TYPE_DELETE |
|||
|
|||
Deletes the file with this id. An inverse to create, this tag moves over |
|||
any files neighboring this id similar to a deletion from an imaginary |
|||
array of files. |
|||
|
|||
--- |
|||
#### `0x0xx` LFS_TYPE_NAME |
|||
|
|||
Associates the id with a file name and file type. |
|||
|
|||
The data contains the file name stored as an ASCII string (may be expanded to |
|||
UTF8 in the future). |
|||
|
|||
The chunk field in this tag indicates an 8-bit file type which can be one of |
|||
the following. |
|||
|
|||
Currently, the name tag must precede any other tags associated with the id and |
|||
can not be reassigned without deleting the file. |
|||
|
|||
Layout of the name tag: |
|||
|
|||
``` |
|||
tag data |
|||
[-- 32 --][--- variable length ---] |
|||
[1| 3| 8 | 10 | 10 ][--- (size * 8) ---] |
|||
^ ^ ^ ^ ^- size ^- file name |
|||
| | | '------ id |
|||
| | '----------- file type |
|||
| '-------------- type1 (0x0) |
|||
'----------------- valid bit |
|||
``` |
|||
|
|||
Name fields: |
|||
|
|||
1. **file type (8-bits)** - Type of the file. |
|||
|
|||
2. **file name** - File name stored as an ASCII string. |
|||
|
|||
--- |
|||
#### `0x001` LFS_TYPE_REG |
|||
|
|||
Initializes the id + name as a regular file. |
|||
|
|||
How each file is stored depends on its struct tag, which is described below. |
|||
|
|||
--- |
|||
#### `0x002` LFS_TYPE_DIR |
|||
|
|||
Initializes the id + name as a directory. |
|||
|
|||
Directories in littlefs are stored on disk as a linked-list of metadata pairs, |
|||
each pair containing any number of files in alphabetical order. A pointer to |
|||
the directory is stored in the struct tag, which is described below. |
|||
|
|||
--- |
|||
#### `0x0ff` LFS_TYPE_SUPERBLOCK |
|||
|
|||
Initializes the id as a superblock entry. |
|||
|
|||
The superblock entry is a special entry used to store format-time configuration |
|||
and identify the filesystem. |
|||
|
|||
The name is a bit of a misnomer. While the superblock entry serves the same |
|||
purpose as a superblock found in other filesystems, in littlefs the superblock |
|||
does not get a dedicated block. Instead, the superblock entry is duplicated |
|||
across a linked-list of metadata pairs rooted on the blocks 0 and 1. The last |
|||
metadata pair doubles as the root directory of the filesystem. |
|||
|
|||
``` |
|||
.--------. .--------. .--------. .--------. .--------. |
|||
.| super |->| super |->| super |->| super |->| file B | |
|||
|| block | || block | || block | || block | || file C | |
|||
|| | || | || | || file A | || file D | |
|||
|'--------' |'--------' |'--------' |'--------' |'--------' |
|||
'--------' '--------' '--------' '--------' '--------' |
|||
|
|||
\----------------+----------------/ \----------+----------/ |
|||
superblock pairs root directory |
|||
``` |
|||
|
|||
The filesystem starts with only the root directory. The superblock metadata |
|||
pairs grow every time the root pair is compacted in order to prolong the |
|||
life of the device exponentially. |
|||
|
|||
The contents of the superblock entry are stored in a name tag with the |
|||
superblock type and an inline-struct tag. The name tag contains the magic |
|||
string "littlefs", while the inline-struct tag contains version and |
|||
configuration information. |
|||
|
|||
Layout of the superblock name tag and inline-struct tag: |
|||
|
|||
``` |
|||
tag data |
|||
[-- 32 --][-- 32 --|-- 32 --] |
|||
[1|- 11 -| 10 | 10 ][--- 64 ---] |
|||
^ ^ ^ ^- size (8) ^- magic string ("littlefs") |
|||
| | '------ id (0) |
|||
| '------------ type (0x0ff) |
|||
'----------------- valid bit |
|||
|
|||
tag data |
|||
[-- 32 --][-- 32 --|-- 32 --|-- 32 --] |
|||
[1|- 11 -| 10 | 10 ][-- 32 --|-- 32 --|-- 32 --] |
|||
^ ^ ^ ^ ^- version ^- block size ^- block count |
|||
| | | | [-- 32 --|-- 32 --|-- 32 --] |
|||
| | | | [-- 32 --|-- 32 --|-- 32 --] |
|||
| | | | ^- name max ^- file max ^- attr max |
|||
| | | '- size (24) |
|||
| | '------ id (0) |
|||
| '------------ type (0x201) |
|||
'----------------- valid bit |
|||
``` |
|||
|
|||
Superblock fields: |
|||
|
|||
1. **Magic string (8-bytes)** - Magic string indicating the presence of |
|||
littlefs on the device. Must be the string "littlefs". |
|||
|
|||
2. **Version (32-bits)** - The version of littlefs at format time. The version |
|||
is encoded in a 32-bit value with the upper 16-bits containing the major |
|||
version, and the lower 16-bits containing the minor version. |
|||
|
|||
This specification describes version 2.0 (`0x00020000`). |
|||
|
|||
3. **Block size (32-bits)** - Size of the logical block size used by the |
|||
filesystem in bytes. |
|||
|
|||
4. **Block count (32-bits)** - Number of blocks in the filesystem. |
|||
|
|||
5. **Name max (32-bits)** - Maximum size of file names in bytes. |
|||
|
|||
6. **File max (32-bits)** - Maximum size of files in bytes. |
|||
|
|||
7. **Attr max (32-bits)** - Maximum size of file attributes in bytes. |
|||
|
|||
The superblock must always be the first entry (id 0) in the metadata pair, and |
|||
the name tag must always be the first tag in the metadata pair. This makes it |
|||
so that the magic string "littlefs" will always reside at offset=8 in a valid |
|||
littlefs superblock. |
|||
|
|||
--- |
|||
#### `0x2xx` LFS_TYPE_STRUCT |
|||
|
|||
Associates the id with an on-disk data structure. |
|||
|
|||
The exact layout of the data depends on the data structure type stored in the |
|||
chunk field and can be one of the following. |
|||
|
|||
Any type of struct supersedes all other structs associated with the id. For |
|||
example, appending a ctz-struct replaces an inline-struct on the same file. |
|||
|
|||
--- |
|||
#### `0x200` LFS_TYPE_DIRSTRUCT |
|||
|
|||
Gives the id a directory data structure. |
|||
|
|||
Directories in littlefs are stored on disk as a linked-list of metadata pairs, |
|||
each pair containing any number of files in alphabetical order. |
|||
|
|||
``` |
|||
| |
|||
v |
|||
.--------. .--------. .--------. .--------. .--------. .--------. |
|||
.| file A |->| file D |->| file G |->| file I |->| file J |->| file M | |
|||
|| file B | || file E | || file H | || | || file K | || file N | |
|||
|| file C | || file F | || | || | || file L | || | |
|||
|'--------' |'--------' |'--------' |'--------' |'--------' |'--------' |
|||
'--------' '--------' '--------' '--------' '--------' '--------' |
|||
``` |
|||
|
|||
The dir-struct tag contains only the pointer to the first metadata-pair in the |
|||
directory. The directory size is not known without traversing the directory. |
|||
|
|||
The pointer to the next metadata-pair in the directory is stored in a tail tag, |
|||
which is described below. |
|||
|
|||
Layout of the dir-struct tag: |
|||
|
|||
``` |
|||
tag data |
|||
[-- 32 --][-- 32 --|-- 32 --] |
|||
[1|- 11 -| 10 | 10 ][--- 64 ---] |
|||
^ ^ ^ ^- size (8) ^- metadata pair |
|||
| | '------ id |
|||
| '------------ type (0x200) |
|||
'----------------- valid bit |
|||
``` |
|||
|
|||
Dir-struct fields: |
|||
|
|||
1. **Metadata pair (8-bytes)** - Pointer to the first metadata-pair |
|||
in the directory. |
|||
|
|||
--- |
|||
#### `0x201` LFS_TYPE_INLINESTRUCT |
|||
|
|||
Gives the id an inline data structure. |
|||
|
|||
Inline structs store small files that can fit in the metadata pair. In this |
|||
case, the file data is stored directly in the tag's data area. |
|||
|
|||
Layout of the inline-struct tag: |
|||
|
|||
``` |
|||
tag data |
|||
[-- 32 --][--- variable length ---] |
|||
[1|- 11 -| 10 | 10 ][--- (size * 8) ---] |
|||
^ ^ ^ ^- size ^- inline data |
|||
| | '------ id |
|||
| '------------ type (0x201) |
|||
'----------------- valid bit |
|||
``` |
|||
|
|||
Inline-struct fields: |
|||
|
|||
1. **Inline data** - File data stored directly in the metadata-pair. |
|||
|
|||
--- |
|||
#### `0x202` LFS_TYPE_CTZSTRUCT |
|||
|
|||
Gives the id a CTZ skip-list data structure. |
|||
|
|||
CTZ skip-lists store files that can not fit in the metadata pair. These files |
|||
are stored in a skip-list in reverse, with a pointer to the head of the |
|||
skip-list. Note that the head of the skip-list and the file size is enough |
|||
information to read the file. |
|||
|
|||
How exactly CTZ skip-lists work is a bit complicated. A full explanation can be |
|||
found in the [DESIGN.md](DESIGN.md#ctz-skip-lists). |
|||
|
|||
A quick summary: For every _n_‍th block where _n_ is divisible by |
|||
2‍_ˣ_, that block contains a pointer to block _n_-2‍_ˣ_. |
|||
These pointers are stored in increasing order of _x_ in each block of the file |
|||
before the actual data. |
|||
|
|||
``` |
|||
| |
|||
v |
|||
.--------. .--------. .--------. .--------. .--------. .--------. |
|||
| A |<-| D |<-| G |<-| J |<-| M |<-| P | |
|||
| B |<-| E |--| H |<-| K |--| N | | Q | |
|||
| C |<-| F |--| I |--| L |--| O | | | |
|||
'--------' '--------' '--------' '--------' '--------' '--------' |
|||
block 0 block 1 block 2 block 3 block 4 block 5 |
|||
1 skip 2 skips 1 skip 3 skips 1 skip |
|||
``` |
|||
|
|||
Note that the maximum number of pointers in a block is bounded by the maximum |
|||
file size divided by the block size. With 32 bits for file size, this results |
|||
in a minimum block size of 104 bytes. |
|||
|
|||
Layout of the CTZ-struct tag: |
|||
|
|||
``` |
|||
tag data |
|||
[-- 32 --][-- 32 --|-- 32 --] |
|||
[1|- 11 -| 10 | 10 ][-- 32 --|-- 32 --] |
|||
^ ^ ^ ^ ^ ^- file size |
|||
| | | | '-------------------- file head |
|||
| | | '- size (8) |
|||
| | '------ id |
|||
| '------------ type (0x202) |
|||
'----------------- valid bit |
|||
``` |
|||
|
|||
CTZ-struct fields: |
|||
|
|||
1. **File head (32-bits)** - Pointer to the block that is the head of the |
|||
file's CTZ skip-list. |
|||
|
|||
2. **File size (32-bits)** - Size of the file in bytes. |
|||
|
|||
--- |
|||
#### `0x3xx` LFS_TYPE_USERATTR |
|||
|
|||
Attaches a user attribute to an id. |
|||
|
|||
littlefs has a concept of "user attributes". These are small user-provided |
|||
attributes that can be used to store things like timestamps, hashes, |
|||
permissions, etc. |
|||
|
|||
Each user attribute is uniquely identified by an 8-bit type which is stored in |
|||
the chunk field, and the user attribute itself can be found in the tag's data. |
|||
|
|||
There are currently no standard user attributes and a portable littlefs |
|||
implementation should work with any user attributes missing. |
|||
|
|||
Layout of the user-attr tag: |
|||
|
|||
``` |
|||
tag data |
|||
[-- 32 --][--- variable length ---] |
|||
[1| 3| 8 | 10 | 10 ][--- (size * 8) ---] |
|||
^ ^ ^ ^ ^- size ^- attr data |
|||
| | | '------ id |
|||
| | '----------- attr type |
|||
| '-------------- type1 (0x3) |
|||
'----------------- valid bit |
|||
``` |
|||
|
|||
User-attr fields: |
|||
|
|||
1. **Attr type (8-bits)** - Type of the user attributes. |
|||
|
|||
2. **Attr data** - The data associated with the user attribute. |
|||
|
|||
--- |
|||
#### `0x6xx` LFS_TYPE_TAIL |
|||
|
|||
Provides the tail pointer for the metadata pair itself. |
|||
|
|||
The metadata pair's tail pointer is used in littlefs for a linked-list |
|||
containing all metadata pairs. The chunk field contains the type of the tail, |
|||
which indicates if the following metadata pair is a part of the directory |
|||
(hard-tail) or only used to traverse the filesystem (soft-tail). |
|||
|
|||
``` |
|||
.--------. |
|||
.| dir A |-. |
|||
||softtail| | |
|||
.--------| |-' |
|||
| |'--------' |
|||
| '---|--|-' |
|||
| .-' '-------------. |
|||
| v v |
|||
| .--------. .--------. .--------. |
|||
'->| dir B |->| dir B |->| dir C | |
|||
||hardtail| ||softtail| || | |
|||
|| | || | || | |
|||
|'--------' |'--------' |'--------' |
|||
'--------' '--------' '--------' |
|||
``` |
|||
|
|||
Currently any type supersedes any other preceding tails in the metadata pair, |
|||
but this may change if additional metadata pair state is added. |
|||
|
|||
A note about the metadata pair linked-list: Normally, this linked-list contains |
|||
every metadata pair in the filesystem. However, there are some operations that |
|||
can cause this linked-list to become out of sync if a power-loss were to occur. |
|||
When this happens, littlefs sets the "sync" flag in the global state. How |
|||
exactly this flag is stored is described below. |
|||
|
|||
When the sync flag is set: |
|||
|
|||
1. The linked-list may contain an orphaned directory that has been removed in |
|||
the filesystem. |
|||
2. The linked-list may contain a metadata pair with a bad block that has been |
|||
replaced in the filesystem. |
|||
|
|||
If the sync flag is set, the threaded linked-list must be checked for these |
|||
errors before it can be used reliably. Note that the threaded linked-list can |
|||
be ignored if littlefs is mounted read-only. |
|||
|
|||
Layout of the tail tag: |
|||
|
|||
``` |
|||
tag data |
|||
[-- 32 --][-- 32 --|-- 32 --] |
|||
[1| 3| 8 | 10 | 10 ][--- 64 ---] |
|||
^ ^ ^ ^ ^- size (8) ^- metadata pair |
|||
| | | '------ id |
|||
| | '---------- tail type |
|||
| '------------- type1 (0x6) |
|||
'---------------- valid bit |
|||
``` |
|||
|
|||
Tail fields: |
|||
|
|||
1. **Tail type (8-bits)** - Type of the tail pointer. |
|||
|
|||
2. **Metadata pair (8-bytes)** - Pointer to the next metadata-pair. |
|||
|
|||
--- |
|||
#### `0x600` LFS_TYPE_SOFTTAIL |
|||
|
|||
Provides a tail pointer that points to the next metadata pair in the |
|||
filesystem. |
|||
|
|||
In this case, the next metadata pair is not a part of our current directory |
|||
and should only be followed when traversing the entire filesystem. |
|||
|
|||
--- |
|||
#### `0x601` LFS_TYPE_HARDTAIL |
|||
|
|||
Provides a tail pointer that points to the next metadata pair in the |
|||
directory. |
|||
|
|||
In this case, the next metadata pair belongs to the current directory. Note |
|||
that because directories in littlefs are sorted alphabetically, the next |
|||
metadata pair should only contain filenames greater than any filename in the |
|||
current pair. |
|||
|
|||
--- |
|||
#### `0x7xx` LFS_TYPE_GSTATE |
|||
|
|||
Provides delta bits for global state entries. |
|||
|
|||
littlefs has a concept of "global state". This is a small set of state that |
|||
can be updated by a commit to _any_ metadata pair in the filesystem. |
|||
|
|||
The way this works is that the global state is stored as a set of deltas |
|||
distributed across the filesystem such that the global state can be found by |
|||
the xor-sum of these deltas. |
|||
|
|||
``` |
|||
.--------. .--------. .--------. .--------. .--------. |
|||
.| |->| gdelta |->| |->| gdelta |->| gdelta | |
|||
|| | || 0x23 | || | || 0xff | || 0xce | |
|||
|| | || | || | || | || | |
|||
|'--------' |'--------' |'--------' |'--------' |'--------' |
|||
'--------' '----|---' '--------' '----|---' '----|---' |
|||
v v v |
|||
0x00 --> xor ------------------> xor ------> xor --> gstate = 0x12 |
|||
``` |
|||
|
|||
Note that storing globals this way is very expensive in terms of storage usage, |
|||
so any global state should be kept very small. |
|||
|
|||
The size and format of each piece of global state depends on the type, which |
|||
is stored in the chunk field. Currently, the only global state is move state, |
|||
which is outlined below. |
|||
|
|||
--- |
|||
#### `0x7ff` LFS_TYPE_MOVESTATE |
|||
|
|||
Provides delta bits for the global move state. |
|||
|
|||
The move state in littlefs is used to store info about operations that could |
|||
cause to filesystem to go out of sync if the power is lost. The operations |
|||
where this could occur is moves of files between metadata pairs and any |
|||
operation that changes metadata pairs on the threaded linked-list. |
|||
|
|||
In the case of moves, the move state contains a tag + metadata pair describing |
|||
the source of the ongoing move. If this tag is non-zero, that means that power |
|||
was lost during a move, and the file exists in two different locations. If this |
|||
happens, the source of the move should be considered deleted, and the move |
|||
should be completed (the source should be deleted) before any other write |
|||
operations to the filesystem. |
|||
|
|||
In the case of operations to the threaded linked-list, a single "sync" bit is |
|||
used to indicate that a modification is ongoing. If this sync flag is set, the |
|||
threaded linked-list will need to be checked for errors before it can be used |
|||
reliably. The exact cases to check for are described above in the tail tag. |
|||
|
|||
Layout of the move state: |
|||
|
|||
``` |
|||
tag data |
|||
[-- 32 --][-- 32 --|-- 32 --|-- 32 --] |
|||
[1|- 11 -| 10 | 10 ][1|- 11 -| 10 | 10 |--- 64 ---] |
|||
^ ^ ^ ^ ^ ^ ^ ^- padding (0) ^- metadata pair |
|||
| | | | | | '------ move id |
|||
| | | | | '------------ move type |
|||
| | | | '----------------- sync bit |
|||
| | | | |
|||
| | | '- size (12) |
|||
| | '------ id (0x3ff) |
|||
| '------------ type (0x7ff) |
|||
'----------------- valid bit |
|||
``` |
|||
|
|||
Move state fields: |
|||
|
|||
1. **Sync bit (1-bit)** - Indicates if the metadata pair threaded linked-list |
|||
is in-sync. If set, the threaded linked-list should be checked for errors. |
|||
|
|||
2. **Move type (11-bits)** - Type of move being performed. Must be either |
|||
`0x000`, indicating no move, or `0x4ff` indicating the source file should |
|||
be deleted. |
|||
|
|||
3. **Move id (10-bits)** - The file id being moved. |
|||
|
|||
4. **Metadata pair (8-bytes)** - Pointer to the metadata-pair containing |
|||
the move. |
|||
|
|||
--- |
|||
#### `0x5xx` LFS_TYPE_CRC |
|||
|
|||
Last but not least, the CRC tag marks the end of a commit and provides a |
|||
checksum for any commits to the metadata block. |
|||
|
|||
The first 32-bits of the data contain a CRC-32 with a polynomial of |
|||
`0x04c11db7` initialized with `0xffffffff`. This CRC provides a checksum for |
|||
all metadata since the previous CRC tag, including the CRC tag itself. For |
|||
the first commit, this includes the revision count for the metadata block. |
|||
|
|||
However, the size of the data is not limited to 32-bits. The data field may |
|||
larger to pad the commit to the next program-aligned boundary. |
|||
|
|||
In addition, the CRC tag's chunk field contains a set of flags which can |
|||
change the behaviour of commits. Currently the only flag in use is the lowest |
|||
bit, which determines the expected state of the valid bit for any following |
|||
tags. This is used to guarantee that unwritten storage in a metadata block |
|||
will be detected as invalid. |
|||
|
|||
Layout of the CRC tag: |
|||
|
|||
``` |
|||
tag data |
|||
[-- 32 --][-- 32 --|--- variable length ---] |
|||
[1| 3| 8 | 10 | 10 ][-- 32 --|--- (size * 8 - 32) ---] |
|||
^ ^ ^ ^ ^ ^- crc ^- padding |
|||
| | | | '- size |
|||
| | | '------ id (0x3ff) |
|||
| | '----------- valid state |
|||
| '-------------- type1 (0x5) |
|||
'----------------- valid bit |
|||
``` |
|||
|
|||
CRC fields: |
|||
|
|||
1. **Valid state (1-bit)** - Indicates the expected value of the valid bit for |
|||
any tags in the next commit. |
|||
|
|||
2. **CRC (32-bits)** - CRC-32 with a polynomial of `0x04c11db7` initialized |
|||
with `0xffffffff`. |
|||
|
|||
3. **Padding** - Padding to the next program-aligned boundary. No guarantees |
|||
are made about the contents. |
|||
|
|||
--- |
|||
#### `0x5ff` LFS_TYPE_FCRC |
|||
|
|||
Added in lfs2.1, the optional FCRC tag contains a checksum of some amount of |
|||
bytes in the next commit at the time it was erased. This allows us to ensure |
|||
that we only ever program erased bytes, even if a previous commit failed due |
|||
to power-loss. |
|||
|
|||
When programming a commit, the FCRC size must be at least as large as the |
|||
program block size. However, the program block is not saved on disk, and can |
|||
change between mounts, so the FCRC size on disk may be different than the |
|||
current program block size. |
|||
|
|||
If the FCRC is missing or the checksum does not match, we must assume a |
|||
commit was attempted but failed due to power-loss. |
|||
|
|||
Layout of the FCRC tag: |
|||
|
|||
``` |
|||
tag data |
|||
[-- 32 --][-- 32 --|-- 32 --] |
|||
[1|- 11 -| 10 | 10 ][-- 32 --|-- 32 --] |
|||
^ ^ ^ ^ ^- fcrc size ^- fcrc |
|||
| | | '- size (8) |
|||
| | '------ id (0x3ff) |
|||
| '------------ type (0x5ff) |
|||
'----------------- valid bit |
|||
``` |
|||
|
|||
FCRC fields: |
|||
|
|||
1. **FCRC size (32-bits)** - Number of bytes after this commit's CRC tag's |
|||
padding to include in the FCRC. |
|||
|
|||
2. **FCRC (32-bits)** - CRC of the bytes after this commit's CRC tag's padding |
|||
when erased. Like the CRC tag, this uses a CRC-32 with a polynomial of |
|||
`0x04c11db7` initialized with `0xffffffff`. |
|||
|
|||
--- |
|||
@ -0,0 +1,802 @@ |
|||
/*
|
|||
* The little filesystem |
|||
* |
|||
* Copyright (c) 2022, The littlefs authors. |
|||
* Copyright (c) 2017, Arm Limited. All rights reserved. |
|||
* SPDX-License-Identifier: BSD-3-Clause |
|||
*/ |
|||
#ifndef LFS_H |
|||
#define LFS_H |
|||
|
|||
#include "lfs_util.h" |
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" |
|||
{ |
|||
#endif |
|||
|
|||
|
|||
/// Version info ///
|
|||
|
|||
// Software library version
|
|||
// Major (top-nibble), incremented on backwards incompatible changes
|
|||
// Minor (bottom-nibble), incremented on feature additions
|
|||
#define LFS_VERSION 0x0002000b |
|||
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) |
|||
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) |
|||
|
|||
// Version of On-disk data structures
|
|||
// Major (top-nibble), incremented on backwards incompatible changes
|
|||
// Minor (bottom-nibble), incremented on feature additions
|
|||
#define LFS_DISK_VERSION 0x00020001 |
|||
#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) |
|||
#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) |
|||
|
|||
|
|||
/// Definitions ///
|
|||
|
|||
// Type definitions
|
|||
typedef uint32_t lfs_size_t; |
|||
typedef uint32_t lfs_off_t; |
|||
|
|||
typedef int32_t lfs_ssize_t; |
|||
typedef int32_t lfs_soff_t; |
|||
|
|||
typedef uint32_t lfs_block_t; |
|||
|
|||
// Maximum name size in bytes, may be redefined to reduce the size of the
|
|||
// info struct. Limited to <= 1022. Stored in superblock and must be
|
|||
// respected by other littlefs drivers.
|
|||
#ifndef LFS_NAME_MAX |
|||
#define LFS_NAME_MAX 255 |
|||
#endif |
|||
|
|||
// Maximum size of a file in bytes, may be redefined to limit to support other
|
|||
// drivers. Limited on disk to <= 2147483647. Stored in superblock and must be
|
|||
// respected by other littlefs drivers.
|
|||
#ifndef LFS_FILE_MAX |
|||
#define LFS_FILE_MAX 2147483647 |
|||
#endif |
|||
|
|||
// Maximum size of custom attributes in bytes, may be redefined, but there is
|
|||
// no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022. Stored
|
|||
// in superblock and must be respected by other littlefs drivers.
|
|||
#ifndef LFS_ATTR_MAX |
|||
#define LFS_ATTR_MAX 1022 |
|||
#endif |
|||
|
|||
// Possible error codes, these are negative to allow
|
|||
// valid positive return values
|
|||
enum lfs_error { |
|||
LFS_ERR_OK = 0, // No error
|
|||
LFS_ERR_IO = -5, // Error during device operation
|
|||
LFS_ERR_CORRUPT = -84, // Corrupted
|
|||
LFS_ERR_NOENT = -2, // No directory entry
|
|||
LFS_ERR_EXIST = -17, // Entry already exists
|
|||
LFS_ERR_NOTDIR = -20, // Entry is not a dir
|
|||
LFS_ERR_ISDIR = -21, // Entry is a dir
|
|||
LFS_ERR_NOTEMPTY = -39, // Dir is not empty
|
|||
LFS_ERR_BADF = -9, // Bad file number
|
|||
LFS_ERR_FBIG = -27, // File too large
|
|||
LFS_ERR_INVAL = -22, // Invalid parameter
|
|||
LFS_ERR_NOSPC = -28, // No space left on device
|
|||
LFS_ERR_NOMEM = -12, // No more memory available
|
|||
LFS_ERR_NOATTR = -61, // No data/attr available
|
|||
LFS_ERR_NAMETOOLONG = -36, // File name too long
|
|||
}; |
|||
|
|||
// File types
|
|||
enum lfs_type { |
|||
// file types
|
|||
LFS_TYPE_REG = 0x001, |
|||
LFS_TYPE_DIR = 0x002, |
|||
|
|||
// internally used types
|
|||
LFS_TYPE_SPLICE = 0x400, |
|||
LFS_TYPE_NAME = 0x000, |
|||
LFS_TYPE_STRUCT = 0x200, |
|||
LFS_TYPE_USERATTR = 0x300, |
|||
LFS_TYPE_FROM = 0x100, |
|||
LFS_TYPE_TAIL = 0x600, |
|||
LFS_TYPE_GLOBALS = 0x700, |
|||
LFS_TYPE_CRC = 0x500, |
|||
|
|||
// internally used type specializations
|
|||
LFS_TYPE_CREATE = 0x401, |
|||
LFS_TYPE_DELETE = 0x4ff, |
|||
LFS_TYPE_SUPERBLOCK = 0x0ff, |
|||
LFS_TYPE_DIRSTRUCT = 0x200, |
|||
LFS_TYPE_CTZSTRUCT = 0x202, |
|||
LFS_TYPE_INLINESTRUCT = 0x201, |
|||
LFS_TYPE_SOFTTAIL = 0x600, |
|||
LFS_TYPE_HARDTAIL = 0x601, |
|||
LFS_TYPE_MOVESTATE = 0x7ff, |
|||
LFS_TYPE_CCRC = 0x500, |
|||
LFS_TYPE_FCRC = 0x5ff, |
|||
|
|||
// internal chip sources
|
|||
LFS_FROM_NOOP = 0x000, |
|||
LFS_FROM_MOVE = 0x101, |
|||
LFS_FROM_USERATTRS = 0x102, |
|||
}; |
|||
|
|||
// File open flags
|
|||
enum lfs_open_flags { |
|||
// open flags
|
|||
LFS_O_RDONLY = 1, // Open a file as read only
|
|||
#ifndef LFS_READONLY |
|||
LFS_O_WRONLY = 2, // Open a file as write only
|
|||
LFS_O_RDWR = 3, // Open a file as read and write
|
|||
LFS_O_CREAT = 0x0100, // Create a file if it does not exist
|
|||
LFS_O_EXCL = 0x0200, // Fail if a file already exists
|
|||
LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size
|
|||
LFS_O_APPEND = 0x0800, // Move to end of file on every write
|
|||
#endif |
|||
|
|||
// internally used flags
|
|||
#ifndef LFS_READONLY |
|||
LFS_F_DIRTY = 0x00010000, // File does not match storage due to write
|
|||
LFS_F_DUSTY = 0x00020000, // File does not match storage due to desync
|
|||
LFS_F_WRITING = 0x00040000, // File has been written since last flush
|
|||
#endif |
|||
LFS_F_READING = 0x00080000, // File has been read since last flush
|
|||
#ifndef LFS_READONLY |
|||
LFS_F_ERRED = 0x00100000, // An error occurred during write
|
|||
#endif |
|||
LFS_F_INLINE = 0x01000000, // Currently inlined in directory entry
|
|||
}; |
|||
|
|||
// File seek flags
|
|||
enum lfs_whence_flags { |
|||
LFS_SEEK_SET = 0, // Seek relative to an absolute position
|
|||
LFS_SEEK_CUR = 1, // Seek relative to the current file position
|
|||
LFS_SEEK_END = 2, // Seek relative to the end of the file
|
|||
}; |
|||
|
|||
|
|||
// Configuration provided during initialization of the littlefs
|
|||
struct lfs_config { |
|||
// Opaque user provided context that can be used to pass
|
|||
// information to the block device operations
|
|||
void *context; |
|||
|
|||
// Read a region in a block. Negative error codes are propagated
|
|||
// to the user.
|
|||
int (*read)(const struct lfs_config *c, lfs_block_t block, |
|||
lfs_off_t off, void *buffer, lfs_size_t size); |
|||
|
|||
// Program a region in a block. The block must have previously
|
|||
// been erased. Negative error codes are propagated to the user.
|
|||
// May return LFS_ERR_CORRUPT if the block should be considered bad.
|
|||
int (*prog)(const struct lfs_config *c, lfs_block_t block, |
|||
lfs_off_t off, const void *buffer, lfs_size_t size); |
|||
|
|||
// Erase a block. A block must be erased before being programmed.
|
|||
// The state of an erased block is undefined. Negative error codes
|
|||
// are propagated to the user.
|
|||
// May return LFS_ERR_CORRUPT if the block should be considered bad.
|
|||
int (*erase)(const struct lfs_config *c, lfs_block_t block); |
|||
|
|||
// Sync the state of the underlying block device. Negative error codes
|
|||
// are propagated to the user.
|
|||
int (*sync)(const struct lfs_config *c); |
|||
|
|||
#ifdef LFS_THREADSAFE |
|||
// Lock the underlying block device. Negative error codes
|
|||
// are propagated to the user.
|
|||
int (*lock)(const struct lfs_config *c); |
|||
|
|||
// Unlock the underlying block device. Negative error codes
|
|||
// are propagated to the user.
|
|||
int (*unlock)(const struct lfs_config *c); |
|||
#endif |
|||
|
|||
// Minimum size of a block read in bytes. All read operations will be a
|
|||
// multiple of this value.
|
|||
lfs_size_t read_size; |
|||
|
|||
// Minimum size of a block program in bytes. All program operations will be
|
|||
// a multiple of this value.
|
|||
lfs_size_t prog_size; |
|||
|
|||
// Size of an erasable block in bytes. This does not impact ram consumption
|
|||
// and may be larger than the physical erase size. However, non-inlined
|
|||
// files take up at minimum one block. Must be a multiple of the read and
|
|||
// program sizes.
|
|||
lfs_size_t block_size; |
|||
|
|||
// Number of erasable blocks on the device. Defaults to block_count stored
|
|||
// on disk when zero.
|
|||
lfs_size_t block_count; |
|||
|
|||
// Number of erase cycles before littlefs evicts metadata logs and moves
|
|||
// the metadata to another block. Suggested values are in the
|
|||
// range 100-1000, with large values having better performance at the cost
|
|||
// of less consistent wear distribution.
|
|||
//
|
|||
// Set to -1 to disable block-level wear-leveling.
|
|||
int32_t block_cycles; |
|||
|
|||
// Size of block caches in bytes. Each cache buffers a portion of a block in
|
|||
// RAM. The littlefs needs a read cache, a program cache, and one additional
|
|||
// cache per file. Larger caches can improve performance by storing more
|
|||
// data and reducing the number of disk accesses. Must be a multiple of the
|
|||
// read and program sizes, and a factor of the block size.
|
|||
lfs_size_t cache_size; |
|||
|
|||
// Size of the lookahead buffer in bytes. A larger lookahead buffer
|
|||
// increases the number of blocks found during an allocation pass. The
|
|||
// lookahead buffer is stored as a compact bitmap, so each byte of RAM
|
|||
// can track 8 blocks.
|
|||
lfs_size_t lookahead_size; |
|||
|
|||
// Threshold for metadata compaction during lfs_fs_gc in bytes. Metadata
|
|||
// pairs that exceed this threshold will be compacted during lfs_fs_gc.
|
|||
// Defaults to ~88% block_size when zero, though the default may change
|
|||
// in the future.
|
|||
//
|
|||
// Note this only affects lfs_fs_gc. Normal compactions still only occur
|
|||
// when full.
|
|||
//
|
|||
// Set to -1 to disable metadata compaction during lfs_fs_gc.
|
|||
lfs_size_t compact_thresh; |
|||
|
|||
// Optional statically allocated read buffer. Must be cache_size.
|
|||
// By default lfs_malloc is used to allocate this buffer.
|
|||
void *read_buffer; |
|||
|
|||
// Optional statically allocated program buffer. Must be cache_size.
|
|||
// By default lfs_malloc is used to allocate this buffer.
|
|||
void *prog_buffer; |
|||
|
|||
// Optional statically allocated lookahead buffer. Must be lookahead_size.
|
|||
// By default lfs_malloc is used to allocate this buffer.
|
|||
void *lookahead_buffer; |
|||
|
|||
// Optional upper limit on length of file names in bytes. No downside for
|
|||
// larger names except the size of the info struct which is controlled by
|
|||
// the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX or name_max stored on
|
|||
// disk when zero.
|
|||
lfs_size_t name_max; |
|||
|
|||
// Optional upper limit on files in bytes. No downside for larger files
|
|||
// but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX or file_max stored
|
|||
// on disk when zero.
|
|||
lfs_size_t file_max; |
|||
|
|||
// Optional upper limit on custom attributes in bytes. No downside for
|
|||
// larger attributes size but must be <= LFS_ATTR_MAX. Defaults to
|
|||
// LFS_ATTR_MAX or attr_max stored on disk when zero.
|
|||
lfs_size_t attr_max; |
|||
|
|||
// Optional upper limit on total space given to metadata pairs in bytes. On
|
|||
// devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB)
|
|||
// can help bound the metadata compaction time. Must be <= block_size.
|
|||
// Defaults to block_size when zero.
|
|||
lfs_size_t metadata_max; |
|||
|
|||
// Optional upper limit on inlined files in bytes. Inlined files live in
|
|||
// metadata and decrease storage requirements, but may be limited to
|
|||
// improve metadata-related performance. Must be <= cache_size, <=
|
|||
// attr_max, and <= block_size/8. Defaults to the largest possible
|
|||
// inline_max when zero.
|
|||
//
|
|||
// Set to -1 to disable inlined files.
|
|||
lfs_size_t inline_max; |
|||
|
|||
#ifdef LFS_MULTIVERSION |
|||
// On-disk version to use when writing in the form of 16-bit major version
|
|||
// + 16-bit minor version. This limiting metadata to what is supported by
|
|||
// older minor versions. Note that some features will be lost. Defaults to
|
|||
// to the most recent minor version when zero.
|
|||
uint32_t disk_version; |
|||
#endif |
|||
}; |
|||
|
|||
// File info structure
|
|||
struct lfs_info { |
|||
// Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR
|
|||
uint8_t type; |
|||
|
|||
// Size of the file, only valid for REG files. Limited to 32-bits.
|
|||
lfs_size_t size; |
|||
|
|||
// Name of the file stored as a null-terminated string. Limited to
|
|||
// LFS_NAME_MAX+1, which can be changed by redefining LFS_NAME_MAX to
|
|||
// reduce RAM. LFS_NAME_MAX is stored in superblock and must be
|
|||
// respected by other littlefs drivers.
|
|||
char name[LFS_NAME_MAX+1]; |
|||
}; |
|||
|
|||
// Filesystem info structure
|
|||
struct lfs_fsinfo { |
|||
// On-disk version.
|
|||
uint32_t disk_version; |
|||
|
|||
// Size of a logical block in bytes.
|
|||
lfs_size_t block_size; |
|||
|
|||
// Number of logical blocks in filesystem.
|
|||
lfs_size_t block_count; |
|||
|
|||
// Upper limit on the length of file names in bytes.
|
|||
lfs_size_t name_max; |
|||
|
|||
// Upper limit on the size of files in bytes.
|
|||
lfs_size_t file_max; |
|||
|
|||
// Upper limit on the size of custom attributes in bytes.
|
|||
lfs_size_t attr_max; |
|||
}; |
|||
|
|||
// Custom attribute structure, used to describe custom attributes
|
|||
// committed atomically during file writes.
|
|||
struct lfs_attr { |
|||
// 8-bit type of attribute, provided by user and used to
|
|||
// identify the attribute
|
|||
uint8_t type; |
|||
|
|||
// Pointer to buffer containing the attribute
|
|||
void *buffer; |
|||
|
|||
// Size of attribute in bytes, limited to LFS_ATTR_MAX
|
|||
lfs_size_t size; |
|||
}; |
|||
|
|||
// Optional configuration provided during lfs_file_opencfg
|
|||
struct lfs_file_config { |
|||
// Optional statically allocated file buffer. Must be cache_size.
|
|||
// By default lfs_malloc is used to allocate this buffer.
|
|||
void *buffer; |
|||
|
|||
// Optional list of custom attributes related to the file. If the file
|
|||
// is opened with read access, these attributes will be read from disk
|
|||
// during the open call. If the file is opened with write access, the
|
|||
// attributes will be written to disk every file sync or close. This
|
|||
// write occurs atomically with update to the file's contents.
|
|||
//
|
|||
// Custom attributes are uniquely identified by an 8-bit type and limited
|
|||
// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller
|
|||
// than the buffer, it will be padded with zeros. If the stored attribute
|
|||
// is larger, then it will be silently truncated. If the attribute is not
|
|||
// found, it will be created implicitly.
|
|||
struct lfs_attr *attrs; |
|||
|
|||
// Number of custom attributes in the list
|
|||
lfs_size_t attr_count; |
|||
}; |
|||
|
|||
|
|||
/// internal littlefs data structures ///
|
|||
typedef struct lfs_cache { |
|||
lfs_block_t block; |
|||
lfs_off_t off; |
|||
lfs_size_t size; |
|||
uint8_t *buffer; |
|||
} lfs_cache_t; |
|||
|
|||
typedef struct lfs_mdir { |
|||
lfs_block_t pair[2]; |
|||
uint32_t rev; |
|||
lfs_off_t off; |
|||
uint32_t etag; |
|||
uint16_t count; |
|||
bool erased; |
|||
bool split; |
|||
lfs_block_t tail[2]; |
|||
} lfs_mdir_t; |
|||
|
|||
// littlefs directory type
|
|||
typedef struct lfs_dir { |
|||
struct lfs_dir *next; |
|||
uint16_t id; |
|||
uint8_t type; |
|||
lfs_mdir_t m; |
|||
|
|||
lfs_off_t pos; |
|||
lfs_block_t head[2]; |
|||
} lfs_dir_t; |
|||
|
|||
// littlefs file type
|
|||
typedef struct lfs_file { |
|||
struct lfs_file *next; |
|||
uint16_t id; |
|||
uint8_t type; |
|||
lfs_mdir_t m; |
|||
|
|||
struct lfs_ctz { |
|||
lfs_block_t head; |
|||
lfs_size_t size; |
|||
} ctz; |
|||
|
|||
uint32_t flags; |
|||
lfs_off_t pos; |
|||
lfs_block_t block; |
|||
lfs_off_t off; |
|||
lfs_cache_t cache; |
|||
|
|||
const struct lfs_file_config *cfg; |
|||
} lfs_file_t; |
|||
|
|||
typedef struct lfs_superblock { |
|||
uint32_t version; |
|||
lfs_size_t block_size; |
|||
lfs_size_t block_count; |
|||
lfs_size_t name_max; |
|||
lfs_size_t file_max; |
|||
lfs_size_t attr_max; |
|||
} lfs_superblock_t; |
|||
|
|||
typedef struct lfs_gstate { |
|||
uint32_t tag; |
|||
lfs_block_t pair[2]; |
|||
} lfs_gstate_t; |
|||
|
|||
// The littlefs filesystem type
|
|||
typedef struct lfs { |
|||
lfs_cache_t rcache; |
|||
lfs_cache_t pcache; |
|||
|
|||
lfs_block_t root[2]; |
|||
struct lfs_mlist { |
|||
struct lfs_mlist *next; |
|||
uint16_t id; |
|||
uint8_t type; |
|||
lfs_mdir_t m; |
|||
} *mlist; |
|||
uint32_t seed; |
|||
|
|||
lfs_gstate_t gstate; |
|||
lfs_gstate_t gdisk; |
|||
lfs_gstate_t gdelta; |
|||
|
|||
struct lfs_lookahead { |
|||
lfs_block_t start; |
|||
lfs_block_t size; |
|||
lfs_block_t next; |
|||
lfs_block_t ckpoint; |
|||
uint8_t *buffer; |
|||
} lookahead; |
|||
|
|||
const struct lfs_config *cfg; |
|||
lfs_size_t block_count; |
|||
lfs_size_t name_max; |
|||
lfs_size_t file_max; |
|||
lfs_size_t attr_max; |
|||
lfs_size_t inline_max; |
|||
|
|||
#ifdef LFS_MIGRATE |
|||
struct lfs1 *lfs1; |
|||
#endif |
|||
} lfs_t; |
|||
|
|||
|
|||
/// Filesystem functions ///
|
|||
|
|||
#ifndef LFS_READONLY |
|||
// Format a block device with the littlefs
|
|||
//
|
|||
// Requires a littlefs object and config struct. This clobbers the littlefs
|
|||
// object, and does not leave the filesystem mounted. The config struct must
|
|||
// be zeroed for defaults and backwards compatibility.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_format(lfs_t *lfs, const struct lfs_config *config); |
|||
#endif |
|||
|
|||
// Mounts a littlefs
|
|||
//
|
|||
// Requires a littlefs object and config struct. Multiple filesystems
|
|||
// may be mounted simultaneously with multiple littlefs objects. Both
|
|||
// lfs and config must be allocated while mounted. The config struct must
|
|||
// be zeroed for defaults and backwards compatibility.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_mount(lfs_t *lfs, const struct lfs_config *config); |
|||
|
|||
// Unmounts a littlefs
|
|||
//
|
|||
// Does nothing besides releasing any allocated resources.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_unmount(lfs_t *lfs); |
|||
|
|||
/// General operations ///
|
|||
|
|||
#ifndef LFS_READONLY |
|||
// Removes a file or directory
|
|||
//
|
|||
// If removing a directory, the directory must be empty.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_remove(lfs_t *lfs, const char *path); |
|||
#endif |
|||
|
|||
#ifndef LFS_READONLY |
|||
// Rename or move a file or directory
|
|||
//
|
|||
// If the destination exists, it must match the source in type.
|
|||
// If the destination is a directory, the directory must be empty.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); |
|||
#endif |
|||
|
|||
// Find info about a file or directory
|
|||
//
|
|||
// Fills out the info structure, based on the specified file or directory.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); |
|||
|
|||
// Get a custom attribute
|
|||
//
|
|||
// Custom attributes are uniquely identified by an 8-bit type and limited
|
|||
// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than
|
|||
// the buffer, it will be padded with zeros. If the stored attribute is larger,
|
|||
// then it will be silently truncated. If no attribute is found, the error
|
|||
// LFS_ERR_NOATTR is returned and the buffer is filled with zeros.
|
|||
//
|
|||
// Returns the size of the attribute, or a negative error code on failure.
|
|||
// Note, the returned size is the size of the attribute on disk, irrespective
|
|||
// of the size of the buffer. This can be used to dynamically allocate a buffer
|
|||
// or check for existence.
|
|||
lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, |
|||
uint8_t type, void *buffer, lfs_size_t size); |
|||
|
|||
#ifndef LFS_READONLY |
|||
// Set custom attributes
|
|||
//
|
|||
// Custom attributes are uniquely identified by an 8-bit type and limited
|
|||
// to LFS_ATTR_MAX bytes. If an attribute is not found, it will be
|
|||
// implicitly created.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_setattr(lfs_t *lfs, const char *path, |
|||
uint8_t type, const void *buffer, lfs_size_t size); |
|||
#endif |
|||
|
|||
#ifndef LFS_READONLY |
|||
// Removes a custom attribute
|
|||
//
|
|||
// If an attribute is not found, nothing happens.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type); |
|||
#endif |
|||
|
|||
|
|||
/// File operations ///
|
|||
|
|||
#ifndef LFS_NO_MALLOC |
|||
// Open a file
|
|||
//
|
|||
// The mode that the file is opened in is determined by the flags, which
|
|||
// are values from the enum lfs_open_flags that are bitwise-ored together.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_file_open(lfs_t *lfs, lfs_file_t *file, |
|||
const char *path, int flags); |
|||
|
|||
// if LFS_NO_MALLOC is defined, lfs_file_open() will fail with LFS_ERR_NOMEM
|
|||
// thus use lfs_file_opencfg() with config.buffer set.
|
|||
#endif |
|||
|
|||
// Open a file with extra configuration
|
|||
//
|
|||
// The mode that the file is opened in is determined by the flags, which
|
|||
// are values from the enum lfs_open_flags that are bitwise-ored together.
|
|||
//
|
|||
// The config struct provides additional config options per file as described
|
|||
// above. The config struct must remain allocated while the file is open, and
|
|||
// the config struct must be zeroed for defaults and backwards compatibility.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, |
|||
const char *path, int flags, |
|||
const struct lfs_file_config *config); |
|||
|
|||
// Close a file
|
|||
//
|
|||
// Any pending writes are written out to storage as though
|
|||
// sync had been called and releases any allocated resources.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_file_close(lfs_t *lfs, lfs_file_t *file); |
|||
|
|||
// Synchronize a file on storage
|
|||
//
|
|||
// Any pending writes are written out to storage.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); |
|||
|
|||
// Read data from file
|
|||
//
|
|||
// Takes a buffer and size indicating where to store the read data.
|
|||
// Returns the number of bytes read, or a negative error code on failure.
|
|||
lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, |
|||
void *buffer, lfs_size_t size); |
|||
|
|||
#ifndef LFS_READONLY |
|||
// Write data to file
|
|||
//
|
|||
// Takes a buffer and size indicating the data to write. The file will not
|
|||
// actually be updated on the storage until either sync or close is called.
|
|||
//
|
|||
// Returns the number of bytes written, or a negative error code on failure.
|
|||
lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, |
|||
const void *buffer, lfs_size_t size); |
|||
#endif |
|||
|
|||
// Change the position of the file
|
|||
//
|
|||
// The change in position is determined by the offset and whence flag.
|
|||
// Returns the new position of the file, or a negative error code on failure.
|
|||
lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, |
|||
lfs_soff_t off, int whence); |
|||
|
|||
#ifndef LFS_READONLY |
|||
// Truncates the size of the file to the specified size
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); |
|||
#endif |
|||
|
|||
// Return the position of the file
|
|||
//
|
|||
// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)
|
|||
// Returns the position of the file, or a negative error code on failure.
|
|||
lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); |
|||
|
|||
// Change the position of the file to the beginning of the file
|
|||
//
|
|||
// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_SET)
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); |
|||
|
|||
// Return the size of the file
|
|||
//
|
|||
// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END)
|
|||
// Returns the size of the file, or a negative error code on failure.
|
|||
lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); |
|||
|
|||
|
|||
/// Directory operations ///
|
|||
|
|||
#ifndef LFS_READONLY |
|||
// Create a directory
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_mkdir(lfs_t *lfs, const char *path); |
|||
#endif |
|||
|
|||
// Open a directory
|
|||
//
|
|||
// Once open a directory can be used with read to iterate over files.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path); |
|||
|
|||
// Close a directory
|
|||
//
|
|||
// Releases any allocated resources.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir); |
|||
|
|||
// Read an entry in the directory
|
|||
//
|
|||
// Fills out the info structure, based on the specified file or directory.
|
|||
// Returns a positive value on success, 0 at the end of directory,
|
|||
// or a negative error code on failure.
|
|||
int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); |
|||
|
|||
// Change the position of the directory
|
|||
//
|
|||
// The new off must be a value previous returned from tell and specifies
|
|||
// an absolute offset in the directory seek.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off); |
|||
|
|||
// Return the position of the directory
|
|||
//
|
|||
// The returned offset is only meant to be consumed by seek and may not make
|
|||
// sense, but does indicate the current position in the directory iteration.
|
|||
//
|
|||
// Returns the position of the directory, or a negative error code on failure.
|
|||
lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); |
|||
|
|||
// Change the position of the directory to the beginning of the directory
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); |
|||
|
|||
|
|||
/// Filesystem-level filesystem operations
|
|||
|
|||
// Find on-disk info about the filesystem
|
|||
//
|
|||
// Fills out the fsinfo structure based on the filesystem found on-disk.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo); |
|||
|
|||
// Finds the current size of the filesystem
|
|||
//
|
|||
// Note: Result is best effort. If files share COW structures, the returned
|
|||
// size may be larger than the filesystem actually is.
|
|||
//
|
|||
// Returns the number of allocated blocks, or a negative error code on failure.
|
|||
lfs_ssize_t lfs_fs_size(lfs_t *lfs); |
|||
|
|||
// Traverse through all blocks in use by the filesystem
|
|||
//
|
|||
// The provided callback will be called with each block address that is
|
|||
// currently in use by the filesystem. This can be used to determine which
|
|||
// blocks are in use or how much of the storage is available.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); |
|||
|
|||
#ifndef LFS_READONLY |
|||
// Attempt to make the filesystem consistent and ready for writing
|
|||
//
|
|||
// Calling this function is not required, consistency will be implicitly
|
|||
// enforced on the first operation that writes to the filesystem, but this
|
|||
// function allows the work to be performed earlier and without other
|
|||
// filesystem changes.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_fs_mkconsistent(lfs_t *lfs); |
|||
#endif |
|||
|
|||
#ifndef LFS_READONLY |
|||
// Attempt any janitorial work
|
|||
//
|
|||
// This currently:
|
|||
// 1. Calls mkconsistent if not already consistent
|
|||
// 2. Compacts metadata > compact_thresh
|
|||
// 3. Populates the block allocator
|
|||
//
|
|||
// Though additional janitorial work may be added in the future.
|
|||
//
|
|||
// Calling this function is not required, but may allow the offloading of
|
|||
// expensive janitorial work to a less time-critical code path.
|
|||
//
|
|||
// Returns a negative error code on failure. Accomplishing nothing is not
|
|||
// an error.
|
|||
int lfs_fs_gc(lfs_t *lfs); |
|||
#endif |
|||
|
|||
#ifndef LFS_READONLY |
|||
// Grows the filesystem to a new size, updating the superblock with the new
|
|||
// block count.
|
|||
//
|
|||
// If LFS_SHRINKNONRELOCATING is defined, this function will also accept
|
|||
// block_counts smaller than the current configuration, after checking
|
|||
// that none of the blocks that are being removed are in use.
|
|||
// Note that littlefs's pseudorandom block allocation means that
|
|||
// this is very unlikely to work in the general case.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count); |
|||
#endif |
|||
|
|||
#ifndef LFS_READONLY |
|||
#ifdef LFS_MIGRATE |
|||
// Attempts to migrate a previous version of littlefs
|
|||
//
|
|||
// Behaves similarly to the lfs_format function. Attempts to mount
|
|||
// the previous version of littlefs and update the filesystem so it can be
|
|||
// mounted with the current version of littlefs.
|
|||
//
|
|||
// Requires a littlefs object and config struct. This clobbers the littlefs
|
|||
// object, and does not leave the filesystem mounted. The config struct must
|
|||
// be zeroed for defaults and backwards compatibility.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg); |
|||
#endif |
|||
#endif |
|||
|
|||
|
|||
#ifdef __cplusplus |
|||
} /* extern "C" */ |
|||
#endif |
|||
|
|||
#endif |
|||
@ -0,0 +1,202 @@ |
|||
#ifndef _LFS_CONFIG_H_ |
|||
#define _LFS_CONFIG_H_ |
|||
|
|||
#include "common.h" |
|||
|
|||
// System includes
|
|||
#include <stdint.h> |
|||
#include <stdbool.h> |
|||
#include <string.h> |
|||
#include <inttypes.h> |
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" |
|||
{ |
|||
#endif |
|||
// Macros, may be replaced by system specific wrappers. Arguments to these
|
|||
// macros must not have side-effects as the macros can be removed for a smaller
|
|||
// code footprint
|
|||
|
|||
// Logging functions
|
|||
#ifdef LFS_YES_TRACE |
|||
#define LFS_TRACE(fmt, ...) \ |
|||
RD_PRINTF("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) |
|||
#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") |
|||
#else |
|||
#define LFS_TRACE(...) |
|||
#endif |
|||
|
|||
#ifndef LFS_NO_DEBUG |
|||
#define LFS_DEBUG_(fmt, ...) \ |
|||
RD_PRINTF("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) |
|||
#define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") |
|||
#else |
|||
#define LFS_DEBUG(...) |
|||
#endif |
|||
|
|||
#ifndef LFS_NO_WARN |
|||
#define LFS_WARN_(fmt, ...) \ |
|||
RD_PRINTF("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) |
|||
#define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") |
|||
#else |
|||
#define LFS_WARN(...) |
|||
#endif |
|||
|
|||
#ifndef LFS_NO_ERROR |
|||
#define LFS_ERROR_(fmt, ...) \ |
|||
RD_PRINTF("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) |
|||
#define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") |
|||
#else |
|||
#define LFS_ERROR(...) |
|||
#endif |
|||
|
|||
// Runtime assertions
|
|||
#ifndef LFS_NO_ASSERT |
|||
#define LFS_ASSERT(test) assert(test) |
|||
#else |
|||
#define LFS_ASSERT(test) |
|||
#endif |
|||
|
|||
|
|||
// Builtin functions, these may be replaced by more efficient
|
|||
// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more
|
|||
// expensive basic C implementation for debugging purposes
|
|||
|
|||
// Min/max functions for unsigned 32-bit numbers
|
|||
static inline uint32_t lfs_max(uint32_t a, uint32_t b) { |
|||
return (a > b) ? a : b; |
|||
} |
|||
|
|||
static inline uint32_t lfs_min(uint32_t a, uint32_t b) { |
|||
return (a < b) ? a : b; |
|||
} |
|||
|
|||
// Align to nearest multiple of a size
|
|||
static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { |
|||
return a - (a % alignment); |
|||
} |
|||
|
|||
static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { |
|||
return lfs_aligndown(a + alignment-1, alignment); |
|||
} |
|||
|
|||
// Find the smallest power of 2 greater than or equal to a
|
|||
static inline uint32_t lfs_npw2(uint32_t a) { |
|||
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) |
|||
return 32 - __builtin_clz(a-1); |
|||
#else |
|||
uint32_t r = 0; |
|||
uint32_t s; |
|||
a -= 1; |
|||
s = (a > 0xffff) << 4; a >>= s; r |= s; |
|||
s = (a > 0xff ) << 3; a >>= s; r |= s; |
|||
s = (a > 0xf ) << 2; a >>= s; r |= s; |
|||
s = (a > 0x3 ) << 1; a >>= s; r |= s; |
|||
return (r | (a >> 1)) + 1; |
|||
#endif |
|||
} |
|||
|
|||
// Count the number of trailing binary zeros in a
|
|||
// lfs_ctz(0) may be undefined
|
|||
static inline uint32_t lfs_ctz(uint32_t a) { |
|||
#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) |
|||
return __builtin_ctz(a); |
|||
#else |
|||
return lfs_npw2((a & -a) + 1) - 1; |
|||
#endif |
|||
} |
|||
|
|||
// Count the number of binary ones in a
|
|||
static inline uint32_t lfs_popc(uint32_t a) { |
|||
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) |
|||
return __builtin_popcount(a); |
|||
#else |
|||
a = a - ((a >> 1) & 0x55555555); |
|||
a = (a & 0x33333333) + ((a >> 2) & 0x33333333); |
|||
return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; |
|||
#endif |
|||
} |
|||
|
|||
// Find the sequence comparison of a and b, this is the distance
|
|||
// between a and b ignoring overflow
|
|||
static inline int lfs_scmp(uint32_t a, uint32_t b) { |
|||
return (int)(unsigned)(a - b); |
|||
} |
|||
|
|||
// Convert between 32-bit little-endian and native order
|
|||
static inline uint32_t lfs_fromle32(uint32_t a) { |
|||
#if !defined(LFS_NO_INTRINSICS) && ( \ |
|||
(defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) |
|||
return a; |
|||
#elif !defined(LFS_NO_INTRINSICS) && ( \ |
|||
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) |
|||
return __builtin_bswap32(a); |
|||
#else |
|||
return (((uint8_t*)&a)[0] << 0) | |
|||
(((uint8_t*)&a)[1] << 8) | |
|||
(((uint8_t*)&a)[2] << 16) | |
|||
(((uint8_t*)&a)[3] << 24); |
|||
#endif |
|||
} |
|||
|
|||
static inline uint32_t lfs_tole32(uint32_t a) { |
|||
return lfs_fromle32(a); |
|||
} |
|||
|
|||
// Convert between 32-bit big-endian and native order
|
|||
static inline uint32_t lfs_frombe32(uint32_t a) { |
|||
#if !defined(LFS_NO_INTRINSICS) && ( \ |
|||
(defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) |
|||
return __builtin_bswap32(a); |
|||
#elif !defined(LFS_NO_INTRINSICS) && ( \ |
|||
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) |
|||
return a; |
|||
#else |
|||
return (((uint8_t*)&a)[0] << 24) | |
|||
(((uint8_t*)&a)[1] << 16) | |
|||
(((uint8_t*)&a)[2] << 8) | |
|||
(((uint8_t*)&a)[3] << 0); |
|||
#endif |
|||
} |
|||
|
|||
static inline uint32_t lfs_tobe32(uint32_t a) { |
|||
return lfs_frombe32(a); |
|||
} |
|||
|
|||
// Calculate CRC-32 with polynomial = 0x04c11db7
|
|||
uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); |
|||
|
|||
// Allocate memory, only used if buffers are not provided to littlefs
|
|||
// Note, memory must be 64-bit aligned
|
|||
static inline void *lfs_malloc(size_t size) { |
|||
#ifndef LFS_NO_MALLOC |
|||
return RD_MALLOC(size); |
|||
#else |
|||
(void)size; |
|||
return NULL; |
|||
#endif |
|||
} |
|||
|
|||
// Deallocate memory, only used if buffers are not provided to littlefs
|
|||
static inline void lfs_free(void *p) { |
|||
#ifndef LFS_NO_MALLOC |
|||
RD_FREE(p); |
|||
#else |
|||
(void)p; |
|||
#endif |
|||
} |
|||
|
|||
|
|||
#ifdef __cplusplus |
|||
} /* extern "C" */ |
|||
#endif |
|||
|
|||
#endif |
|||
@ -0,0 +1,82 @@ |
|||
/*
|
|||
* Block device emulated in a file |
|||
* |
|||
* Copyright (c) 2022, The littlefs authors. |
|||
* Copyright (c) 2017, Arm Limited. All rights reserved. |
|||
* SPDX-License-Identifier: BSD-3-Clause |
|||
*/ |
|||
#ifndef LFS_FILEBD_H |
|||
#define LFS_FILEBD_H |
|||
|
|||
#include "lfs.h" |
|||
#include "lfs_util.h" |
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" |
|||
{ |
|||
#endif |
|||
|
|||
|
|||
// Block device specific tracing
|
|||
#ifndef LFS_FILEBD_TRACE |
|||
#ifdef LFS_FILEBD_YES_TRACE |
|||
#define LFS_FILEBD_TRACE(...) LFS_TRACE(__VA_ARGS__) |
|||
#else |
|||
#define LFS_FILEBD_TRACE(...) |
|||
#endif |
|||
#endif |
|||
|
|||
// filebd config
|
|||
struct lfs_filebd_config { |
|||
// Minimum size of a read operation in bytes.
|
|||
lfs_size_t read_size; |
|||
|
|||
// Minimum size of a program operation in bytes.
|
|||
lfs_size_t prog_size; |
|||
|
|||
// Size of an erase operation in bytes.
|
|||
lfs_size_t erase_size; |
|||
|
|||
// Number of erase blocks on the device.
|
|||
lfs_size_t erase_count; |
|||
}; |
|||
|
|||
// filebd state
|
|||
typedef struct lfs_filebd { |
|||
int fd; |
|||
const struct lfs_filebd_config *cfg; |
|||
} lfs_filebd_t; |
|||
|
|||
|
|||
// Create a file block device
|
|||
int lfs_filebd_create(const struct lfs_config *cfg, const char *path, |
|||
const struct lfs_filebd_config *bdcfg); |
|||
|
|||
// Clean up memory associated with block device
|
|||
int lfs_filebd_destroy(const struct lfs_config *cfg); |
|||
|
|||
// Read a block
|
|||
int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block, |
|||
lfs_off_t off, void *buffer, lfs_size_t size); |
|||
|
|||
// Program a block
|
|||
//
|
|||
// The block must have previously been erased.
|
|||
int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block, |
|||
lfs_off_t off, const void *buffer, lfs_size_t size); |
|||
|
|||
// Erase a block
|
|||
//
|
|||
// A block must be erased before being programmed. The
|
|||
// state of an erased block is undefined.
|
|||
int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block); |
|||
|
|||
// Sync the block device
|
|||
int lfs_filebd_sync(const struct lfs_config *cfg); |
|||
|
|||
|
|||
#ifdef __cplusplus |
|||
} /* extern "C" */ |
|||
#endif |
|||
|
|||
#endif |
|||
@ -0,0 +1,99 @@ |
|||
/*
|
|||
* LittleFS block device adapter for W25QXX SPI Flash |
|||
* |
|||
* Maps littlefs read/prog/erase/sync callbacks to the W25QXX driver: |
|||
* read -> W25QXX_Read |
|||
* prog -> W25QXX_Write_NoCheck (littleFS guarantees erase-before-prog) |
|||
* erase -> W25QXX_Erase_Sector (4 KiB sector) |
|||
* sync -> W25QXX_Wait_Busy |
|||
*/ |
|||
#ifndef LFS_FLASHBD_H |
|||
#define LFS_FLASHBD_H |
|||
|
|||
#include "lfs.h" |
|||
#include "lfs_util.h" |
|||
#include <stdint.h> |
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
/* W25QXX 扇区大小固定为 4096 字节 */ |
|||
#define LFS_FLASHBD_SECTOR_SIZE 4096 |
|||
|
|||
/* Flash 块设备配置 */ |
|||
struct lfs_flashbd_config { |
|||
/* Flash 起始偏移地址(字节),必须 4096 对齐 */ |
|||
uint32_t base_addr; |
|||
|
|||
/* 使用的块(扇区)数量 */ |
|||
uint32_t block_count; |
|||
}; |
|||
|
|||
/* Flash 块设备状态 */ |
|||
typedef struct lfs_flashbd { |
|||
const struct lfs_flashbd_config *cfg; |
|||
} lfs_flashbd_t; |
|||
|
|||
|
|||
/* 初始化块设备(内部调用 W25QXX_Init) */ |
|||
int lfs_flashbd_create(const struct lfs_config *cfg, |
|||
const struct lfs_flashbd_config *bdcfg); |
|||
|
|||
/* 销毁块设备(当前为空操作) */ |
|||
int lfs_flashbd_destroy(const struct lfs_config *cfg); |
|||
|
|||
/* 读取一个块的区域 */ |
|||
int lfs_flashbd_read(const struct lfs_config *cfg, lfs_block_t block, |
|||
lfs_off_t off, void *buffer, lfs_size_t size); |
|||
|
|||
/* 编程一个块的区域(块必须已擦除) */ |
|||
int lfs_flashbd_prog(const struct lfs_config *cfg, lfs_block_t block, |
|||
lfs_off_t off, const void *buffer, lfs_size_t size); |
|||
|
|||
/* 擦除一个块(4 KiB 扇区) */ |
|||
int lfs_flashbd_erase(const struct lfs_config *cfg, lfs_block_t block); |
|||
|
|||
/* 同步块设备(等待 Flash 空闲) */ |
|||
int lfs_flashbd_sync(const struct lfs_config *cfg); |
|||
|
|||
|
|||
/*
|
|||
* 便捷函数:用 W25QXX 默认参数填充 lfs_config 和 flashbd 配置。 |
|||
* |
|||
* 调用者需提供 lfs_config、lfs_flashbd_t、lfs_flashbd_config 结构体 |
|||
* (可以是在栈上或静态分配),以及 Flash 起始地址和使用的块数量。 |
|||
* |
|||
* 如果 read_buffer / prog_buffer / lookahead_buffer 为 NULL, |
|||
* littleFS 会通过 lfs_malloc 动态分配缓存。 |
|||
* 静态 lookahead_buffer 大小需 >= lookahead_size = ceil(block_count / 8) 字节。 |
|||
* |
|||
* 典型用法(W25Q80,从地址 0 开始使用全部 1 MB): |
|||
* |
|||
* lfs_t lfs; |
|||
* lfs_flashbd_t bd; |
|||
* struct lfs_flashbd_config bd_cfg; |
|||
* struct lfs_config cfg; |
|||
* uint8_t read_buf[256], prog_buf[256], lookahead_buf[32]; // 256 块 -> 32 字节
|
|||
* |
|||
* lfs_flashbd_default_config(&cfg, &bd, &bd_cfg, 0, 256, |
|||
* read_buf, prog_buf, lookahead_buf); |
|||
* lfs_format(&lfs, &cfg); // 首次使用需格式化
|
|||
* lfs_mount(&lfs, &cfg); |
|||
*/ |
|||
void lfs_flashbd_default_config( |
|||
struct lfs_config *cfg, |
|||
lfs_flashbd_t *bd, |
|||
struct lfs_flashbd_config *bdcfg, |
|||
uint32_t base_addr, |
|||
uint32_t block_count, |
|||
void *read_buffer, |
|||
void *prog_buffer, |
|||
void *lookahead_buffer); |
|||
|
|||
|
|||
#ifdef __cplusplus |
|||
} /* extern "C" */ |
|||
#endif |
|||
|
|||
#endif /* LFS_FLASHBD_H */ |
|||
@ -0,0 +1,276 @@ |
|||
/*
|
|||
* lfs utility functions |
|||
* |
|||
* Copyright (c) 2022, The littlefs authors. |
|||
* Copyright (c) 2017, Arm Limited. All rights reserved. |
|||
* SPDX-License-Identifier: BSD-3-Clause |
|||
*/ |
|||
#ifndef LFS_UTIL_H |
|||
#define LFS_UTIL_H |
|||
|
|||
#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) |
|||
#define LFS_STRINGIZE2(x) #x |
|||
|
|||
// Users can override lfs_util.h with their own configuration by defining
|
|||
// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h).
|
|||
//
|
|||
// If LFS_CONFIG is used, none of the default utils will be emitted and must be
|
|||
// provided by the config file. To start, I would suggest copying lfs_util.h
|
|||
// and modifying as needed.
|
|||
|
|||
#define LFS_CONFIG lfs_config.h |
|||
|
|||
#ifdef LFS_CONFIG |
|||
#include LFS_STRINGIZE(LFS_CONFIG) |
|||
#else |
|||
|
|||
// Alternatively, users can provide a header file which defines
|
|||
// macros and other things consumed by littlefs.
|
|||
//
|
|||
// For example, provide my_defines.h, which contains
|
|||
// something like:
|
|||
//
|
|||
// #include <stddef.h>
|
|||
// extern void *my_malloc(size_t sz);
|
|||
// #define LFS_MALLOC(sz) my_malloc(sz)
|
|||
//
|
|||
// And build littlefs with the header by defining LFS_DEFINES.
|
|||
// (-DLFS_DEFINES=my_defines.h)
|
|||
|
|||
#ifdef LFS_DEFINES |
|||
#include LFS_STRINGIZE(LFS_DEFINES) |
|||
#endif |
|||
|
|||
// System includes
|
|||
#include <stdint.h> |
|||
#include <stdbool.h> |
|||
#include <string.h> |
|||
#include <inttypes.h> |
|||
|
|||
#ifndef LFS_NO_MALLOC |
|||
#include <stdlib.h> |
|||
#endif |
|||
#ifndef LFS_NO_ASSERT |
|||
#include <assert.h> |
|||
#endif |
|||
#if !defined(LFS_NO_DEBUG) || \ |
|||
!defined(LFS_NO_WARN) || \ |
|||
!defined(LFS_NO_ERROR) || \ |
|||
defined(LFS_YES_TRACE) |
|||
#include <stdio.h> |
|||
#endif |
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" |
|||
{ |
|||
#endif |
|||
|
|||
|
|||
// Macros, may be replaced by system specific wrappers. Arguments to these
|
|||
// macros must not have side-effects as the macros can be removed for a smaller
|
|||
// code footprint
|
|||
|
|||
// Logging functions
|
|||
#ifndef LFS_TRACE |
|||
#ifdef LFS_YES_TRACE |
|||
#define LFS_TRACE_(fmt, ...) \ |
|||
printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) |
|||
#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") |
|||
#else |
|||
#define LFS_TRACE(...) |
|||
#endif |
|||
#endif |
|||
|
|||
#ifndef LFS_DEBUG |
|||
#ifndef LFS_NO_DEBUG |
|||
#define LFS_DEBUG_(fmt, ...) \ |
|||
printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) |
|||
#define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") |
|||
#else |
|||
#define LFS_DEBUG(...) |
|||
#endif |
|||
#endif |
|||
|
|||
#ifndef LFS_WARN |
|||
#ifndef LFS_NO_WARN |
|||
#define LFS_WARN_(fmt, ...) \ |
|||
printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) |
|||
#define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") |
|||
#else |
|||
#define LFS_WARN(...) |
|||
#endif |
|||
#endif |
|||
|
|||
#ifndef LFS_ERROR |
|||
#ifndef LFS_NO_ERROR |
|||
#define LFS_ERROR_(fmt, ...) \ |
|||
printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) |
|||
#define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") |
|||
#else |
|||
#define LFS_ERROR(...) |
|||
#endif |
|||
#endif |
|||
|
|||
// Runtime assertions
|
|||
#ifndef LFS_ASSERT |
|||
#ifndef LFS_NO_ASSERT |
|||
#define LFS_ASSERT(test) assert(test) |
|||
#else |
|||
#define LFS_ASSERT(test) |
|||
#endif |
|||
#endif |
|||
|
|||
|
|||
// Builtin functions, these may be replaced by more efficient
|
|||
// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more
|
|||
// expensive basic C implementation for debugging purposes
|
|||
|
|||
// Min/max functions for unsigned 32-bit numbers
|
|||
static inline uint32_t lfs_max(uint32_t a, uint32_t b) { |
|||
return (a > b) ? a : b; |
|||
} |
|||
|
|||
static inline uint32_t lfs_min(uint32_t a, uint32_t b) { |
|||
return (a < b) ? a : b; |
|||
} |
|||
|
|||
// Align to nearest multiple of a size
|
|||
static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { |
|||
return a - (a % alignment); |
|||
} |
|||
|
|||
static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { |
|||
return lfs_aligndown(a + alignment-1, alignment); |
|||
} |
|||
|
|||
// Find the smallest power of 2 greater than or equal to a
|
|||
static inline uint32_t lfs_npw2(uint32_t a) { |
|||
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) |
|||
return 32 - __builtin_clz(a-1); |
|||
#else |
|||
uint32_t r = 0; |
|||
uint32_t s; |
|||
a -= 1; |
|||
s = (a > 0xffff) << 4; a >>= s; r |= s; |
|||
s = (a > 0xff ) << 3; a >>= s; r |= s; |
|||
s = (a > 0xf ) << 2; a >>= s; r |= s; |
|||
s = (a > 0x3 ) << 1; a >>= s; r |= s; |
|||
return (r | (a >> 1)) + 1; |
|||
#endif |
|||
} |
|||
|
|||
// Count the number of trailing binary zeros in a
|
|||
// lfs_ctz(0) may be undefined
|
|||
static inline uint32_t lfs_ctz(uint32_t a) { |
|||
#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) |
|||
return __builtin_ctz(a); |
|||
#else |
|||
return lfs_npw2((a & -a) + 1) - 1; |
|||
#endif |
|||
} |
|||
|
|||
// Count the number of binary ones in a
|
|||
static inline uint32_t lfs_popc(uint32_t a) { |
|||
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) |
|||
return __builtin_popcount(a); |
|||
#else |
|||
a = a - ((a >> 1) & 0x55555555); |
|||
a = (a & 0x33333333) + ((a >> 2) & 0x33333333); |
|||
return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; |
|||
#endif |
|||
} |
|||
|
|||
// Find the sequence comparison of a and b, this is the distance
|
|||
// between a and b ignoring overflow
|
|||
static inline int lfs_scmp(uint32_t a, uint32_t b) { |
|||
return (int)(unsigned)(a - b); |
|||
} |
|||
|
|||
// Convert between 32-bit little-endian and native order
|
|||
static inline uint32_t lfs_fromle32(uint32_t a) { |
|||
#if (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) |
|||
return a; |
|||
#elif !defined(LFS_NO_INTRINSICS) && ( \ |
|||
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) |
|||
return __builtin_bswap32(a); |
|||
#else |
|||
return ((uint32_t)((uint8_t*)&a)[0] << 0) | |
|||
((uint32_t)((uint8_t*)&a)[1] << 8) | |
|||
((uint32_t)((uint8_t*)&a)[2] << 16) | |
|||
((uint32_t)((uint8_t*)&a)[3] << 24); |
|||
#endif |
|||
} |
|||
|
|||
static inline uint32_t lfs_tole32(uint32_t a) { |
|||
return lfs_fromle32(a); |
|||
} |
|||
|
|||
// Convert between 32-bit big-endian and native order
|
|||
static inline uint32_t lfs_frombe32(uint32_t a) { |
|||
#if !defined(LFS_NO_INTRINSICS) && ( \ |
|||
(defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) |
|||
return __builtin_bswap32(a); |
|||
#elif (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) |
|||
return a; |
|||
#else |
|||
return ((uint32_t)((uint8_t*)&a)[0] << 24) | |
|||
((uint32_t)((uint8_t*)&a)[1] << 16) | |
|||
((uint32_t)((uint8_t*)&a)[2] << 8) | |
|||
((uint32_t)((uint8_t*)&a)[3] << 0); |
|||
#endif |
|||
} |
|||
|
|||
static inline uint32_t lfs_tobe32(uint32_t a) { |
|||
return lfs_frombe32(a); |
|||
} |
|||
|
|||
// Calculate CRC-32 with polynomial = 0x04c11db7
|
|||
#ifdef LFS_CRC |
|||
static inline uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { |
|||
return LFS_CRC(crc, buffer, size); |
|||
} |
|||
#else |
|||
uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); |
|||
#endif |
|||
|
|||
// Allocate memory, only used if buffers are not provided to littlefs
|
|||
//
|
|||
// littlefs current has no alignment requirements, as it only allocates
|
|||
// byte-level buffers.
|
|||
static inline void *lfs_malloc(size_t size) { |
|||
#if defined(LFS_MALLOC) |
|||
return LFS_MALLOC(size); |
|||
#elif !defined(LFS_NO_MALLOC) |
|||
return malloc(size); |
|||
#else |
|||
(void)size; |
|||
return NULL; |
|||
#endif |
|||
} |
|||
|
|||
// Deallocate memory, only used if buffers are not provided to littlefs
|
|||
static inline void lfs_free(void *p) { |
|||
#if defined(LFS_FREE) |
|||
LFS_FREE(p); |
|||
#elif !defined(LFS_NO_MALLOC) |
|||
free(p); |
|||
#else |
|||
(void)p; |
|||
#endif |
|||
} |
|||
|
|||
|
|||
#ifdef __cplusplus |
|||
} /* extern "C" */ |
|||
#endif |
|||
|
|||
#endif |
|||
#endif |
|||
File diff suppressed because it is too large
@ -0,0 +1,20 @@ |
|||
#include "lfs_util.h" |
|||
|
|||
// Software CRC implementation with small lookup table
|
|||
uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { |
|||
static const uint32_t rtable[16] = { |
|||
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, |
|||
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, |
|||
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, |
|||
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, |
|||
}; |
|||
|
|||
const uint8_t *data = buffer; |
|||
|
|||
for (size_t i = 0; i < size; i++) { |
|||
crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; |
|||
crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; |
|||
} |
|||
|
|||
return crc; |
|||
} |
|||
@ -0,0 +1,168 @@ |
|||
/*
|
|||
* Block device emulated in a file |
|||
* |
|||
* Copyright (c) 2022, The littlefs authors. |
|||
* Copyright (c) 2017, Arm Limited. All rights reserved. |
|||
* SPDX-License-Identifier: BSD-3-Clause |
|||
*/ |
|||
#include "lfs_filebd.h" |
|||
|
|||
#include <fcntl.h> |
|||
#include <unistd.h> |
|||
#include <errno.h> |
|||
|
|||
#ifdef _WIN32 |
|||
#include <windows.h> |
|||
#endif |
|||
|
|||
int lfs_filebd_create(const struct lfs_config *cfg, const char *path, |
|||
const struct lfs_filebd_config *bdcfg) { |
|||
LFS_FILEBD_TRACE("lfs_filebd_create(%p {.context=%p, " |
|||
".read=%p, .prog=%p, .erase=%p, .sync=%p}, " |
|||
"\"%s\", " |
|||
"%p {.read_size=%"PRIu32", .prog_size=%"PRIu32", " |
|||
".erase_size=%"PRIu32", .erase_count=%"PRIu32"})", |
|||
(void*)cfg, cfg->context, |
|||
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, |
|||
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, |
|||
path, |
|||
(void*)bdcfg, |
|||
bdcfg->read_size, bdcfg->prog_size, bdcfg->erase_size, |
|||
bdcfg->erase_count); |
|||
lfs_filebd_t *bd = cfg->context; |
|||
bd->cfg = bdcfg; |
|||
|
|||
// open file
|
|||
#ifdef _WIN32 |
|||
bd->fd = open(path, O_RDWR | O_CREAT | O_BINARY, 0666); |
|||
#else |
|||
bd->fd = open(path, O_RDWR | O_CREAT, 0666); |
|||
#endif |
|||
|
|||
if (bd->fd < 0) { |
|||
int err = -errno; |
|||
LFS_FILEBD_TRACE("lfs_filebd_create -> %d", err); |
|||
return err; |
|||
} |
|||
|
|||
LFS_FILEBD_TRACE("lfs_filebd_create -> %d", 0); |
|||
return 0; |
|||
} |
|||
|
|||
int lfs_filebd_destroy(const struct lfs_config *cfg) { |
|||
LFS_FILEBD_TRACE("lfs_filebd_destroy(%p)", (void*)cfg); |
|||
lfs_filebd_t *bd = cfg->context; |
|||
int err = close(bd->fd); |
|||
if (err < 0) { |
|||
err = -errno; |
|||
LFS_FILEBD_TRACE("lfs_filebd_destroy -> %d", err); |
|||
return err; |
|||
} |
|||
LFS_FILEBD_TRACE("lfs_filebd_destroy -> %d", 0); |
|||
return 0; |
|||
} |
|||
|
|||
int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block, |
|||
lfs_off_t off, void *buffer, lfs_size_t size) { |
|||
LFS_FILEBD_TRACE("lfs_filebd_read(%p, " |
|||
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", |
|||
(void*)cfg, block, off, buffer, size); |
|||
lfs_filebd_t *bd = cfg->context; |
|||
|
|||
// check if read is valid
|
|||
LFS_ASSERT(block < bd->cfg->erase_count); |
|||
LFS_ASSERT(off % bd->cfg->read_size == 0); |
|||
LFS_ASSERT(size % bd->cfg->read_size == 0); |
|||
LFS_ASSERT(off+size <= bd->cfg->erase_size); |
|||
|
|||
// zero for reproducibility (in case file is truncated)
|
|||
memset(buffer, 0, size); |
|||
|
|||
// read
|
|||
off_t res1 = lseek(bd->fd, |
|||
(off_t)block*bd->cfg->erase_size + (off_t)off, SEEK_SET); |
|||
if (res1 < 0) { |
|||
int err = -errno; |
|||
LFS_FILEBD_TRACE("lfs_filebd_read -> %d", err); |
|||
return err; |
|||
} |
|||
|
|||
ssize_t res2 = read(bd->fd, buffer, size); |
|||
if (res2 < 0) { |
|||
int err = -errno; |
|||
LFS_FILEBD_TRACE("lfs_filebd_read -> %d", err); |
|||
return err; |
|||
} |
|||
|
|||
LFS_FILEBD_TRACE("lfs_filebd_read -> %d", 0); |
|||
return 0; |
|||
} |
|||
|
|||
int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block, |
|||
lfs_off_t off, const void *buffer, lfs_size_t size) { |
|||
LFS_FILEBD_TRACE("lfs_filebd_prog(%p, " |
|||
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", |
|||
(void*)cfg, block, off, buffer, size); |
|||
lfs_filebd_t *bd = cfg->context; |
|||
|
|||
// check if write is valid
|
|||
LFS_ASSERT(block < bd->cfg->erase_count); |
|||
LFS_ASSERT(off % bd->cfg->prog_size == 0); |
|||
LFS_ASSERT(size % bd->cfg->prog_size == 0); |
|||
LFS_ASSERT(off+size <= bd->cfg->erase_size); |
|||
|
|||
// program data
|
|||
off_t res1 = lseek(bd->fd, |
|||
(off_t)block*bd->cfg->erase_size + (off_t)off, SEEK_SET); |
|||
if (res1 < 0) { |
|||
int err = -errno; |
|||
LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err); |
|||
return err; |
|||
} |
|||
|
|||
ssize_t res2 = write(bd->fd, buffer, size); |
|||
if (res2 < 0) { |
|||
int err = -errno; |
|||
LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err); |
|||
return err; |
|||
} |
|||
|
|||
LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", 0); |
|||
return 0; |
|||
} |
|||
|
|||
int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) { |
|||
LFS_FILEBD_TRACE("lfs_filebd_erase(%p, 0x%"PRIx32" (%"PRIu32"))", |
|||
(void*)cfg, block, ((lfs_filebd_t*)cfg->context)->cfg->erase_size); |
|||
lfs_filebd_t *bd = cfg->context; |
|||
|
|||
// check if erase is valid
|
|||
LFS_ASSERT(block < bd->cfg->erase_count); |
|||
|
|||
// erase is a noop
|
|||
(void)block; |
|||
(void)bd; |
|||
|
|||
LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", 0); |
|||
return 0; |
|||
} |
|||
|
|||
int lfs_filebd_sync(const struct lfs_config *cfg) { |
|||
LFS_FILEBD_TRACE("lfs_filebd_sync(%p)", (void*)cfg); |
|||
|
|||
// file sync
|
|||
lfs_filebd_t *bd = cfg->context; |
|||
#ifdef _WIN32 |
|||
int err = FlushFileBuffers((HANDLE) _get_osfhandle(bd->fd)) ? 0 : -1; |
|||
#else |
|||
int err = fsync(bd->fd); |
|||
#endif |
|||
if (err) { |
|||
err = -errno; |
|||
LFS_FILEBD_TRACE("lfs_filebd_sync -> %d", 0); |
|||
return err; |
|||
} |
|||
|
|||
LFS_FILEBD_TRACE("lfs_filebd_sync -> %d", 0); |
|||
return 0; |
|||
} |
|||
@ -0,0 +1,149 @@ |
|||
/*
|
|||
* LittleFS block device adapter for W25QXX SPI Flash |
|||
* |
|||
* Maps littlefs read/prog/erase/sync callbacks to the W25QXX driver: |
|||
* read -> W25QXX_Read |
|||
* prog -> W25QXX_Write_NoCheck (littleFS guarantees erase-before-prog) |
|||
* erase -> W25QXX_Erase_Sector (4 KiB sector) |
|||
* sync -> W25QXX_Wait_Busy |
|||
*/ |
|||
#include "lfs_flashbd.h" |
|||
#include "../../bspMCU/include/w25qxx.h" |
|||
#include <string.h> |
|||
|
|||
int lfs_flashbd_create(const struct lfs_config *cfg, |
|||
const struct lfs_flashbd_config *bdcfg) { |
|||
lfs_flashbd_t *bd = (lfs_flashbd_t *)cfg->context; |
|||
|
|||
/* 参数校验 */ |
|||
if (bdcfg->base_addr % LFS_FLASHBD_SECTOR_SIZE != 0) { |
|||
return LFS_ERR_INVAL; |
|||
} |
|||
if (bdcfg->block_count == 0) { |
|||
return LFS_ERR_INVAL; |
|||
} |
|||
|
|||
bd->cfg = bdcfg; |
|||
|
|||
/* 初始化 W25QXX Flash */ |
|||
W25QXX_Init(); |
|||
|
|||
return LFS_ERR_OK; |
|||
} |
|||
|
|||
int lfs_flashbd_destroy(const struct lfs_config *cfg) { |
|||
(void)cfg; |
|||
return LFS_ERR_OK; |
|||
} |
|||
|
|||
int lfs_flashbd_read(const struct lfs_config *cfg, lfs_block_t block, |
|||
lfs_off_t off, void *buffer, lfs_size_t size) { |
|||
lfs_flashbd_t *bd = (lfs_flashbd_t *)cfg->context; |
|||
|
|||
LFS_ASSERT(block < bd->cfg->block_count); |
|||
LFS_ASSERT(off + size <= cfg->block_size); |
|||
|
|||
uint32_t addr = bd->cfg->base_addr |
|||
+ (uint32_t)block * cfg->block_size |
|||
+ (uint32_t)off; |
|||
|
|||
W25QXX_Read((uint8_t *)buffer, addr, (uint16_t)size); |
|||
|
|||
return LFS_ERR_OK; |
|||
} |
|||
|
|||
int lfs_flashbd_prog(const struct lfs_config *cfg, lfs_block_t block, |
|||
lfs_off_t off, const void *buffer, lfs_size_t size) { |
|||
lfs_flashbd_t *bd = (lfs_flashbd_t *)cfg->context; |
|||
|
|||
LFS_ASSERT(block < bd->cfg->block_count); |
|||
LFS_ASSERT(off + size <= cfg->block_size); |
|||
|
|||
uint32_t addr = bd->cfg->base_addr |
|||
+ (uint32_t)block * cfg->block_size |
|||
+ (uint32_t)off; |
|||
|
|||
/*
|
|||
* W25QXX_Write_NoCheck 的第一个参数是 uint8_t*(非 const), |
|||
* 但该函数只读取 buffer 内容,不会修改,故安全地去除 const。 |
|||
* littleFS 保证写入前块已擦除,因此无需带擦除的 W25QXX_Write。 |
|||
*/ |
|||
W25QXX_Write_NoCheck((uint8_t *)(uintptr_t)buffer, addr, (uint16_t)size); |
|||
|
|||
return LFS_ERR_OK; |
|||
} |
|||
|
|||
int lfs_flashbd_erase(const struct lfs_config *cfg, lfs_block_t block) { |
|||
lfs_flashbd_t *bd = (lfs_flashbd_t *)cfg->context; |
|||
|
|||
LFS_ASSERT(block < bd->cfg->block_count); |
|||
|
|||
uint32_t addr = bd->cfg->base_addr |
|||
+ (uint32_t)block * cfg->block_size; |
|||
|
|||
/*
|
|||
* W25QXX_Erase_Sector 的参数是扇区号(内部会乘以 4096), |
|||
* 所以传入字节地址除以扇区大小。 |
|||
*/ |
|||
W25QXX_Erase_Sector(addr / LFS_FLASHBD_SECTOR_SIZE); |
|||
|
|||
return LFS_ERR_OK; |
|||
} |
|||
|
|||
int lfs_flashbd_sync(const struct lfs_config *cfg) { |
|||
(void)cfg; |
|||
W25QXX_Wait_Busy(); |
|||
return LFS_ERR_OK; |
|||
} |
|||
|
|||
void lfs_flashbd_default_config( |
|||
struct lfs_config *cfg, |
|||
lfs_flashbd_t *bd, |
|||
struct lfs_flashbd_config *bdcfg, |
|||
uint32_t base_addr, |
|||
uint32_t block_count, |
|||
void *read_buffer, |
|||
void *prog_buffer, |
|||
void *lookahead_buffer) { |
|||
memset(cfg, 0, sizeof(*cfg)); |
|||
|
|||
/* flashbd 配置 */ |
|||
bdcfg->base_addr = base_addr; |
|||
bdcfg->block_count = block_count; |
|||
|
|||
bd->cfg = bdcfg; |
|||
|
|||
/* 回调函数 */ |
|||
cfg->context = bd; |
|||
cfg->read = lfs_flashbd_read; |
|||
cfg->prog = lfs_flashbd_prog; |
|||
cfg->erase = lfs_flashbd_erase; |
|||
cfg->sync = lfs_flashbd_sync; |
|||
|
|||
/* 块设备参数 */ |
|||
cfg->read_size = 256; /* W25QXX 页大小,读取可任意长度 */ |
|||
cfg->prog_size = 256; /* W25QXX 页编程大小 */ |
|||
cfg->block_size = LFS_FLASHBD_SECTOR_SIZE; /* 4096 字节扇区 */ |
|||
cfg->block_count = block_count; |
|||
cfg->block_cycles = 500; /* 磨损均衡建议值 */ |
|||
cfg->cache_size = 256; /* 缓存大小 = prog_size */ |
|||
|
|||
/*
|
|||
* lookahead 缓存是一个位图,每字节跟踪 8 个块。按块数自动计算, |
|||
* 保证一次分配扫描即可覆盖全部块(块数向上取整到字节)。 |
|||
* RAM 消耗 = ceil(block_count / 8) 字节,例如 3072 块 -> 384 字节。 |
|||
* 注意:若调用者提供静态 lookahead_buffer,其大小必须 >= 该值。 |
|||
*/ |
|||
cfg->lookahead_size = (block_count + 7) / 8; |
|||
cfg->compact_thresh = 0; /* 使用默认值 */ |
|||
|
|||
/* 静态缓冲区,NULL 则由 littleFS 动态分配 */ |
|||
cfg->read_buffer = read_buffer; |
|||
cfg->prog_buffer = prog_buffer; |
|||
cfg->lookahead_buffer = lookahead_buffer; |
|||
|
|||
/* 使用默认值 */ |
|||
cfg->name_max = 0; |
|||
cfg->file_max = 0; |
|||
cfg->attr_max = 0; |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
/*
|
|||
* lfs util functions |
|||
* |
|||
* Copyright (c) 2022, The littlefs authors. |
|||
* Copyright (c) 2017, Arm Limited. All rights reserved. |
|||
* SPDX-License-Identifier: BSD-3-Clause |
|||
*/ |
|||
#include "lfs_util.h" |
|||
|
|||
// Only compile if user does not provide custom config
|
|||
#ifndef LFS_CONFIG |
|||
|
|||
|
|||
// If user provides their own CRC impl we don't need this
|
|||
#ifndef LFS_CRC |
|||
// Software CRC implementation with small lookup table
|
|||
uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { |
|||
static const uint32_t rtable[16] = { |
|||
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, |
|||
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, |
|||
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, |
|||
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, |
|||
}; |
|||
|
|||
const uint8_t *data = buffer; |
|||
|
|||
for (size_t i = 0; i < size; i++) { |
|||
crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; |
|||
crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; |
|||
} |
|||
|
|||
return crc; |
|||
} |
|||
#endif |
|||
|
|||
|
|||
#endif |
|||
Loading…
Reference in new issue