Skip to content

Commit 4cc20a9

Browse files
authored
SpiUtils - explicit asm instead of volatile (#9256)
* SpiUtils - explicit asm instead of volatile getting rid of volatile arg(s) is must-have for -std=c++20 and later provide helper macros to init & load vars at specific points in code modify existing macros to use c++-style casts & less volatile change locals to plain non-volatile variables, avoid extra memw also fixes unintentional const mask load in (pre_cmd) branch * words * 'a' constraint for output as noted in implementation, all but sp
1 parent 8126849 commit 4cc20a9

File tree

1 file changed

+70
-25
lines changed

1 file changed

+70
-25
lines changed

cores/esp8266/core_esp8266_spi_utils.cpp

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
2020
*/
2121

22-
#include <stdint.h>
23-
#include <string.h>
22+
#include <cstdint>
23+
#include <cstring>
24+
#include <memory>
2425

2526
// register names
2627
#include "esp8266_peri.h"
@@ -43,35 +44,80 @@ namespace experimental {
4344
* Kept in a separate function to aid with precaching
4445
* PRECACHE_* saves having to make the function IRAM_ATTR.
4546
*
46-
* spiIfNum needs to be volatile to keep the optimiser from
47-
* deciding it can be treated as a constant (due to this being a
48-
* static function only called with spiIfNum set to 0)
47+
* PRELOAD_* allows access to consts and external values
48+
* through a different name, while also forcing immediate load.
49+
* (but, note that compiler only knows about DST and SRC as dependencies)
4950
*
5051
* Note: if porting to ESP32 mosi/miso bits are set in 2 registers, not 1.
5152
*/
53+
54+
#define PRELOAD_DST_SRC(DST,SRC)\
55+
__asm__ __volatile__ (\
56+
"mov %0, %1\n\t"\
57+
: "=a"(DST)\
58+
: "r"(SRC)\
59+
: "memory")
60+
61+
#define PRELOAD_IMMEDIATE(DST,SRC)\
62+
uint32_t DST;\
63+
__asm__ __volatile__ (\
64+
"movi %0, %1\n\t"\
65+
: "=a"(DST)\
66+
: "i"(SRC)\
67+
: "memory")
68+
69+
#define PRELOAD_VAL(DST,SRC)\
70+
decltype(SRC) DST;\
71+
PRELOAD_DST_SRC(DST,SRC)
72+
73+
#define PRELOAD_PTR(DST,SRC)\
74+
decltype(std::addressof(SRC)) DST;\
75+
PRELOAD_DST_SRC(DST,SRC)
76+
5277
static SpiOpResult PRECACHE_ATTR
53-
_SPICommand(volatile uint32_t spiIfNum,
54-
uint32_t spic,uint32_t spiu,uint32_t spiu1,uint32_t spiu2,
55-
uint32_t *data,uint32_t writeWords,uint32_t readWords, uint32_t pre_cmd)
78+
_SPICommand(uint32_t spiIfNum,
79+
uint32_t spic, uint32_t spiu, uint32_t spiu1, uint32_t spiu2,
80+
uint32_t *data, uint32_t writeWords, uint32_t readWords, uint32_t _pre_cmd)
5681
{
5782
if (spiIfNum>1)
5883
return SPI_RESULT_ERR;
5984

60-
// force SPI register access via base+offset.
61-
// Prevents loading individual address constants from flash.
62-
uint32_t *spibase = (uint32_t*)(spiIfNum ? &(SPI1CMD) : &(SPI0CMD));
63-
#define SPIREG(reg) (*((volatile uint32_t *)(spibase+(&(reg) - &(SPI0CMD)))))
85+
// force SPI register access via base+offset by deconstructing SPI# access macros
86+
// note that the function below only ever calls this one w/ spiIfNum==0
87+
// in case it is *really* necessary, preload spiIfNum as well
88+
#define VOLATILE_PTR(X) reinterpret_cast<volatile uint32_t *>(X)
89+
#define SPIADDR(X) const_cast<uint32_t *>(std::addressof(X))
90+
91+
// preload all required constants and functions into variables.
92+
// when modifying code below, always double-check the asm output
93+
94+
PRELOAD_IMMEDIATE(spi0cmd_addr, SPIADDR(SPI0CMD));
95+
PRELOAD_IMMEDIATE(spi1cmd_addr, SPIADDR(SPI1CMD));
96+
uint32_t *spibase = spiIfNum
97+
? reinterpret_cast<uint32_t *>(spi1cmd_addr)
98+
: reinterpret_cast<uint32_t *>(spi0cmd_addr);
99+
#define SPIREG(reg) \
100+
(*VOLATILE_PTR(spibase + (SPIADDR(reg) - SPIADDR(SPI0CMD))))
101+
102+
PRELOAD_PTR(SPI_write_enablep, SPI_write_enable);
103+
PRELOAD_PTR(Wait_SPI_Idlep, Wait_SPI_Idle);
104+
105+
PRELOAD_VAL(fchip, flashchip);
106+
107+
PRELOAD_IMMEDIATE(spicmdusr, SPICMDUSR);
108+
PRELOAD_IMMEDIATE(saved_ps, 0);
109+
110+
// also force 'pre_cmd' & 'spiu' mask constant to be loaded right now
111+
// (TODO write all of the preamble in asm directly?)
112+
PRELOAD_VAL(pre_cmd, _pre_cmd);
64113

65-
// preload any constants and functions we need into variables
66-
// Everything defined here must be volatile or the optimizer can
67-
// treat them as constants, resulting in the flash reads we're
68-
// trying to avoid
69-
SpiFlashOpResult (* volatile SPI_write_enablep)(SpiFlashChip *) = SPI_write_enable;
70-
uint32_t (* volatile Wait_SPI_Idlep)(SpiFlashChip *) = Wait_SPI_Idle;
71-
volatile SpiFlashChip *fchip=flashchip;
72-
volatile uint32_t spicmdusr=SPICMDUSR;
114+
PRELOAD_IMMEDIATE(pre_cmd_spiu_mask, ~(SPIUMOSI | SPIUMISO));
115+
uint32_t _pre_cmd_spiu = spiu & pre_cmd_spiu_mask;
116+
PRELOAD_VAL(pre_cmd_spiu, _pre_cmd_spiu);
73117

74-
uint32_t saved_ps=0;
118+
PRELOAD_IMMEDIATE(pre_cmd_spiu2_mask, ~0xFFFFu);
119+
uint32_t _pre_cmd_spiu2 = (spiu2 & pre_cmd_spiu2_mask) | pre_cmd;
120+
PRELOAD_VAL(pre_cmd_spiu2, _pre_cmd_spiu2);
75121

76122
if (!spiIfNum) {
77123
// Only need to disable interrupts and precache when using SPI0
@@ -91,12 +137,11 @@ _SPICommand(volatile uint32_t spiIfNum,
91137
if (SPI_FLASH_CMD_WREN == pre_cmd) {
92138
// See SPI_write_enable comments in esp8266_undocumented.h
93139
SPI_write_enablep((SpiFlashChip *)fchip);
94-
} else
95-
if (pre_cmd) {
140+
} else if (pre_cmd) {
96141
// Send prefix cmd w/o data - sends 8 bits. eg. Volatile SR Write Enable, 0x50
97-
SPIREG(SPI0U) = (spiu & ~(SPIUMOSI|SPIUMISO));
142+
SPIREG(SPI0U) = pre_cmd_spiu;
98143
SPIREG(SPI0U1) = 0;
99-
SPIREG(SPI0U2) = (spiu2 & ~0xFFFFu) | pre_cmd;
144+
SPIREG(SPI0U2) = pre_cmd_spiu2;
100145

101146
SPIREG(SPI0CMD) = spicmdusr; //Send cmd
102147
while ((SPIREG(SPI0CMD) & spicmdusr));

0 commit comments

Comments
 (0)