Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions bsp/ESP/ESP32_C6/SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,158 @@ def _use_c6_idf_when_official_has_none():

_use_c6_idf_when_official_has_none()

def _match_idf_spi_lock_stub_to_spi_config():
"""components/esp_hw_support/port/esp32c6/spi_lock_stub.c defines
spicommon_bus_using_iomux() and spi_bus_lock_get_by_id() as
always-true/always-0, and the package's C6 block compiles it because its
directory scan takes every .c under that port directory that is not in
skip_name. It is there because the package does not build
components/driver/spi_common.c for C6, and components/spi_flash/
esp_flash_spi_init.c needs those two names: with SPI off this BSP has the
same hole, so the stub stays.

idf_port/SConscript compiles spi_common.c itself once BSP_USING_SPI is
selected, and spi_common.c:726 and :743 define the same two names -- the
real ones, the second of which hands the master driver a bus lock instead of
the stub's 0. Two strong definitions of both names in one image is a link
error, so the scan has to skip the stub in that configuration only. The
switch is written into the package's own SConscript because that scan lives
nowhere else; packages/ is not in the repository, so this is redone on every
build and on every re-fetch of the package, and it is written as bytes to
keep the file's own line endings.
"""
import re
from env_package import find_package_path

with open(os.path.join(SDK_ROOT, 'rtconfig.h'), 'r',
encoding='utf-8', errors='replace') as f:
spi_selected = re.search(r'^[ \t]*#[ \t]*define[ \t]+BSP_USING_SPI[ \t]*$',
f.read(), re.M) is not None

sc = os.path.join(os.path.abspath(
find_package_path(SDK_ROOT, 'ESP-IDF-latest')), 'SConscript')
if not os.path.isfile(sc):
return
with open(sc, 'rb') as f:
text = f.read()
entry = b"\n 'spi_lock_stub',"
have = entry in text
if have == spi_selected:
return
if spi_selected:
marker = b'skip_name = ('
if marker not in text:
raise RuntimeError('%s has no %s to skip spi_lock_stub.c in'
% (sc, marker.decode()))
text = text.replace(marker, marker + entry, 1)
else:
text = text.replace(entry, b'', 1)
with open(sc, 'wb') as f:
f.write(text)

_match_idf_spi_lock_stub_to_spi_config()

def _adapt_idf_gdma_to_c6_ll():
"""idf_port/SConscript compiles the package's components/esp_hw_support/
gdma.c when BSP_USING_SPI is on, because spi_common.c's
SOC_GDMA_SUPPORTED section calls gdma_new_channel() and friends. That gdma.c
is written against the C3 generation of hal/gdma_ll.h and this package's
components/hal/esp32c6/include/hal/gdma_ll.h is a different one:

- gdma_ll_enable_m2m_mode() (gdma.c:259) does not exist on C6. C6 folds
the same thing into the two connect helpers: gdma_ll_rx_connect_to_periph
(gdma_ll.h:297) writes in_conf0.mem_trans_en = (periph ==
GDMA_TRIG_PERIPH_M2M) itself, and gdma_ll_rx_disconnect_from_periph
(gdma_ll.h:306) clears it. So the call goes away rather than being
renamed to something C6 does not do.
- gdma_ll_tx/rx_connect_to_periph take four arguments on C6 (gdma_ll.h:520
and :297) -- dev, channel, periph, periph_id -- while gdma.c:264/:269
pass three. gdma_trigger_t (esp_hw_support/include/esp_private/
gdma.h:130) keeps .periph and .instance_id apart, and .instance_id is
what the C3 header wrote into the same select field, so the fix is to
hand C6 the periph it also asks for and keep that value as periph_id.
- gdma.c:289/:291 disconnect by feeding GDMA_INVALID_PERIPH_TRIG back into
connect. C6 has no such sentinel path: it has
gdma_ll_tx_disconnect_from_periph (gdma_ll.h:529) and
gdma_ll_rx_disconnect_from_periph, which write GDMA_LL_INVALID_PERIPH_ID
and clear mem_trans_en.
- the RX connect is left inside its `periph != GDMA_TRIG_PERIPH_M2M`
guard, which on C6 also skips the only writer of mem_trans_en; it is
unguarded here and the TX guard is left alone. Rewritten below, after
these four.

The rewrite is here, in a tracked file, and it is a whole call expression
swapped for a whole call expression, so a second run finds nothing left to
match and cannot stack a second copy. packages/ is not in the repository.
"""
import re
from env_package import find_package_path

gdma = os.path.join(os.path.abspath(
find_package_path(SDK_ROOT, 'ESP-IDF-latest')),
'components', 'esp_hw_support', 'gdma.c')
if not os.path.isfile(gdma):
return
with open(gdma, 'rb') as f:
text = f.read()

fixed = text
# C6 has no m2m setter: its rx connect sets mem_trans_en, its rx disconnect
# clears it. Drop the call, keep the comment above it, which still says what
# the connect below now does.
fixed = re.sub(rb'^[ \t]*gdma_ll_enable_m2m_mode\([^\r\n]*\r?\n', b'',
fixed, flags=re.M)
for old, new in [
(b'gdma_ll_tx_connect_to_periph(group->hal.dev, pair->pair_id, '
b'trig_periph.instance_id)',
b'gdma_ll_tx_connect_to_periph(group->hal.dev, pair->pair_id, '
b'trig_periph.periph, trig_periph.instance_id)'),
(b'gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id, '
b'trig_periph.instance_id)',
b'gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id, '
b'trig_periph.periph, trig_periph.instance_id)'),
(b'gdma_ll_tx_connect_to_periph(group->hal.dev, pair->pair_id, '
b'GDMA_INVALID_PERIPH_TRIG)',
b'gdma_ll_tx_disconnect_from_periph(group->hal.dev, pair->pair_id)'),
(b'gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id, '
b'GDMA_INVALID_PERIPH_TRIG)',
b'gdma_ll_rx_disconnect_from_periph(group->hal.dev, pair->pair_id)'),
]:
fixed = fixed.replace(old, new)

# The RX connect stays inside the same `periph != M2M` guard gdma.c uses for
# TX, and on C6 that guard is exactly what decides mem_trans_en: the only
# writer of that bit is gdma_ll_rx_connect_to_periph (gdma_ll.h:297-301), and
# it writes mem_trans_en = (periph == GDMA_TRIG_PERIPH_M2M) while doing the
# selection. Guarded, a memory-to-memory channel never reaches the call that
# would set its own mode bit, so mem_trans_en stays 0 forever. C3's
# gdma_ll_enable_m2m_mode -- the call removed above -- wrote that one RX bit
# and nothing else, so the guard replaces a call that used to be made
# unconditionally. TX keeps its guard: gdma_ll_tx_connect_to_periph
# (gdma_ll.h:520-523) throws `periph` away and writes only peri_out_sel, so
# for a TX channel skipping the call loses nothing.
rx_guarded = (
b' if (trig_periph.periph != GDMA_TRIG_PERIPH_M2M) {\n'
b' gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id,'
b' trig_periph.periph, trig_periph.instance_id);\n'
b' }\n')
rx_unguarded = (
b' gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id,'
b' trig_periph.periph, trig_periph.instance_id);\n')
for old, new in ((rx_guarded, rx_unguarded),
(rx_guarded.replace(b'\n', b'\r\n'),
rx_unguarded.replace(b'\n', b'\r\n'))):
if old in fixed:
fixed = fixed.replace(old, new, 1)
break

if fixed != text:
with open(gdma, 'wb') as f:
f.write(fixed)

_adapt_idf_gdma_to_c6_ll()


objs = PrepareBuilding(env, RTT_ROOT, remove_components = ['libc'])

objs.extend(SConscript(os.path.join(libraries_path_prefix, 'drivers', 'SConscript'), variant_dir='build/libraries/drivers', duplicate=0))
Expand Down
118 changes: 118 additions & 0 deletions bsp/ESP/ESP32_C6/idf_port/SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,122 @@ CPPPATH = Split(

group = DefineGroup('idf_port', src, depend = [''], CPPPATH = CPPPATH)

# bsp/ESP/libraries/drivers/drv_spi.c calls the ESP-IDF master mode SPI driver:
# spi_bus_initialize() lives in components/driver/spi_common.c and
# spi_bus_add_device()/spi_device_acquire_bus()/spi_device_polling_transmit()/
# spi_device_release_bus() in components/driver/spi_master.c. The package's own
# driver group drops both for C6 (packages/ESP-IDF-latest/SConscript:999 keeps
# only gpio.c, rtc_io.c, uart.c and spi_bus_lock.c out of components/driver), so
# they are compiled here -- and only with BSP_USING_SPI selected, so a default
# build adds nothing.
if GetDepend(['BSP_USING_SPI']):
from env_package import find_package_path

bsp_root = os.path.normpath(os.path.join(GetCurrentDir(), '..'))
idf = find_package_path(bsp_root, 'ESP-IDF-latest',
relative_to=bsp_root, pathsep='/')
wrapper = find_package_path(bsp_root, 'FreeRTOS-Wrapper-latest',
relative_to=bsp_root, pathsep='/')

# scons resolves these against the BSP root (the variant dir 'build' mirrors
# them), so '..' steps out of this idf_port directory; the include
# directories below are absolute to stay independent of that.
spi_src = [
'../%s/components/driver/spi_common.c' % idf,
'../%s/components/driver/spi_master.c' % idf,
# spi_common.c and spi_master.c are written against the HAL layer, and
# this package's C6 hal group drops spi_hal.c; without it the ten
# spi_hal_* calls those two files make have no definition.
'../%s/components/hal/spi_hal.c' % idf,
# spi_common.c reaches GDMA through its own SOC_GDMA_SUPPORTED section
# (soc/esp32c6/include/soc/soc_caps.h defines it as 1), and that code is
# compiled into the object no matter what the third argument of
# spi_bus_initialize() is. The package drops every path containing
# 'gdma' from its C6 source list, so the driver and its hal wrapper are
# compiled here instead. That gdma.c is written against the C3
# generation of hal/gdma_ll.h; SConstruct rewrites its gdma_ll_* call
# sites to the prototypes in this package's C6 header before anything is
# compiled.
'../%s/components/esp_hw_support/gdma.c' % idf,
'../%s/components/hal/gdma_hal.c' % idf,
# gdma.c reads gdma_periph_signals (gdma.c:541 and on) to map a
# GDMA_TRIG_PERIPH_* onto the signal slots of a channel; the one object
# file defining it is this SoC table, which the package drops for the
# same 'gdma' reason as the two files above.
'../%s/components/soc/esp32c6/gdma_periph.c' % idf,
# this gcc has no __atomic_compare_exchange_1 (see the file); the byte
# sized compare-exchange of spi_common.c's atomic_bool needs one.
'src/rt_atomic_cas_stub.c',
]

# headers the two files include. freertos/queue.h and freertos/task.h come
# from the RT-Thread FreeRTOS wrapper, like everywhere else in this BSP: the
# kernel copy inside the package wants an IDF sdkconfig this BSP does not
# carry.
spi_path = [os.path.join(bsp_root, idf, 'components', p) for p in (
'driver/include',
'esp_common/include',
'esp_hw_support/include',
'esp_hw_support/port/esp32c6/include',
'esp_rom/include',
'esp_system/include',
'esp_system/port/include',
'esp_system/port/include/private',
'esp_timer/include',
'hal/include',
'hal/esp32c6/include',
'hal/platform_port/include',
'heap/include',
'log/include',
'newlib/platform_include',
'riscv/include',
'soc/include',
'soc/esp32c6/include',
)] + [os.path.join(bsp_root, wrapper, 'FreeRTOS/include')]

# hal/esp32c6/include/hal/spi_ll.h names SPI_CLK_SRC_XTAL/SPI_CLK_SRC_RC_FAST
# (enumerators of soc/clk_tree_defs.h) and spi_command_t with its
# SPI_CMD_HD_* values, and this package snapshot defines neither of the
# latter anywhere -- only spi_ll.h uses them, hal/include/hal/spi_types.h has
# no spi_command_t. drv_spi.c carries the declarations for its own
# translation unit; these flags give the package's own sources the same
# thing. The real home of both is the package's hal/include/hal/spi_types.h.
# One string, because building.py appends LOCAL_CFLAGS to a CFLAGS string.
spi_flags = (
'-include soc/clk_tree_defs.h'
' -Dspi_command_t=int'
' -DSPI_CMD_HD_WRBUF=0x01'
' -DSPI_CMD_HD_RDBUF=0x02'
' -DSPI_CMD_HD_WRDMA=0x03'
' -DSPI_CMD_HD_RDDMA=0x04'
' -DSPI_CMD_HD_SEG_END=0x05'
' -DSPI_CMD_HD_EN_QPI=0x06'
' -DSPI_CMD_HD_WR_END=0x07'
' -DSPI_CMD_HD_INT0=0x08'
' -DSPI_CMD_HD_INT1=0x09'
' -DSPI_CMD_HD_INT2=0x0A'
)

group += DefineGroup('idf_port_spi', spi_src, depend = ['BSP_USING_SPI'],
LOCAL_CPPPATH = spi_path, LOCAL_CFLAGS = spi_flags)

# spi_hal.c is only init/deinit/cal_clock. spi_hal_setup_device,
# spi_hal_setup_trans, spi_hal_prepare_data, spi_hal_user_start and
# spi_hal_fetch_result -- everything spi_master.c drives a transfer with --
# are in spi_hal_iram.c, the second file of that pair, which this package's
# C6 drop_hal list removes as well.
#
# It is a group of its own because it is the only source that may see
# include/spi_ll_master_start_compat.h: that header defines a macro named
# spi_ll_master_user_start, and giving it to spi_common.c, spi_master.c or
# gdma.c would only shadow the C6 header's own helpers in files that never
# ask for that name.
iram_path = [os.path.join(bsp_root, 'idf_port', 'include')] + spi_path
group += DefineGroup('idf_port_spi_iram',
['../%s/components/hal/spi_hal_iram.c' % idf],
depend = ['BSP_USING_SPI'],
LOCAL_CPPPATH = iram_path,
LOCAL_CFLAGS = spi_flags +
' -include spi_ll_master_start_compat.h')

Return('group')
29 changes: 29 additions & 0 deletions bsp/ESP/ESP32_C6/idf_port/include/spi_ll_master_start_compat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#ifndef RT_ESP_IDF_PORT_SPI_LL_MASTER_START_COMPAT_H
#define RT_ESP_IDF_PORT_SPI_LL_MASTER_START_COMPAT_H

#include "hal/spi_ll.h"

/*
* components/hal/spi_hal_iram.c:188 starts the transaction with
* spi_ll_master_user_start(hal->hw), a name this package's
* hal/esp32c6/include/hal/spi_ll.h does not have. C6 splits it in two:
* spi_ll_apply_config() (spi_ll.h:208, sets cmd.update and waits until the
* peripheral has taken the configuration) and spi_ll_user_start() (spi_ll.h:231,
* sets cmd.usr). The six targets whose spi_ll.h does define
* spi_ll_master_user_start -- esp32, esp32c2, esp32c3, esp32h2, esp32s2,
* esp32s3 -- all define it as those same two writes, in this order. So this is
* not a substitute for the C6 sequence, it is the C6 sequence under the name the
* shared spi_hal_iram.c asks for.
*
* Included with -include, for that one source file only (idf_port/SConscript),
* so no other translation unit sees the name. packages/ is not in the
* repository, which is why the fix lives here.
*/
#define spi_ll_master_user_start(hw) \
do \
{ \
spi_ll_apply_config(hw); \
spi_ll_user_start(hw); \
} while (0)

#endif /* RT_ESP_IDF_PORT_SPI_LL_MASTER_START_COMPAT_H */
55 changes: 55 additions & 0 deletions bsp/ESP/ESP32_C6/idf_port/src/rt_atomic_cas_stub.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include <rtthread.h>

/*
* spi_common.c claims a bus with
* atomic_compare_exchange_strong(&spi_periph_claimed[host], &expected, value)
* on an _Atomic bool, and an ESP32-C6 core can only reserve a whole word, so
* this gcc emits a call to the out-of-line helper instead of an lr/sc sequence.
* The install has no __atomic_compare_exchange_1:
* riscv32-esp-elf/bin/../lib/gcc/riscv32-esp-elf/11.2.0/libgcc.a -> 0 hits
* and there is no libatomic.a next to it, so the name stays undefined at link
* time. The prototype below is the one the compiler itself declares -- it
* rejects any other spelling with -Wbuiltin-declaration-mismatch -- and it
* matches what the call site really passes: the address of the object, the
* address of the expected byte, the desired byte, and two memory orders. The
* `weak` argument is not passed in a register at all (an atomic_compare_exchange
* weak/strong pair compiles to the same five loads), which is why it is ignored
* here; the helper's answer is always the strong one, so a weak CAS merely gets
* one it did not ask for.
*
* Masking interrupts is enough for this SoC: it has one core, so the only other
* writer of these bytes is an interrupt handler.
*/
_Bool __atomic_compare_exchange_1(volatile void *object,
void *expected,
unsigned char desired,
_Bool weak,
int success,
int failure)
{
volatile unsigned char *addr = (volatile unsigned char *)object;
unsigned char *compare = (unsigned char *)expected;
rt_base_t level;
_Bool exchanged;

(void)weak;
(void)success;
(void)failure;

level = rt_hw_interrupt_disable();

if (*addr == *compare)
{
*addr = desired;
exchanged = 1;
}
else
{
*compare = *addr;
exchanged = 0;
}

rt_hw_interrupt_enable(level);

return exchanged;
}
2 changes: 1 addition & 1 deletion bsp/ESP/libraries/drivers/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ menu "On-chip Peripheral Drivers"

menuconfig BSP_USING_SPI
bool "Enable SPI"
depends on SOC_ESP32_C3
depends on SOC_ESP32_C3 || SOC_ESP32_C6
default n
select RT_USING_SPI
if BSP_USING_SPI
Expand Down
Loading
Loading