ВходНаше всё Теги codebook 无线电组件 Поиск Опросы Закон Вторник
21 января
1490557 Топик полностью
klen (12.01.2025 23:39, просмотров: 204) ответил framer на Надел зеленые очки и повдуплял. Только у меня в очках не тот оттенок зеленого :) Интересно было бы поганять телегу под линухом. Только нет интерфейса для io_t и надо вылускивать, что там внутри. Видно там все на свалку и вызовы библиотечных функций и переменные и буфера. Вот если бы был интерфейс можно было бы сделать имплементацию под линх и погонять на тестах и проверить санитайзерами. Да и функции драйвера можно вынести в класс (если понял правильно то это
функции драйвера как раз и вынесены - в параметр шаблона - класс io_t , а сам io_t тоже шаблонный и имеет параметр - класс драйвер специфической флехи. 
#pragma once

#include "ch32v30x.h"

namespace storage
{



struct io_t
{
	void (*init)() ;
	unsigned char (*xfer)(const uint8_t data) ;
	void (*cs_low)() ;
	void (*cs_high)() ;
	void (*delay_ms)(const size_t val) ;
};


template <typename io_t>
class w25qxx_tt

{



public:


/* Winbond SPIFalsh ID */
#define W25Q80                   0xef13
#define W25Q16                   0xef14
#define W25Q32                   0xef15
#define W25Q64                   0xef16
#define W25Q128                  0xef17
#define W25Q256                  0xef18
#define W25Q512                  0xef19
#define W25Q01                   0xef20
#define W25Q02                   0xef21


enum class command_t : uint8_t
  {
	 ManufactDeviceID =  0x90,

	 ReadStatusReg    =  0x05,

	 WriteEnable      =  0x06,
     WriteDisable     =  0x04,

     PowerDown        =  0xB9,
     ReleasePowerDown =  0xAB,

	 SectorErase      =  0x20,
	 SectorErase32addr=  0x21,

	 PageProgram      =  0x02,
	 PageProgram32addr=  0x12,

	 ReadData         =  0x03,
	 ReadData32addr   =  0x13,

	 ChipErase        =  0xC7,


	 Enter32addr      = 0xb7,
	 Exit32addr       = 0xe9,
  };


static constexpr string_view part_name[] =
{
 /* 128Kb */  "w25q10", //
 /* 256Kb */  "w25q2x", //
 /* 512Kb */  "w25q4x", //
 /*   1Mb */  "w25q8x", //
 /*   2Mb */  "w25q16", //
 /*   4Mb */  "w25q32", //
 /*   8Mb */  "w25q64", //    22
 /*  16Mb */  "w25q128",//
 /*  32Mb */  "w25q256",//
 /*  64Mb */  "w25q512",//
 /* 128Mb */  "w25q01", //
 /* 256Mb */  "w25q02", //
};

__ai__  w25qxx_tt()  noexcept { init(); }
__ai__  ~w25qxx_tt() noexcept {}

template <const command_t val>
__ais__ void command()
{
	io_t::cs_low();
	io_t::xfer( static_cast<uint8_t>(val));
	io_t::cs_high();
}

__ais__ void write_enable(void) { command<command_t::WriteEnable>(); }
__ais__ void write_disable(void){ command<command_t::WriteDisable>();}


__ais__ void four_byte_addr_enable(void) { command<command_t::Enter32addr>(); }
__ais__ void four_byte_addr_disable(void){ command<command_t::Exit32addr>();}

__ais__ uint8_t read_sr(void)
{
    uint8_t byte;
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::ReadStatusReg));
    byte = io_t::xfer(0Xff);
    io_t::cs_high();
    return byte;
}

__ais__ bool busy() noexcept { return read_sr() & 0x01 ; }

__ais__ void wait_busy(void) noexcept
  {
	 while ( busy() );
  }

__ais__ bool wait_busy(size_t& counter) noexcept
  {
	 while ( busy() && counter-- )  ;
	 return busy()  ;
  }




template<typename T>
__ais__ int read(const T& c, const uint32_t sector, const size_t off, uint8_t* data, const size_t size) noexcept
{
	const  uint32_t addr = sector * sector_size + off ;
    unsigned short i;
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::ReadData));
    io_t::xfer((uint8_t) ((addr) >> 16));
    io_t::xfer((uint8_t) ((addr) >> 8));
    io_t::xfer((uint8_t) addr);
    for (i = 0; i < size; i++)
    	((uint8_t *)data)[i] = io_t::xfer(0xff);
    io_t::cs_high();
//    c.con < "\tbd read s" < sector < " o" < off < " z" < size <<= c.con.endl;
    return 0;
}


template<typename T>
__ais__ int read32addr(const T& c, const uint32_t sector, const size_t off, uint8_t* data, const size_t size) noexcept
{
	const  uint32_t addr = sector * sector_size + off ;
    unsigned short i;
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::ReadData32addr));
    io_t::xfer((uint8_t) ((addr) >> 24));
    io_t::xfer((uint8_t) ((addr) >> 16));
    io_t::xfer((uint8_t) ((addr) >> 8));
    io_t::xfer((uint8_t) addr);
    for (i = 0; i < size; i++)
    	((uint8_t *)data)[i] = io_t::xfer(0xff);
    io_t::cs_high();
//    c.con < "\tbd read s" < sector < " o" < off < " z" < size <<= c.con.endl;
    return 0;
}

//--------------------------------------------------


template<typename T>
__ais__ int write(const T& c, const uint32_t sector, const size_t off, const uint8_t* data, const size_t size) noexcept
{
	const  uint32_t addr = sector * sector_size + off ;
    unsigned short i;
    write_enable();
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::PageProgram));
    io_t::xfer((uint8_t) ((addr) >> 16));
    io_t::xfer((uint8_t) ((addr) >> 8));
    io_t::xfer((uint8_t) addr);
    for (i = 0; i < size; i++)
        io_t::xfer(data[i]);
    io_t::cs_high();
    wait_busy();
//    c.con < "\tbd write s" < sector < " o" < off < " z" < size <<= c.con.endl;
    return 0;
}


template<typename T>
__ais__ int write32addr(const T& c, const uint32_t sector, const size_t off, const uint8_t* data, const size_t size) noexcept
{
	const  uint32_t addr = sector * sector_size + off ;
    unsigned short i;
    write_enable();
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::PageProgram32addr));
    io_t::xfer((uint8_t) ((addr) >> 24));
    io_t::xfer((uint8_t) ((addr) >> 16));
    io_t::xfer((uint8_t) ((addr) >> 8));
    io_t::xfer((uint8_t) addr);
    for (i = 0; i < size; i++)
        io_t::xfer(data[i]);
    io_t::cs_high();
    wait_busy();
//    c.con < "\tbd write s" < sector < " o" < off < " z" < size <<= c.con.endl;
    return 0;
}

//--------------------------------------------------


template<typename T>
__ais__ int erase(const T& c, const uint32_t sector) noexcept
{
    const size_t addr = sector * sector_size;
    write_enable();
    wait_busy();
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::SectorErase));
    io_t::xfer((uint8_t) ((addr) >> 16));
    io_t::xfer((uint8_t) ((addr) >> 8));
    io_t::xfer((uint8_t) addr);
    io_t::cs_high();
    wait_busy();
//    c.con < "\tbd erase " < sector <<= c.con.endl;
    return 0;
}




template<typename T>
__ais__ int erase32addr(const T& c, const uint32_t sector) noexcept
{
    const size_t addr = sector * sector_size;
    write_enable();
    wait_busy();
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::SectorErase32addr));
    io_t::xfer((uint8_t) ((addr) >> 24));
    io_t::xfer((uint8_t) ((addr) >> 16));
    io_t::xfer((uint8_t) ((addr) >> 8));
    io_t::xfer((uint8_t) addr);
    io_t::cs_high();
    wait_busy();
//    c.con < "\tbd erase " < sector <<= c.con.endl;
    return 0;
}



__ais__ int erase(const uint32_t sector) noexcept
{
    const size_t addr = sector * sector_size;
    write_enable();
    wait_busy();
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::SectorErase));
    io_t::xfer((uint8_t) ((addr) >> 16));
    io_t::xfer((uint8_t) ((addr) >> 8));
    io_t::xfer((uint8_t) addr);
    io_t::cs_high();
    wait_busy();
    return 0;
}

__ais__ int erase32addr(const uint32_t sector) noexcept
{
    const size_t addr = sector * sector_size;
    write_enable();
    wait_busy();
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::SectorErase32addr));
    io_t::xfer((uint8_t) ((addr) >> 24));
    io_t::xfer((uint8_t) ((addr) >> 16));
    io_t::xfer((uint8_t) ((addr) >> 8));
    io_t::xfer((uint8_t) addr);
    io_t::cs_high();
    wait_busy();
    return 0;
}


__ais__ void erase_chip(void)  noexcept
{
    write_enable();
    wait_busy();
    command<command_t::ChipErase>();
    wait_busy();
}


__ais__ void erase_chip_async(void)  noexcept
{
    write_enable();
    wait_busy();
    command<command_t::ChipErase>();
}


__ais__ void write_page(unsigned int addr, const char *data,  unsigned short size) noexcept
{
    unsigned short i;
    write_enable();
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::PageProgram));
    io_t::xfer((uint8_t) ((addr) >> 16));
    io_t::xfer((uint8_t) ((addr) >> 8));
    io_t::xfer((uint8_t) addr);
    for (i = 0; i < size; i++)
        io_t::xfer(data[i]);
    io_t::cs_high();
    wait_busy();
}

__ais__ void write_page32addr(unsigned int addr, const char *data,  unsigned short size) noexcept
{
    unsigned short i;
    write_enable();
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::PageProgram32addr));
    io_t::xfer((uint8_t) ((addr) >> 24));
    io_t::xfer((uint8_t) ((addr) >> 16));
    io_t::xfer((uint8_t) ((addr) >> 8));
    io_t::xfer((uint8_t) addr);
    for (i = 0; i < size; i++)
        io_t::xfer(data[i]);
    io_t::cs_high();
    wait_busy();
}





__ais__ void write_nocheck(unsigned int addr, const char *data, size_t size) noexcept
{
    unsigned short pageremain;
    pageremain = 256 - addr % 256;
    if (size <= pageremain)
        pageremain = size;
    while (1)
      {
        write_page( addr, data, pageremain);
        if (size == pageremain)
            break;
        else
          {
            data += pageremain;
            addr += pageremain;
            size -= pageremain;
            if (size > 256)
                pageremain = 256;
            else
                pageremain = size;
          }
      }
}

__ais__ void write_nocheck32addr(unsigned int addr, const char *data, size_t size) noexcept
{
    unsigned short pageremain;
    pageremain = 256 - addr % 256;
    if (size <= pageremain)
        pageremain = size;
    while (1)
      {
        write_page32addr( addr, data, pageremain);
        if (size == pageremain)
            break;
        else
          {
            data += pageremain;
            addr += pageremain;
            size -= pageremain;
            if (size > 256)
                pageremain = 256;
            else
                pageremain = size;
          }
      }
}


__ais__ unsigned short read_id(void) noexcept
{
    unsigned short Temp = 0;
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::ManufactDeviceID));
    io_t::xfer(0x00);
    io_t::xfer(0x00);
    io_t::xfer(0x00);
    Temp |= io_t::xfer(0xFF) << 8;
    Temp |= io_t::xfer(0xFF);
    io_t::cs_high();
    return Temp;
}

__ais__ void read_bytes(unsigned int addr, char *data, size_t size) noexcept
{
    unsigned short i;
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::ReadData));
    io_t::xfer((unsigned char) ((addr) >> 16));
    io_t::xfer((unsigned char) ((addr) >> 8));
    io_t::xfer((unsigned char) addr);
    for (i = 0; i < size; i++)
    	data[i] = io_t::xfer(0xff);
    io_t::cs_high();
}

__ais__ void read_bytes32addr(unsigned int addr, char *data, size_t size) noexcept
{
    unsigned short i;
    io_t::cs_low();
    io_t::xfer( static_cast<uint8_t>(command_t::ReadData32addr));
    io_t::xfer((unsigned char) ((addr) >> 24));
    io_t::xfer((unsigned char) ((addr) >> 16));
    io_t::xfer((unsigned char) ((addr) >> 8));
    io_t::xfer((unsigned char) addr);
    for (i = 0; i < size; i++)
    	data[i] = io_t::xfer(0xff);
    io_t::cs_high();
}

__ais__ void write_bytes(unsigned int addr, char *data, size_t size) noexcept
{
    unsigned int secpos;
    unsigned short secoff, secremain, i;

    char* flash_buffer = new char [sector_size] ;


    secpos = addr / sector_size;
    secoff = addr % sector_size;
    secremain = sector_size - secoff;

    if (size <= secremain)  secremain = size;

    while (1)
      {
        read_bytes(secpos * sector_size, flash_buffer, sector_size);
        for (i = 0; i < secremain; i++)
          {
            if (flash_buffer[secoff + i] != 0Xff)  break;
          }

        if (i < secremain)
          {
            erase(secpos);
            for (i = 0; i < secremain; i++)  flash_buffer[i + secoff] = data[i];
            write_nocheck(secpos * sector_size, flash_buffer,  sector_size);
          }
        else
            write_nocheck(addr, data, secremain);

        if (size == secremain) break;
        else
          {
            secpos++;
            secoff = 0;

            data += secremain;
            addr += secremain;
            size -= secremain;

            if (size > sector_size)
                secremain = sector_size;
            else
                secremain = size;
          }
      }
    delete [] flash_buffer ;
}

__ais__ void write_bytes32addr(unsigned int addr, char *data, size_t size) noexcept
{
    unsigned int secpos;
    unsigned short secoff, secremain, i;

    char* flash_buffer = new char [sector_size] ;


    secpos = addr / sector_size;
    secoff = addr % sector_size;
    secremain = sector_size - secoff;

    if (size <= secremain)  secremain = size;

    while (1)
      {
        read_bytes32addr(secpos * sector_size, flash_buffer, sector_size);
        for (i = 0; i < secremain; i++)
          {
            if (flash_buffer[secoff + i] != 0Xff)  break;
          }

        if (i < secremain)
          {
            erase32addr(secpos);
            for (i = 0; i < secremain; i++)  flash_buffer[i + secoff] = data[i];
            write_nocheck32addr(secpos * sector_size, flash_buffer,  sector_size);
          }
        else
            write_nocheck32addr(addr, data, secremain);

        if (size == secremain) break;
        else
          {
            secpos++;
            secoff = 0;

            data += secremain;
            addr += secremain;
            size -= secremain;

            if (size > sector_size)
                secremain = sector_size;
            else
                secremain = size;
          }
      }
    delete [] flash_buffer ;
}



constexpr __ais__ void power_down(void) noexcept { command<command_t::PowerDown>(); io_t::delay_us(3); }
constexpr __ais__ void wakeup(void) noexcept { command<command_t::ReleasePowerDown>(); io_t::delay_us(3); }
constexpr __ais__ uint32_t get_size() noexcept { return get_size(read_id()) ; }
constexpr __ais__ uint32_t sector_count() noexcept { return get_size()  / sector_size ; }
constexpr __ais__ void init() noexcept { io_t::init(); wakeup(); }
constexpr __ais__ auto info() noexcept
  {
	const auto device_id = read_id();
	struct ret_t { const uint16_t    id_code ;
	                  const string_view part_name ;
	                  const size_t size ;
	                  const size_t pages;
	                  const size_t sectors;
	                  const size_t blocks32k;
			          const size_t blocks64k;
	                 } ;
	const auto part_index = ( device_id & 0xff) ;
	const auto size = get_size(device_id) ;

	   return ret_t
	           { device_id,
	             part_name[part_index  - 16],
		         size,
		         size / 256,
		         size / ( 4*1024),
	             size / (32*1024),
		         size / (64*1024)
	           } ;
  }

static constexpr size_t page_size    = 256 ;
static constexpr size_t sector_size  = 4096 ;
static constexpr size_t sector_cycle = 100000 ;

protected:
    constexpr __ais__ uint32_t get_size(const uint16_t id)  noexcept  {	return 1 << ((id & 0xff) + 1) ;}



  private:


};


typedef w25qxx_tt<w25qxx_io_t>  w25qxx_t ;

#ifdef __APP_WRAPS_IMPL__
__attribute__((used, init_priority (net_constructor_priority))) w25qxx_t w25qxx ;

#else

  extern w25qxx_t w25qxx;
#endif

}














template <typename T> static int stub(const T& context)   {return 0;}
template <typename T> static int stub_lock  (const T& context) { /*context.con <<= " lock " ;*/ return 0;}
template <typename T> static int stub_unlock(const T& context) { /*context.con <<= " unlock " ;*/ return 0;}


// Calculate CRC-32 with polynomial = 0x04c11db7, software CRC implementation with small lookup table
static uint32_t crc32(const uint32_t val, const void *buffer, const size_t size) noexcept
{
    static constexpr uint32_t rtable[16] =
    { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };

    const uint8_t* data = static_cast<const uint8_t*>(buffer);
    auto crc = val ;
    for (size_t i = 0; i < size; i++) [[likely]] { crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; }
    return crc;
}


static uint32_t crchw( const uint32_t val, const uint8_t* buffer, const size_t size) noexcept
{
   return crc.process(val, buffer,size);
}




template <typename S /*storage type*/>
struct io_tt
{
	typedef S storage_t ;

	__ai__ io_tt () { storage_t::four_byte_addr_enable(); }



	//static constexpr struct context_t 	{ con_t& con ; } context {::con} ;
	static constexpr struct context_t 	{} context {} ;


	static constexpr uint32_t read_size   = storage_t::page_size  ;    // Minimum size of a block read in bytes. All read operations will be a multiple of this value.
	static constexpr uint32_t write_size  = storage_t::page_size  ;    // Minimum size of a block write in bytes. All program operations will be a multiple of this value.
	static constexpr uint32_t sector_size = storage_t::sector_size  ;  // Size of an erasable sector 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.

    // 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.
	static constexpr uint32_t sector_cycles = storage_t::sector_cycle;

    // 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.
	static constexpr uint32_t cache_size = storage_t::page_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.
	static constexpr uint32_t lookahead_size = storage_t::page_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% sector_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.
	static constexpr uint32_t compact_thresh = 0;

    // 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 <= sector_size.
    // Defaults to sector_size when zero.
	static constexpr uint32_t metadata_max = 0 ;

    // 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 <= sector_size/8. Defaults to the largest possible
    // inline_max when zero.
    //
    // Set to -1 to disable inlined files.
	static constexpr uint32_t inline_max = 0 ;

	// Read a region in a block. Negative error codes are propagated
    // to the user.
	static constexpr int (*read)(const context_t& context, const uint32_t sector, const size_t off, uint8_t* buffer, const size_t size) = storage_t::read32addr ;

    // 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.
	static constexpr int (*write)(const context_t& context, const uint32_t sector, const size_t off, const uint8_t* buffer, const size_t size) = storage_t::write32addr ;

    // Erase a sector. A sector must be erased before being programmed.
    // The state of an erased sector is undefined. Negative error codes
    // are propagated to the user.
    // May return LFS_ERR_CORRUPT if the sector should be considered bad.
	static constexpr int (*erase)(const context_t& context, const uint32_t sector) = storage_t::erase32addr ;

    // Sync the state of the underlying sector device. Negative error codes
    // are propagated to the user.
	static constexpr int (*sync)(const context_t& context) = stub ;

	// Calculate CRC-32
    static constexpr uint32_t (*crc)(const uint32_t val, const void *buffer, const size_t size) = crc32 ;

    struct lock_t
	  {
		 __ai__  lock_t() noexcept { stub_lock  (context); }
		 __ai__ ~lock_t() noexcept { stub_unlock(context); }
	  }  ;

    // Statically allocated read buffer. Must be cache_size.
    static uint8_t read_buffer[cache_size] ;

    // Statically allocated program buffer. Must be cache_size.
    static uint8_t write_buffer[cache_size];

    // Statically allocated lookahead buffer. Must be lookahead_size.
	static uint8_t lookahead_buffer[cache_size];


	// зависимлсти по реализациям libc-like функций
    static void*   memcpy (void *__restrict dst0 , const void *__restrict src0 , size_t len0) noexcept { return kgp_sdk_libc::memcpy (dst0, src0 , len0); }
    static void*   memset (void* m, int c, size_t n)                                          noexcept { return kgp_sdk_libc::memset(m, c, n); }
    static int     memcmp(const void* m1, const void* m2, size_t n)                           noexcept { return kgp_sdk_libc::memcmp (m1, m2, n); }
    static char*   strchr(const char* s , const int i)                                        noexcept { return kgp_sdk_libc::strchr(s, i);}
    static size_t  strspn (const char* s1, const char* s2)                                    noexcept { return kgp_sdk_libc::strspn(s1, s2);}
    static size_t  strcspn(const char* s, const char* reject)                                 noexcept { return kgp_sdk_libc::strcspn(s, reject);}
    static char*   strcpy (char* __restrict dst, const char* __restrict src)                  noexcept { return kgp_sdk_libc::strcpy(dst, src); }
    static size_t  strlen (const char* str)                                                   noexcept { return kgp_sdk_libc::strlen(str); }
};

typedef io_tt<storage::w25qxx_t> io_t ;


struct klfs_format_config_t
{
	static constexpr uint32_t (*sector_count)() =  []() -> uint32_t { return storage::w25qxx.sector_count(); } ;
	// 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.
	static constexpr uint32_t name_max    =  klfs_tt<void>::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.
	static constexpr uint32_t file_max    =  klfs_tt<void>::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.
	static constexpr uint32_t attr_max    =  klfs_tt<void>::attr_max;
} ;

typedef io_tt<storage::w25qxx_t> io_t ;

typedef klfs_tt<io_t> klfs_t ;


#ifdef __APP_WRAPS_IMPL__
   __attribute__((used, init_priority (net_constructor_priority))) klfs_t klfs ;
   template<> uint8_t io_t::read_buffer     [io_t::cache_size] = {};
   template<> uint8_t io_t::write_buffer    [io_t::cache_size] = {};
   template<> uint8_t io_t::lookahead_buffer[io_t::cache_size] = {};
#else

   extern klfs_t klfs ;
#endif