machine/stm32, nrf: flash API (#3472)

machine/stm32, nrf: implement machine.Flash

Implements the machine.Flash interface using the same definition as the tinyfs BlockDevice.

This implementation covers the stm32f4, stm32l4, stm32wlx, nrf51, nrf52, and nrf528xx processors.
This commit is contained in:
Ron Evans
2023-02-27 13:55:38 +01:00
committed by GitHub
parent 8bf94b9231
commit 6e1b8a54aa
12 changed files with 817 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
//go:build stm32f4 || stm32l4 || stm32wlx
package machine
import (
"device/stm32"
"bytes"
"unsafe"
)
// compile-time check for ensuring we fulfill BlockDevice interface
var _ BlockDevice = flashBlockDevice{}
var Flash flashBlockDevice
type flashBlockDevice struct {
}
// ReadAt reads the given number of bytes from the block device.
func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
return 0, errFlashCannotReadPastEOF
}
data := unsafe.Slice((*byte)(unsafe.Pointer(FlashDataStart()+uintptr(off))), len(p))
copy(p, data)
return len(p), nil
}
// WriteAt writes the given number of bytes to the block device.
// Only double-word (64 bits) length data can be programmed. See rm0461 page 78.
// If the length of p is not long enough it will be padded with 0xFF bytes.
// This method assumes that the destination is already erased.
func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
return 0, errFlashCannotWritePastEOF
}
unlockFlash()
defer lockFlash()
return writeFlashData(FlashDataStart()+uintptr(off), f.pad(p))
}
// Size returns the number of bytes in this block device.
func (f flashBlockDevice) Size() int64 {
return int64(FlashDataEnd() - FlashDataStart())
}
// WriteBlockSize returns the block size in which data can be written to
// memory. It can be used by a client to optimize writes, non-aligned writes
// should always work correctly.
func (f flashBlockDevice) WriteBlockSize() int64 {
return writeBlockSize
}
func eraseBlockSize() int64 {
return eraseBlockSizeValue
}
// EraseBlockSize returns the smallest erasable area on this particular chip
// in bytes. This is used for the block size in EraseBlocks.
// It must be a power of two, and may be as small as 1. A typical size is 4096.
// TODO: correctly handle processors that have differently sized blocks
// in different areas of memory like the STM32F40x and STM32F1x.
func (f flashBlockDevice) EraseBlockSize() int64 {
return eraseBlockSize()
}
// EraseBlocks erases the given number of blocks. An implementation may
// transparently coalesce ranges of blocks into larger bundles if the chip
// supports this. The start and len parameters are in block numbers, use
// EraseBlockSize to map addresses to blocks.
func (f flashBlockDevice) EraseBlocks(start, len int64) error {
unlockFlash()
defer lockFlash()
for i := start; i < start+len; i++ {
if err := eraseBlock(uint32(i)); err != nil {
return err
}
}
return nil
}
// pad data if needed so it is long enough for correct byte alignment on writes.
func (f flashBlockDevice) pad(p []byte) []byte {
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
if paddingNeeded == 0 {
return p
}
padded := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
return append(p, padded...)
}
const memoryStart = 0x08000000
func unlockFlash() {
// keys as described rm0461 page 76
var fkey1 uint32 = 0x45670123
var fkey2 uint32 = 0xCDEF89AB
// Wait for the flash memory not to be busy
for stm32.FLASH.GetSR_BSY() != 0 {
}
// Check if the controller is unlocked already
if stm32.FLASH.GetCR_LOCK() != 0 {
// Write the first key
stm32.FLASH.SetKEYR(fkey1)
// Write the second key
stm32.FLASH.SetKEYR(fkey2)
}
}
func lockFlash() {
stm32.FLASH.SetCR_LOCK(1)
}