Merge branch 'feat/spi_bus_lock' into 'master'

SPI: support running SPI master and esp_flash on the same bus

See merge request espressif/esp-idf!6520
This commit is contained in:
Michael (XIAO Xufeng) 2020-03-27 19:59:43 +08:00
commit a304421124
20 changed files with 2331 additions and 662 deletions

View file

@ -18,6 +18,7 @@ set(srcs
"spi_common.c" "spi_common.c"
"spi_master.c" "spi_master.c"
"spi_slave.c" "spi_slave.c"
"spi_bus_lock.c"
"timer.c" "timer.c"
"touch_sensor_common.c" "touch_sensor_common.c"
"uart.c") "uart.c")
@ -45,7 +46,7 @@ idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS ${includes} INCLUDE_DIRS ${includes}
PRIV_INCLUDE_DIRS "include/driver" PRIV_INCLUDE_DIRS "include/driver"
PRIV_REQUIRES efuse esp_timer PRIV_REQUIRES efuse esp_timer
REQUIRES esp_ringbuf soc) #cannot totally hide soc headers, since there are a lot arguments in the driver are chip-dependent REQUIRES esp_ringbuf freertos soc) #cannot totally hide soc headers, since there are a lot arguments in the driver are chip-dependent
# uses C11 atomic feature # uses C11 atomic feature
set_source_files_properties(spi_master.c PROPERTIES COMPILE_FLAGS -std=gnu11) set_source_files_properties(spi_master.c PROPERTIES COMPILE_FLAGS -std=gnu11)

View file

@ -102,7 +102,7 @@ typedef struct {
* *
* @warning For now, only supports HSPI and VSPI. * @warning For now, only supports HSPI and VSPI.
* *
* @param host SPI peripheral that controls this bus * @param host_id SPI peripheral that controls this bus
* @param bus_config Pointer to a spi_bus_config_t struct specifying how the host should be initialized * @param bus_config Pointer to a spi_bus_config_t struct specifying how the host should be initialized
* @param dma_chan Either channel 1 or 2, or 0 in the case when no DMA is required. Selecting a DMA channel * @param dma_chan Either channel 1 or 2, or 0 in the case when no DMA is required. Selecting a DMA channel
* for a SPI bus allows transfers on the bus to have sizes only limited by the amount of * for a SPI bus allows transfers on the bus to have sizes only limited by the amount of
@ -123,20 +123,20 @@ typedef struct {
* - ESP_ERR_NO_MEM if out of memory * - ESP_ERR_NO_MEM if out of memory
* - ESP_OK on success * - ESP_OK on success
*/ */
esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus_config, int dma_chan); esp_err_t spi_bus_initialize(spi_host_device_t host_id, const spi_bus_config_t *bus_config, int dma_chan);
/** /**
* @brief Free a SPI bus * @brief Free a SPI bus
* *
* @warning In order for this to succeed, all devices have to be removed first. * @warning In order for this to succeed, all devices have to be removed first.
* *
* @param host SPI peripheral to free * @param host_id SPI peripheral to free
* @return * @return
* - ESP_ERR_INVALID_ARG if parameter is invalid * - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_INVALID_STATE if not all devices on the bus are freed * - ESP_ERR_INVALID_STATE if not all devices on the bus are freed
* - ESP_OK on success * - ESP_OK on success
*/ */
esp_err_t spi_bus_free(spi_host_device_t host); esp_err_t spi_bus_free(spi_host_device_t host_id);
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -16,7 +16,11 @@
#pragma once #pragma once
#include <esp_intr_alloc.h>
#include "driver/spi_common.h" #include "driver/spi_common.h"
#include "freertos/FreeRTOS.h"
#include "hal/spi_types.h"
#include "esp_pm.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"
@ -24,6 +28,57 @@ extern "C"
#endif #endif
#ifdef CONFIG_SPI_MASTER_ISR_IN_IRAM
#define SPI_MASTER_ISR_ATTR IRAM_ATTR
#else
#define SPI_MASTER_ISR_ATTR
#endif
#ifdef CONFIG_SPI_MASTER_IN_IRAM
#define SPI_MASTER_ATTR IRAM_ATTR
#else
#define SPI_MASTER_ATTR
#endif
#define BUS_LOCK_DEBUG 0
#if BUS_LOCK_DEBUG
#define BUS_LOCK_DEBUG_EXECUTE_CHECK(x) assert(x)
#else
#define BUS_LOCK_DEBUG_EXECUTE_CHECK(x)
#endif
struct spi_bus_lock_t;
struct spi_bus_lock_dev_t;
/// Handle to the lock of an SPI bus
typedef struct spi_bus_lock_t* spi_bus_lock_handle_t;
/// Handle to lock of one of the device on an SPI bus
typedef struct spi_bus_lock_dev_t* spi_bus_lock_dev_handle_t;
/// Background operation control function
typedef void (*bg_ctrl_func_t)(void*);
/// Attributes of an SPI bus
typedef struct {
spi_bus_config_t bus_cfg; ///< Config used to initialize the bus
uint32_t flags; ///< Flags (attributes) of the bus
int max_transfer_sz; ///< Maximum length of bytes available to send
int dma_chan; ///< DMA channel used
int dma_desc_num; ///< DMA descriptor number of dmadesc_tx or dmadesc_rx.
lldesc_t *dmadesc_tx; ///< DMA descriptor array for TX
lldesc_t *dmadesc_rx; ///< DMA descriptor array for RX
spi_bus_lock_handle_t lock;
#ifdef CONFIG_PM_ENABLE
esp_pm_lock_handle_t pm_lock; ///< Power management lock
#endif
} spi_bus_attr_t;
/// Destructor called when a bus is deinitialized.
typedef esp_err_t (*spi_destroy_func_t)(void*);
/** /**
* @brief Try to claim a SPI peripheral * @brief Try to claim a SPI peripheral
* *
@ -262,6 +317,472 @@ void spicommon_dmaworkaround_idle(int dmachan);
*/ */
void spicommon_dmaworkaround_transfer_active(int dmachan); void spicommon_dmaworkaround_transfer_active(int dmachan);
/*******************************************************************************
* Bus attributes
******************************************************************************/
/**
* @brief Set bus lock for the main bus, called by startup code.
*
* @param lock The lock to be used by the main SPI bus.
*/
void spi_bus_main_set_lock(spi_bus_lock_handle_t lock);
/**
* @brief Get the attributes of a specified SPI bus.
*
* @param host_id The specified host to get attribute
* @return (Const) Pointer to the attributes
*/
const spi_bus_attr_t* spi_bus_get_attr(spi_host_device_t host_id);
/**
* @brief Register a function to a initialized bus to make it called when deinitializing the bus.
*
* @param host_id The SPI bus to register the destructor.
* @param f Destructor to register
* @param arg The argument to call the destructor
* @return Always ESP_OK.
*/
esp_err_t spi_bus_register_destroy_func(spi_host_device_t host_id,
spi_destroy_func_t f, void *arg);
/*******************************************************************************
* SPI Bus Lock for arbitration among SPI master (intr, polling) trans, SPI flash operations and
* flash/psram cache access.
*
* NON-PUBLIC API. Don't use it directly in applications.
*
* There is the main lock corresponding to an SPI bus, of which several devices (holding child
* locks) attaching to it. Each of the device is STRONGLY RECOMMENDED to be used in only one task
* to avoid concurrency issues.
*
* Terms:
* - BG operations (BackGround operations) means some transaction that will not immediately /
* explicitly be sent in the task. It can be some cache access, or interrupt transactions.
*
* - Operation: usage of the bus, for example, do SPI transactions.
*
* - Acquiring processor: the task or the ISR that is allowed to use the bus. No operations will be
* performed if there is no acquiring processor. A processor becomes the acquiring processor if
* it ask for that when no acquiring processor exist, otherwise it has to wait for the acquiring
* processor to handle over the role to it. The acquiring processor will and will only assign one
* acquiring processor in the waiting list (if not empty) when it finishes its operation.
*
* - Acquiring device: the only device allowed to use the bus. Operations can be performed in
* either the BG or the task. When there's no acquiring device, only the ISR is allowed to be the
* acquiring processor and perform operations on the bus.
*
* When a device wants to perform operations, it either:
* 1. Acquire the bus, and operate in the task (e.g. polling transactions of SPI master, and SPI flash
* operations)
*
* 2. Request a BG operation. And the ISR will be enabled at proper time.
*
* For example if a task wants to send an interrupt transaction, it prepares the data in the task,
* call `spi_bus_lock_bg_request`, and handle sending in the ISR.
*
* 3. When a device has already acquired the bus, BG operations are also allowed. After the
* `spi_bus_lock_bg_request` is called, call `spi_bus_lock_wait_bg_done` before operations in task
* again to wait until BG operations are done.
*
* Any device may try to invoke the ISR (by `spi_bus_lock_bg_request`). The ISR will be invoked and
* become the acquiring processor immediately when the bus is not acquired by other processors. Any
* device may also try to acquire the bus (by `spi_bus_lock_acquire_start`). The device will become
* the acquiring processor immediately when the bus is not acquired and there is no request active.
*
* The acquiring processor must be aware of its acquiring role, and properly transfer the acquiring
* processor to other tasks or ISR when they have nothing else to do. Before picking a new
* acquiring processor, a new acquiring device must be picked first, if there are other devices,
* asking to be acquiring device. After that, the new acquiring processor is picked by the sequence
* below:
*
* 1. If there is an acquiring device:
* 1.1 The ISR, if acquiring device has active BG requests
* 1.2 The task of the device, if no active BG request for the device
* 2. The ISR, if there's no acquiring device, but any BG request is active
* 3. No one becomes the acquiring processor
*
* The API also helps on the arbitration of SPI cs lines. The bus is initialized with a cs_num
* argument. When attaching devices onto the bus with `spi_bus_lock_register_dev`, it will allocate
* devices with different device ID according to the flags given. If the ID is smaller than the
* cs_num given when bus is initialized, error will be returned.
*
* Usage:
* * Initialization:
* 1. Call `spi_bus_init_lock` to register a lock for a bus.
* 2. Call `spi_bus_lock_set_bg_control` to prepare BG enable/disable functions for
* the lock.
* 3. Call `spi_bus_lock_register_dev` for each devices that may make use of the
* bus, properly store the returned handle, representing those devices.
*
* * Acquiring:
* 1. Call `spi_bus_lock_acquire_start` when a device wants to use the bus
* 2. Call `spi_bus_lock_touch` to mark the bus as touched by this device. Also check if the bus
* has been touched by other devices.
* 3. (optional) Do something on the bus...
* 4. (optional) Call `spi_bus_lock_bg_request` to inform and invoke the BG. See ISR below about
* ISR operations.
* 5. (optional) If `spi_bus_lock_bg_request` is done, you have to call `spi_bus_lock_wait_bg_done`
* before touching the bus again, or do the following steps.
* 6. Call `spi_bus_lock_acquire_end` to release the bus to other devices.
*
* * ISR:
* 1. Call `spi_bus_lock_bg_entry` when entering the ISR, run or skip the closure for the previous
* operation according to the return value.
* 2. Call `spi_bus_lock_get_acquiring_dev` to get the acquiring device. If there is no acquiring
* device, call `spi_bus_lock_bg_check_dev_acq` to check and update a new acquiring device.
* 3. Call `spi_bus_lock_bg_check_dev_req` to check for request of the desired device. If the
* desired device is not requested, go to step 5.
* 4. Check, start operation for the desired device and go to step 6; otherwise if no operations
* can be performed, call `spi_bus_lock_bg_clear_req` to clear the request for this device. If
* `spi_bus_lock_bg_clear_req` is called and there is no BG requests active, goto step 6.
* 5. (optional) If the device is the acquiring device, go to step 6, otherwise
* find another desired device, and go back to step 3.
* 6. Call `spi_bus_lock_bg_exit` to try quitting the ISR. If failed, go back to step 2 to look for
* a new request again. Otherwise, quit the ISR.
*
* * Deinitialization (optional):
* 1. Call `spi_bus_lock_unregister_dev` for each device when they are no longer needed.
* 2. Call `spi_bus_deinit_lock` to release the resources occupied by the lock.
*
* Some technical details:
*
* The child-lock of each device will have its own Binary Semaphore, which allows the task serving
* this device (task A) being blocked when it fail to become the acquiring processor while it's
* calling `spi_bus_lock_acquire_start` or `spi_bus_lock_wait_bg_done`. If it is blocked, there
* must be an acquiring processor (either the ISR or another task (task B)), is doing transaction
* on the bus. After that, task A will get unblocked and become the acquiring processor when the
* ISR call `spi_bus_lock_bg_resume_acquired_dev`, or task B call `spi_bus_lock_acquire_end`.
*
* When the device wants to send ISR transaction, it should call `spi_bus_lock_bg_request` after
* the data is prepared. This function sets a request bit in the critical resource. The ISR will be
* invoked and become the new acquiring processor, when:
*
* 1. A task calls `spi_bus_lock_bg_request` while there is no acquiring processor;
* 2. A tasks calls `spi_bus_lock_bg_request` while the task is the acquiring processor. Then the
* acquiring processor is handled over to the ISR;
* 3. A tasks who is the acquiring processor release the bus by calling `spi_bus_lock_acquire_end`,
* and the ISR happens to be the next acquiring processor.
*
* The ISR will check (by `spi_bus_lock_bg_check_dev_req`) and clear a request bit (by
* `spi_bus_lock_bg_clear_req`) after it confirm that all the requests of the corresponding device
* are served. The request bit supports being written to recursively, which means, the task don't
* need to wait for `spi_bus_lock_bg_clear_req` before call another `spi_bus_lock_bg_request`. The
* API will handle the concurrency conflicts properly.
*
* The `spi_bus_lock_bg_exit` (together with `spi_bus_lock_bg_entry` called before)` is responsible
* to ensure ONE and ONLY ONE of the following will happen when the ISR try to give up its
* acquiring processor rule:
*
* 1. ISR quit, no any task unblocked while the interrupt disabled, and none of the BG bits is
* active.
* 2. ISR quit, there is an acquiring device, and the acquiring processor is passed to the task
* serving the acquiring device by unblocking the task.
* 3. The ISR failed to quit and have to try again.
******************************************************************************/
#define DEV_NUM_MAX 6 ///< Number of devices supported by this lock
/// Lock configuration struct
typedef struct {
int host_id; ///< SPI host id
int cs_num; ///< Physical cs numbers of the host
} spi_bus_lock_config_t;
/// Child-lock configuration struct
typedef struct {
uint32_t flags; ///< flags for the lock, OR-ed of `SPI_BUS_LOCK_DEV_*` flags.
#define SPI_BUS_LOCK_DEV_FLAG_CS_REQUIRED BIT(0) ///< The device needs a physical CS pin.
} spi_bus_lock_dev_config_t;
/************* Common *********************/
/**
* Initialize a lock for an SPI bus.
*
* @param out_lock Output of the handle to the lock
* @return
* - ESP_ERR_NO_MEM: if memory exhausted
* - ESP_OK: if success
*/
esp_err_t spi_bus_init_lock(spi_bus_lock_handle_t *out_lock, const spi_bus_lock_config_t *config);
/**
* Free the resources used by an SPI bus lock.
*
* @note All attached devices should have been unregistered before calling this
* funciton.
*
* @param lock Handle to the lock to free.
*/
void spi_bus_deinit_lock(spi_bus_lock_handle_t lock);
/**
* @brief Get the corresponding lock according to bus id.
*
* @param host_id The bus id to get the lock
* @return The lock handle
*/
spi_bus_lock_handle_t spi_bus_lock_get_by_id(spi_host_device_t host_id);
/**
* @brief Configure how the SPI bus lock enable the background operation.
*
* @note The lock will not try to stop the background operations, but wait for
* The background operations finished indicated by `spi_bus_lock_bg_resume_acquired_dev`.
*
* @param lock Handle to the lock to set
* @param bg_enable The enabling function
* @param bg_disable The disabling function, set to NULL if not required
* @param arg Argument to pass to the enabling/disabling function.
*/
void spi_bus_lock_set_bg_control(spi_bus_lock_handle_t lock, bg_ctrl_func_t bg_enable,
bg_ctrl_func_t bg_disable, void *arg);
/**
* Attach a device onto an SPI bus lock. The returning handle is used to perform
* following requests for the attached device.
*
* @param lock SPI bus lock to attach
* @param out_dev_handle Output handle corresponding to the device
* @param flags requirement of the device, bitwise OR of SPI_BUS_LOCK_FLAG_* flags
*
* @return
* - ESP_ERR_NOT_SUPPORTED: if there's no hardware resources for new devices.
* - ESP_ERR_NO_MEM: if memory exhausted
* - ESP_OK: if success
*/
esp_err_t spi_bus_lock_register_dev(spi_bus_lock_handle_t lock,
spi_bus_lock_dev_config_t *config,
spi_bus_lock_dev_handle_t *out_dev_handle);
/**
* Detach a device from its bus and free the resources used
*
* @param dev_handle Handle to the device.
*/
void spi_bus_lock_unregister_dev(spi_bus_lock_dev_handle_t dev_handle);
/**
* @brief Get the parent bus lock of the device
*
* @param dev_handle Handle to the device to get bus lock
* @return The bus lock handle
*/
spi_bus_lock_handle_t spi_bus_lock_get_parent(spi_bus_lock_dev_handle_t dev_handle);
/**
* @brief Get the device ID of a lock.
*
* The callers should allocate CS pins according to this ID.
*
* @param dev_handle Handle to the device to get ID
* @return ID of the device
*/
int spi_bus_lock_get_dev_id(spi_bus_lock_dev_handle_t dev_handle);
/**
* @brief The device request to touch bus registers. Can only be called by the acquiring processor.
*
* Also check if the registers has been touched by other devices.
*
* @param dev_handle Handle to the device to operate the registers
* @return true if there has been other devices touching SPI registers.
* The caller may need to do a full-configuration. Otherwise return
* false.
*/
bool spi_bus_lock_touch(spi_bus_lock_dev_handle_t dev_handle);
/************* Acquiring service *********************/
/**
* Acquiring the SPI bus for exclusive use. Will also wait for the BG to finish all requests of
* this device before it returns.
*
* After successfully return, the caller becomes the acquiring processor.
*
* @note For the main flash bus, `bg_disable` will be called to disable the cache.
*
* @param dev_handle Handle to the device request for acquiring.
* @param wait Time to wait until timeout or succeed, must be `portMAX_DELAY` for now.
* @return
* - ESP_OK: on success
* - ESP_ERR_INVALID_ARG: timeout is not portMAX_DELAY
*/
esp_err_t spi_bus_lock_acquire_start(spi_bus_lock_dev_handle_t dev_handle, TickType_t wait);
/**
* Release the bus acquired. Will pass the acquiring processor to other blocked
* processors (tasks or ISR), and cause them to be unblocked or invoked.
*
* The acquiring device may also become NULL if no device is asking for acquiring.
* In this case, the BG may be invoked if there is any BG requests.
*
* If the new acquiring device has BG requests, the BG will be invoked before the
* task is resumed later after the BG finishes all requests of the new acquiring
* device. Otherwise the task of the new acquiring device will be resumed immediately.
*
* @param dev_handle Handle to the device releasing the bus.
* @return
* - ESP_OK: on success
* - ESP_ERR_INVALID_STATE: the device hasn't acquired the lock yet
*/
esp_err_t spi_bus_lock_acquire_end(spi_bus_lock_dev_handle_t dev_handle);
/**
* Get the device acquiring the bus.
*
* @note Return value is not stable as the acquiring processor may change
* when this function is called.
*
* @param lock Lock of SPI bus to get the acquiring device.
* @return The argument corresponding to the acquiring device, see
* `spi_bus_lock_register_dev`.
*/
spi_bus_lock_dev_handle_t spi_bus_lock_get_acquiring_dev(spi_bus_lock_handle_t lock);
/************* BG (Background, for ISR or cache) service *********************/
/**
* Call by a device to request a BG operation.
*
* Depending on the bus lock state, the BG operations may be resumed by this
* call, or pending until BG operations allowed.
*
* Cleared by `spi_bus_lock_bg_clear_req` in the BG.
*
* @param dev_handle The device requesting BG operations.
* @return always ESP_OK
*/
esp_err_t spi_bus_lock_bg_request(spi_bus_lock_dev_handle_t dev_handle);
/**
* Wait until the ISR has finished all the BG operations for the acquiring device.
* If any `spi_bus_lock_bg_request` for this device has been called after
* `spi_bus_lock_acquire_start`, this function must be called before any operation
* in the task.
*
* @note Can only be called when bus acquired by this device.
*
* @param dev_handle Handle to the device acquiring the bus.
* @param wait Time to wait until timeout or succeed, must be `portMAX_DELAY` for now.
* @return
* - ESP_OK: on success
* - ESP_ERR_INVALID_STATE: The device is not the acquiring bus.
* - ESP_ERR_INVALID_ARG: Timeout is not portMAX_DELAY.
*/
esp_err_t spi_bus_lock_wait_bg_done(spi_bus_lock_dev_handle_t dev_handle, TickType_t wait);
/**
* Handle interrupt and closure of last operation. Should be called at the beginning of the ISR,
* when the ISR is acting as the acquiring processor.
*
* @param lock The SPI bus lock
*
* @return false if the ISR has already touched the HW, should run closure of the
* last operation first; otherwise true if the ISR just start operating
* on the HW, closure should be skipped.
*/
bool spi_bus_lock_bg_entry(spi_bus_lock_handle_t lock);
/**
* Handle the scheduling of other acquiring devices, and control of HW operation
* status.
*
* If no BG request is found, call with `wip=false`. This function will return false,
* indicating there is incoming BG requests for the current acquiring device (or
* for all devices if there is no acquiring device) and the ISR needs retry.
* Otherwise may schedule a new acquiring processor (unblock the task) if there
* is, and return true.
*
* Otherwise if a BG request is started in this ISR, call with `wip=true` and the
* function will enable the interrupt to make the ISR be called again when the
* request is done.
*
* This function is safe and should still be called when the ISR just lost its acquiring processor
* role, but hasn't quit.
*
* @note This function will not change acquiring device. The ISR call
* `spi_bus_lock_bg_update_acquiring` to check for new acquiring device,
* when acquiring devices need to be served before other devices.
*
* @param lock The SPI bus lock.
* @param wip Whether an operation is being executed when quitting the ISR.
* @param do_yield[out] Not touched when no yielding required, otherwise set
* to pdTRUE.
* @return false if retry is required, indicating that there is pending BG request.
* otherwise true and quit ISR is allowed.
*/
bool spi_bus_lock_bg_exit(spi_bus_lock_handle_t lock, bool wip, BaseType_t* do_yield);
/**
* Check whether there is device asking for the acquiring device, and the desired
* device for the next operation is also recommended.
*
* @note Must be called when the ISR is acting as the acquiring processor, and
* there is no acquiring device.
*
* @param lock The SPI bus lock.
* @param out_dev_lock The recommended device for hte next operation. It's the new
* acquiring device when found, otherwise a device that has active BG request.
*
* @return true if the ISR need to quit (new acquiring device has no active BG
* request, or no active BG requests for all devices when there is no
* acquiring device), otherwise false.
*/
bool spi_bus_lock_bg_check_dev_acq(spi_bus_lock_handle_t lock, spi_bus_lock_dev_handle_t *out_dev_lock);
/**
* Check if the device has BG requests. Must be called when the ISR is acting as
* the acquiring processor.
*
* @note This is not stable, may become true again when a task request for BG
* operation (by `spi_bus_lock_bg_request`).
*
* @param dev_lock The device to check.
* @return true if the device has BG requests, otherwise false.
*/
bool spi_bus_lock_bg_check_dev_req(spi_bus_lock_dev_handle_t dev_lock);
/**
* Clear the pending BG operation request of a device after served. Must be
* called when the ISR is acting as the acquiring processor.
*
* @note When the return value is true, the ISR will lost the acquiring processor role. Then
* `spi_bus_lock_bg_exit` must be called and checked before calling all other functions that
* require to be called when the ISR is the acquiring processor again.
*
* @param dev_handle The device whose request is served.
* @return True if no pending requests for the acquiring device, or for all devices
* if there is no acquiring device. Otherwise false. When the return value is
* true, the ISR is no longer the acquiring processor.
*/
bool spi_bus_lock_bg_clear_req(spi_bus_lock_dev_handle_t dev_lock);
/**
* Check if there is any active BG requests.
*
* @param lock The SPI bus lock.
* @return true if any device has active BG requst, otherwise false.
*/
bool spi_bus_lock_bg_req_exist(spi_bus_lock_handle_t lock);
/*******************************************************************************
* Variable and APIs for the OS to initialize the locks for the main chip
******************************************************************************/
/// The lock for the main flash device
extern const spi_bus_lock_dev_handle_t g_spi_lock_main_flash_dev;
/// The lock for the main bus
extern const spi_bus_lock_handle_t g_main_spi_bus_lock;
/**
* @brief Initialize the main flash device, called during chip startup.
*
* @return
* - ESP_OK: if success
* - ESP_ERR_NO_MEM: memory exhausted
*/
esp_err_t spi_bus_lock_init_main_dev(void);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -171,7 +171,7 @@ typedef struct spi_device_t* spi_device_handle_t; ///< Handle for a device on a
* @note While in general, speeds up to 80MHz on the dedicated SPI pins and 40MHz on GPIO-matrix-routed pins are * @note While in general, speeds up to 80MHz on the dedicated SPI pins and 40MHz on GPIO-matrix-routed pins are
* supported, full-duplex transfers routed over the GPIO matrix only support speeds up to 26MHz. * supported, full-duplex transfers routed over the GPIO matrix only support speeds up to 26MHz.
* *
* @param host SPI peripheral to allocate device on * @param host_id SPI peripheral to allocate device on
* @param dev_config SPI interface protocol config for the device * @param dev_config SPI interface protocol config for the device
* @param handle Pointer to variable to hold the device handle * @param handle Pointer to variable to hold the device handle
* @return * @return
@ -180,7 +180,7 @@ typedef struct spi_device_t* spi_device_handle_t; ///< Handle for a device on a
* - ESP_ERR_NO_MEM if out of memory * - ESP_ERR_NO_MEM if out of memory
* - ESP_OK on success * - ESP_OK on success
*/ */
esp_err_t spi_bus_add_device(spi_host_device_t host, const spi_device_interface_config_t *dev_config, spi_device_handle_t *handle); esp_err_t spi_bus_add_device(spi_host_device_t host_id, const spi_device_interface_config_t *dev_config, spi_device_handle_t *handle);
/** /**

View file

@ -0,0 +1,826 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include <stdatomic.h>
#include "sdkconfig.h"
#include "spi_common_internal.h"
#include "esp_intr_alloc.h"
#include "soc/spi_caps.h"
#include "stdatomic.h"
#include "esp_log.h"
#include <strings.h>
/*
* This lock is designed to solve the conflicts between SPI devices (used in tasks) and
* the background operations (ISR or cache access).
*
* There are N (device/task) + 1 (BG) acquiring processer candidates that may touch the bus.
*
* The core of the lock is a `status` atomic variable, which is always available. No intermediate
* status is allowed. The atomic operations (mainly `atomic_fetch_and`, `atomic_fetch_or`)
* atomically read the status, and bitwisely write status value ORed / ANDed with given masks.
*
* Definitions of the status:
* - [30] WEAK_BG_FLAG, active when the BG is the cache
* - [29:20] LOCK bits, active when corresponding device is asking for acquiring
* - [19:10] PENDING bits, active when the BG acknowledges the REQ bits, but hasn't fully handled them.
* - [ 9: 0] REQ bits, active when corresponding device is requesting for BG operations.
*
* The REQ bits together PENDING bits are called BG bits, which represent the actual BG request
* state of devices. Either one of REQ or PENDING being active indicates the device has pending BG
* requests. Reason of having two bits instead of one is in the appendix below.
*
* Acquiring processer means the current processor (task or ISR) allowed to touch the critical
* resources, or the SPI bus.
*
* States of the lock:
* - STATE_IDLE: There's no acquiring processor. No device is acquiring the bus, and no BG
* operation is in progress.
*
* - STATE_ACQ: The acquiring processor is a device task. This means one of the devices is
* acquiring the bus.
*
* - STATE_BG: The acquiring processor is the ISR, and there is no acquiring device.
*
* - STATE_BG_ACQ: The acquiring processor is the ISR, and there is an acquiring device.
*
*
* Whenever a bit is written to the status, it means the a device on a task is trying to acquire
* the lock (either for the task, or the ISR). When there is no LOCK bits or BG bits active, the
* caller immediately become the acquiring processor. Otherwise, the task has to block, and the ISR
* will not be invoked until scheduled by the current acquiring processor.
*
* The acquiring processor is responsible to assign the next acquiring processor by calling the
* scheduler, usually after it finishes some requests, and cleared the corresponding status bit.
* But there is one exception, when the last bit is cleared from the status, after which there is
* no other LOCK bits or BG bits active, the acquiring processor lost its role immediately, and
* don't need to call the scheduler to assign the next acquiring processor.
*
* The acquiring processor may also choose to assign a new acquiring device when there is no, by
* calling `spi_bus_lock_bg_rotate_acq_dev` in the ISR. But the acquiring processor, in this case,
* is still the ISR, until it calls the scheduler.
*
*
* Transition of the FSM:
*
* - STATE_IDLE: no acquiring device, nor acquiring processor, no LOCK or BG bits active
* -> STATE_BG: by `req_core`
* -> STATE_ACQ: by `acquire_core`
*
* - STATE_BG:
* * No acquiring device, the ISR is the acquiring processor, there is BG bits active, but no LOCK
* bits
* * The BG operation should be enabled while turning into this state.
*
* -> STATE_IDLE: by `bg_exit_core` after `clear_pend_core` for all BG bits
* -> STATE_BG_ACQ: by `schedule_core`, when there is new LOCK bit set (by `acquire_core`)
*
* - STATE_BG_ACQ:
* * There is acquiring device, the ISR is the acquiring processor, there may be BG bits active for
* the acquiring device.
* * The BG operation should be enabled while turning into this state.
*
* -> STATE_ACQ: by `bg_exit_core` after `clear_pend_core` for all BG bits for the acquiring
* device.
*
* Should not go to the STATE_ACQ (unblock the acquiring task) until all requests of the
* acquiring device are finished. This is to preserve the sequence of foreground (polling) and
* background operations of the device. The background operations queued before the acquiring
* should be completed first.
*
* - STATE_ACQ:
* * There is acquiring device, the task is the acquiring processor, there is no BG bits active for
* the acquiring device.
* * The acquiring task (if blocked at `spi_bus_lock_acquire_start` or `spi_bus_lock_wait_bg_done`)
* should be resumed while turning into this state.
*
* -> STATE_BG_ACQ: by `req_core`
* -> STATE_BG_ACQ (other device): by `acquire_end_core`, when there is LOCK bit for another
* device, and the new acquiring device has active BG bits.
* -> STATE_ACQ (other device): by `acquire_end_core`, when there is LOCK bit for another devices,
* but the new acquiring device has no active BG bits.
* -> STATE_BG: by `acquire_end_core` when there is no LOCK bit active, but there are active BG
* bits.
* -> STATE_IDLE: by `acquire_end_core` when there is no LOCK bit, nor BG bit active.
*
* The `req_core` used in the task is a little special. It asks for acquiring processor for the
* ISR. When it succeed for the first time, it will invoke the ISR (hence passing the acquiring
* role to the BG). Otherwise it will not block, the ISR will be automatically be invoked by other
* acquiring processor. The caller of `req_core` will never become acquiring processor by this
* function.
*
*
* Appendix: The design, that having both request bit and pending bit, is to solve the
* concurrency issue between tasks and the bg, when the task can queue several requests,
* however the request bit cannot represent the number of requests queued.
*
* Here's the workflow of task and ISR work concurrently:
* - Task: (a) Write to Queue -> (b) Write request bit
* The Task have to write request bit (b) after the data is prepared in the queue (a),
* otherwise the BG may fail to read from the queue when it sees the request bit set.
*
* - BG: (c) Read queue -> (d) Clear request bit
* Since the BG cannot know the number of requests queued, it have to repeatedly check the
* queue (c), until it find the data is empty, and then clear the request bit (d).
*
* The events are possible to happen in the order: (c) -> (a) -> (b) -> (d). This may cause a false
* clear of the request bit. And there will be data prepared in the queue, but the request bit is
* inactive.
*
* (e) move REQ bits to PEND bits, happen before (c) is introduced to solve this problem. In this
* case (d) is changed to clear the PEND bit. Even if (e) -> (c) -> (a) -> (b) -> (d), only PEND
* bit is cleared, while the REQ bit is still active.
*/
struct spi_bus_lock_dev_t;
typedef struct spi_bus_lock_dev_t spi_bus_lock_dev_t;
typedef struct spi_bus_lock_t spi_bus_lock_t;
#define MAX_DEV_NUM 10
// Bit 29-20: lock bits, Bit 19-10: pending bits
// Bit 9-0: request bits, Bit 30:
#define LOCK_SHIFT 20
#define PENDING_SHIFT 10
#define REQ_SHIFT 0
#define WEAK_BG_FLAG BIT(30) /**< The bus is permanently requested by background operations.
* This flag is weak, will not prevent acquiring of devices. But will help the BG to be re-enabled again after the bus is release.
*/
// get the bit mask wher bit [high-1, low] are all 1'b1 s.
#define BIT1_MASK(high, low) ((UINT32_MAX << (high)) ^ (UINT32_MAX << (low)))
#define LOCK_BIT(mask) ((mask) << LOCK_SHIFT)
#define REQUEST_BIT(mask) ((mask) << REQ_SHIFT)
#define PENDING_BIT(mask) ((mask) << PENDING_SHIFT)
#define DEV_MASK(id) (LOCK_BIT(1<<id) | PENDING_BIT(1<<id) | REQUEST_BIT(1<<id))
#define ID_DEV_MASK(mask) (ffs(mask) - 1)
#define REQ_MASK BIT1_MASK(REQ_SHIFT+MAX_DEV_NUM, REQ_SHIFT)
#define PEND_MASK BIT1_MASK(PENDING_SHIFT+MAX_DEV_NUM, PENDING_SHIFT)
#define BG_MASK BIT1_MASK(REQ_SHIFT+MAX_DEV_NUM*2, REQ_SHIFT)
#define LOCK_MASK BIT1_MASK(LOCK_SHIFT+MAX_DEV_NUM, LOCK_SHIFT)
#define DEV_REQ_MASK(dev) ((dev)->mask & REQ_MASK)
#define DEV_PEND_MASK(dev) ((dev)->mask & PEND_MASK)
#define DEV_BG_MASK(dev) ((dev)->mask & BG_MASK)
struct spi_bus_lock_t {
/**
* The core of the lock. These bits are status of the lock, which should be always available.
* No intermediate status is allowed. This is realized by atomic operations, mainly
* `atomic_fetch_and`, `atomic_fetch_or`, which atomically read the status, and bitwise write
* status value ORed / ANDed with given masks.
*
* The request bits together pending bits represent the actual bg request state of one device.
* Either one of them being active indicates the device has pending bg requests.
*
* Whenever a bit is written to the status, it means the a device on a task is trying to
* acquire the lock. But this will succeed only when no LOCK or BG bits active.
*
* The acquiring processor is responsible to call the scheduler to pass its role to other tasks
* or the BG, unless it clear the last bit in the status register.
*/
//// Critical resources, they are only writable by acquiring processor, and stable only when read by the acquiring processor.
atomic_uint_fast32_t status;
spi_bus_lock_dev_t* volatile acquiring_dev; ///< The acquiring device
bool volatile acq_dev_bg_active; ///< BG is the acquiring processor serving the acquiring device, used for the wait_bg to skip waiting quickly.
bool volatile in_isr; ///< ISR is touching HW
//// End of critical resources
atomic_intptr_t dev[DEV_NUM_MAX]; ///< Child locks.
bg_ctrl_func_t bg_enable; ///< Function to enable background operations.
bg_ctrl_func_t bg_disable; ///< Function to disable background operations
void* bg_arg; ///< Argument for `bg_enable` and `bg_disable` functions.
spi_bus_lock_dev_t* last_dev; ///< Last used device, to decide whether to refresh all registers.
int periph_cs_num; ///< Number of the CS pins the HW has.
//debug information
int host_id; ///< Host ID, for debug information printing
uint32_t new_req; ///< Last int_req when `spi_bus_lock_bg_start` is called. Debug use.
};
struct spi_bus_lock_dev_t {
SemaphoreHandle_t semphr; ///< Binray semaphore to notify the device it claimed the bus
spi_bus_lock_t* parent; ///< Pointer to parent spi_bus_lock_t
uint32_t mask; ///< Bitwise OR-ed mask of the REQ, PEND, LOCK bits of this device
};
static const char TAG[] = "bus_lock";
#define LOCK_CHECK(a, str, ret_val, ...) \
if (!(a)) { \
ESP_LOGE(TAG,"%s(%d): "str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
return (ret_val); \
}
static inline uint32_t mask_get_id(uint32_t mask);
static inline uint32_t dev_lock_get_id(spi_bus_lock_dev_t *dev_lock);
/*******************************************************************************
* atomic operations to the status
******************************************************************************/
SPI_MASTER_ISR_ATTR static inline uint32_t lock_status_fetch_set(spi_bus_lock_t *lock, uint32_t set)
{
return atomic_fetch_or(&lock->status, set);
}
IRAM_ATTR static inline uint32_t lock_status_fetch_clear(spi_bus_lock_t *lock, uint32_t clear)
{
return atomic_fetch_and(&lock->status, ~clear);
}
IRAM_ATTR static inline uint32_t lock_status_fetch(spi_bus_lock_t *lock)
{
return atomic_load(&lock->status);
}
SPI_MASTER_ISR_ATTR static inline void lock_status_init(spi_bus_lock_t *lock)
{
atomic_store(&lock->status, 0);
}
// return the remaining status bits
IRAM_ATTR static inline uint32_t lock_status_clear(spi_bus_lock_t* lock, uint32_t clear)
{
//the fetch and clear should be atomic, avoid missing the all '0' status when all bits are clear.
uint32_t state = lock_status_fetch_clear(lock, clear);
return state & (~clear);
}
/*******************************************************************************
* Schedule service
*
* The modification to the status bits may cause rotating of the acquiring processor. It also have
* effects to `acquired_dev` (the acquiring device), `in_isr` (HW used in BG), and
* `acq_dev_bg_active` (wait_bg_end can be skipped) members of the lock structure.
*
* Most of them should be atomic, and special attention should be paid to the operation
* sequence.
******************************************************************************/
SPI_MASTER_ISR_ATTR static inline void resume_dev_in_isr(spi_bus_lock_dev_t *dev_lock, BaseType_t *do_yield)
{
xSemaphoreGiveFromISR(dev_lock->semphr, do_yield);
}
IRAM_ATTR static inline void resume_dev(const spi_bus_lock_dev_t *dev_lock)
{
xSemaphoreGive(dev_lock->semphr);
}
SPI_MASTER_ISR_ATTR static inline void bg_disable(spi_bus_lock_t *lock)
{
BUS_LOCK_DEBUG_EXECUTE_CHECK(lock->bg_disable);
lock->bg_disable(lock->bg_arg);
}
IRAM_ATTR static inline void bg_enable(spi_bus_lock_t* lock)
{
BUS_LOCK_DEBUG_EXECUTE_CHECK(lock->bg_enable);
lock->bg_enable(lock->bg_arg);
}
// Set the REQ bit. If we become the acquiring processor, invoke the ISR and pass that to it.
// The caller will never become the acquiring processor after this function returns.
SPI_MASTER_ATTR static inline void req_core(spi_bus_lock_dev_t *dev_handle)
{
spi_bus_lock_t *lock = dev_handle->parent;
// Though `acquired_dev` is critical resource, `dev_handle == lock->acquired_dev`
// is a stable statement unless `acquire_start` or `acquire_end` is called by current
// device.
if (dev_handle == lock->acquiring_dev){
// Set the REQ bit and check BG bits if we are the acquiring processor.
// If the BG bits were not active before, invoke the BG again.
// Avoid competitive risk against the `clear_pend_core`, `acq_dev_bg_active` should be set before
// setting REQ bit.
lock->acq_dev_bg_active = true;
uint32_t status = lock_status_fetch_set(lock, DEV_REQ_MASK(dev_handle));
if ((status & DEV_BG_MASK(dev_handle)) == 0) {
bg_enable(lock); //acquiring processor passed to BG
}
} else {
uint32_t status = lock_status_fetch_set(lock, DEV_REQ_MASK(dev_handle));
if (status == 0) {
bg_enable(lock); //acquiring processor passed to BG
}
}
}
//Set the LOCK bit. Handle related stuff and return true if we become the acquiring processor.
SPI_MASTER_ISR_ATTR static inline bool acquire_core(spi_bus_lock_dev_t *dev_handle)
{
spi_bus_lock_t* lock = dev_handle->parent;
uint32_t status = lock_status_fetch_set(lock, dev_handle->mask & LOCK_MASK);
// Check all bits except WEAK_BG
if ((status & (BG_MASK | LOCK_MASK)) == 0) {
//succeed at once
lock->acquiring_dev = dev_handle;
BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acq_dev_bg_active);
if (status & WEAK_BG_FLAG) {
//Mainly to disable the cache (Weak_BG), that is not able to disable itself
bg_disable(lock);
}
return true;
}
return false;
}
/**
* Find the next acquiring processor according to the status. Will directly change
* the acquiring device if new one found.
*
* Cases:
* - BG should still be the acquiring processor (Return false):
* 1. Acquiring device has active BG bits: out_desired_dev = new acquiring device
* 2. No acquiring device, but BG active: out_desired_dev = randomly pick one device with active BG bits
* - BG should yield to the task (Return true):
* 3. Acquiring device has no active BG bits: out_desired_dev = new acquiring device
* 4. No acquiring device while no active BG bits: out_desired_dev=NULL
*
* Acquiring device task need to be resumed only when case 3.
*
* This scheduling can happen in either task or ISR, so `in_isr` or `bg_active` not touched.
*
* @param lock
* @param status Current status
* @param out_desired_dev Desired device to work next, see above.
*
* @return False if BG should still be the acquiring processor, otherwise True (yield to task).
*/
IRAM_ATTR static inline bool
schedule_core(spi_bus_lock_t *lock, uint32_t status, spi_bus_lock_dev_t **out_desired_dev)
{
spi_bus_lock_dev_t* desired_dev = NULL;
uint32_t lock_bits = (status & LOCK_MASK) >> LOCK_SHIFT;
uint32_t bg_bits = status & BG_MASK;
bg_bits = ((bg_bits >> REQ_SHIFT) | (bg_bits >> PENDING_SHIFT)) & REQ_MASK;
bool bg_yield;
if (lock_bits) {
int dev_id = mask_get_id(lock_bits);
desired_dev = (spi_bus_lock_dev_t *)atomic_load(&lock->dev[dev_id]);
BUS_LOCK_DEBUG_EXECUTE_CHECK(desired_dev);
lock->acquiring_dev = desired_dev;
bg_yield = ((bg_bits & desired_dev->mask) == 0);
lock->acq_dev_bg_active = !bg_yield;
} else {
lock->acq_dev_bg_active = false;
if (bg_bits) {
int dev_id = mask_get_id(bg_bits);
desired_dev = (spi_bus_lock_dev_t *)atomic_load(&lock->dev[dev_id]);
BUS_LOCK_DEBUG_EXECUTE_CHECK(desired_dev);
lock->acquiring_dev = NULL;
bg_yield = false;
} else {
desired_dev = NULL;
lock->acquiring_dev = NULL;
bg_yield = true;
}
}
*out_desired_dev = desired_dev;
return bg_yield;
}
//Clear the LOCK bit and trigger a rescheduling.
IRAM_ATTR static inline void acquire_end_core(spi_bus_lock_dev_t *dev_handle)
{
spi_bus_lock_t* lock = dev_handle->parent;
uint32_t status = lock_status_clear(lock, dev_handle->mask & LOCK_MASK);
spi_bus_lock_dev_t* desired_dev = NULL;
bool invoke_bg = !schedule_core(lock, status, &desired_dev);
if (invoke_bg) {
bg_enable(lock);
} else if (desired_dev) {
resume_dev(desired_dev);
} else if (status & WEAK_BG_FLAG) {
bg_enable(lock);
}
}
// Move the REQ bits to corresponding PEND bits. Must be called by acquiring processor.
// Have no side effects on the acquiring device/processor.
SPI_MASTER_ISR_ATTR static inline void update_pend_core(spi_bus_lock_t *lock, uint32_t status)
{
uint32_t active_req_bits = status & REQ_MASK;
#if PENDING_SHIFT > REQ_SHIFT
uint32_t pending_mask = active_req_bits << (PENDING_SHIFT - REQ_SHIFT);
#else
uint32_t pending_mask = active_req_bits >> (REQ_SHIFT - PENDING_SHIFT);
#endif
// We have to set the PEND bits and then clear the REQ bits, since BG bits are using bitwise OR logic,
// this will not influence the effectiveness of the BG bits of every device.
lock_status_fetch_set(lock, pending_mask);
lock_status_fetch_clear(lock, active_req_bits);
}
// Clear the PEND bit (not REQ bit!) of a device, return the suggestion whether we can try to quit the ISR.
// Lost the acquiring processor immediately when the BG bits for active device are inactive, indiciating by the return value.
// Can be called only when ISR is acting as the acquiring processor.
SPI_MASTER_ISR_ATTR static inline bool clear_pend_core(spi_bus_lock_dev_t *dev_handle)
{
bool finished;
spi_bus_lock_t *lock = dev_handle->parent;
uint32_t pend_mask = DEV_PEND_MASK(dev_handle);
BUS_LOCK_DEBUG_EXECUTE_CHECK(lock_status_fetch(lock) & pend_mask);
uint32_t status = lock_status_clear(lock, pend_mask);
if (lock->acquiring_dev == dev_handle) {
finished = ((status & DEV_REQ_MASK(dev_handle)) == 0);
if (finished) {
lock->acq_dev_bg_active = false;
}
} else {
finished = (status == 0);
}
return finished;
}
// Return true if the ISR has already touched the HW, which means previous operations should
// be terminated first, before we use the HW again. Otherwise return false.
// In either case `in_isr` will be marked as true, until call to `bg_exit_core` with `wip=false` successfully.
SPI_MASTER_ISR_ATTR static inline bool bg_entry_core(spi_bus_lock_t *lock)
{
BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acquiring_dev || lock->acq_dev_bg_active);
/*
* The interrupt is disabled at the entry of ISR to avoid competitive risk as below:
*
* The `esp_intr_enable` will be called (b) after new BG request is queued (a) in the task;
* while `esp_intr_disable` should be called (c) if we check and found the sending queue is empty (d).
* If (c) happens after (d), if things happens in this sequence:
* (d) -> (a) -> (b) -> (c), the interrupt will be disabled while there's pending BG request in the queue.
*
* To avoid this, interrupt is disabled here, and re-enabled later if required. (c) -> (d) -> (a) -> (b) -> revert (c) if !d
*/
bg_disable(lock);
if (lock->in_isr) {
return false;
} else {
lock->in_isr = true;
return true;
}
}
// Handle the conditions of status and interrupt, avoiding the ISR being disabled when there is any new coming BG requests.
// When called with `wip=true`, means the ISR is performing some operations. Will enable the interrupt again and exit unconditionally.
// When called with `wip=false`, will only return `true` when there is no coming BG request. If return value is `false`, the ISR should try again.
// Will not change acquiring device.
SPI_MASTER_ISR_ATTR static inline bool bg_exit_core(spi_bus_lock_t *lock, bool wip, BaseType_t *do_yield)
{
//See comments in `bg_entry_core`, re-enable interrupt disabled in entry if we do need the interrupt
if (wip) {
bg_enable(lock);
BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acquiring_dev || lock->acq_dev_bg_active);
return true;
}
bool ret;
uint32_t status = lock_status_fetch(lock);
if (lock->acquiring_dev) {
if (status & DEV_BG_MASK(lock->acquiring_dev)) {
BUS_LOCK_DEBUG_EXECUTE_CHECK(lock->acq_dev_bg_active);
ret = false;
} else {
// The request may happen any time, even after we fetched the status.
// The value of `acq_dev_bg_active` is random.
resume_dev_in_isr(lock->acquiring_dev, do_yield);
ret = true;
}
} else {
BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acq_dev_bg_active);
ret = !(status & BG_MASK);
}
if (ret) {
//when successfully exit, but no transaction done, mark BG as inactive
lock->in_isr = false;
}
return ret;
}
IRAM_ATTR static inline void dev_wait_prepare(spi_bus_lock_dev_t *dev_handle)
{
xSemaphoreTake(dev_handle->semphr, 0);
}
SPI_MASTER_ISR_ATTR static inline esp_err_t dev_wait(spi_bus_lock_dev_t *dev_handle, TickType_t wait)
{
BaseType_t ret = xSemaphoreTake(dev_handle->semphr, wait);
if (ret == pdFALSE) return ESP_ERR_TIMEOUT;
return ESP_OK;
}
/*******************************************************************************
* Initialization & Deinitialization
******************************************************************************/
esp_err_t spi_bus_init_lock(spi_bus_lock_handle_t *out_lock, const spi_bus_lock_config_t *config)
{
spi_bus_lock_t* lock = (spi_bus_lock_t*)calloc(sizeof(spi_bus_lock_t), 1);
if (lock == NULL) {
return ESP_ERR_NO_MEM;
}
lock_status_init(lock);
lock->acquiring_dev = NULL;
lock->last_dev = NULL;
lock->periph_cs_num = config->cs_num;
lock->host_id = config->host_id;
*out_lock = lock;
return ESP_OK;
}
void spi_bus_deinit_lock(spi_bus_lock_handle_t lock)
{
for (int i = 0; i < DEV_NUM_MAX; i++) {
assert(atomic_load(&lock->dev[i]) == (intptr_t)NULL);
}
free(lock);
}
static int try_acquire_free_dev(spi_bus_lock_t *lock, bool cs_required)
{
if (cs_required) {
int i;
for (i = 0; i < lock->periph_cs_num; i++) {
intptr_t null = (intptr_t) NULL;
//use 1 to occupy the slot, actual setup comes later
if (atomic_compare_exchange_strong(&lock->dev[i], &null, (intptr_t) 1)) {
break;
}
}
return ((i == lock->periph_cs_num)? -1: i);
} else {
int i;
for (i = DEV_NUM_MAX - 1; i >= 0; i--) {
intptr_t null = (intptr_t) NULL;
//use 1 to occupy the slot, actual setup comes later
if (atomic_compare_exchange_strong(&lock->dev[i], &null, (intptr_t) 1)) {
break;
}
}
return i;
}
}
esp_err_t spi_bus_lock_register_dev(spi_bus_lock_handle_t lock, spi_bus_lock_dev_config_t *config,
spi_bus_lock_dev_handle_t *out_dev_handle)
{
int id = try_acquire_free_dev(lock, config->flags & SPI_BUS_LOCK_DEV_FLAG_CS_REQUIRED);
if (id == -1) return ESP_ERR_NOT_SUPPORTED;
spi_bus_lock_dev_t* dev_lock = (spi_bus_lock_dev_t*)heap_caps_calloc(sizeof(spi_bus_lock_dev_t), 1, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (dev_lock == NULL) {
return ESP_ERR_NO_MEM;
}
dev_lock->semphr = xSemaphoreCreateBinary();
if (dev_lock->semphr == NULL) {
free(dev_lock);
atomic_store(&lock->dev[id], (intptr_t)NULL);
return ESP_ERR_NO_MEM;
}
dev_lock->parent = lock;
dev_lock->mask = DEV_MASK(id);
ESP_LOGV(TAG, "device registered on bus %d slot %d.", lock->host_id, id);
atomic_store(&lock->dev[id], (intptr_t)dev_lock);
*out_dev_handle = dev_lock;
return ESP_OK;
}
void spi_bus_lock_unregister_dev(spi_bus_lock_dev_handle_t dev_handle)
{
int id = dev_lock_get_id(dev_handle);
spi_bus_lock_t* lock = dev_handle->parent;
BUS_LOCK_DEBUG_EXECUTE_CHECK(atomic_load(&lock->dev[id]) == (intptr_t)dev_handle);
if (lock->last_dev == dev_handle) lock->last_dev = NULL;
atomic_store(&lock->dev[id], (intptr_t)NULL);
if (dev_handle->semphr) {
vSemaphoreDelete(dev_handle->semphr);
}
free(dev_handle);
}
IRAM_ATTR static inline uint32_t mask_get_id(uint32_t mask)
{
return ID_DEV_MASK(mask);
}
IRAM_ATTR static inline uint32_t dev_lock_get_id(spi_bus_lock_dev_t *dev_lock)
{
return mask_get_id(dev_lock->mask);
}
void spi_bus_lock_set_bg_control(spi_bus_lock_handle_t lock, bg_ctrl_func_t bg_enable, bg_ctrl_func_t bg_disable, void *arg)
{
lock->bg_enable = bg_enable;
lock->bg_disable = bg_disable;
lock->bg_arg = arg;
}
IRAM_ATTR int spi_bus_lock_get_dev_id(spi_bus_lock_dev_handle_t dev_handle)
{
return (dev_handle? dev_lock_get_id(dev_handle): -1);
}
//will be called when cache disabled
IRAM_ATTR bool spi_bus_lock_touch(spi_bus_lock_dev_handle_t dev_handle)
{
spi_bus_lock_dev_t* last_dev = dev_handle->parent->last_dev;
dev_handle->parent->last_dev = dev_handle;
ESP_EARLY_LOGD(TAG, "SPI dev changed from %d to %d",
dev_lock_get_id(last_dev), dev_lock_get_id(dev_handle));
return (dev_handle != last_dev);
}
/*******************************************************************************
* Acquiring service
******************************************************************************/
IRAM_ATTR esp_err_t spi_bus_lock_acquire_start(spi_bus_lock_dev_t *dev_handle, TickType_t wait)
{
LOCK_CHECK(wait == portMAX_DELAY, "timeout other than portMAX_DELAY not supported", ESP_ERR_INVALID_ARG);
spi_bus_lock_t* lock = dev_handle->parent;
// Clear the semaphore before checking
dev_wait_prepare(dev_handle);
if (!acquire_core(dev_handle)) {
//block until becoming the acquiring processor (help by previous acquiring processor)
esp_err_t err = dev_wait(dev_handle, wait);
//TODO: add timeout handling here.
if (err != ESP_OK) return err;
}
ESP_LOGV(TAG, "dev %d acquired.", dev_lock_get_id(dev_handle));
BUS_LOCK_DEBUG_EXECUTE_CHECK(lock->acquiring_dev == dev_handle);
//When arrives at here, requests of this device should already be handled
uint32_t status = lock_status_fetch(lock);
(void) status;
BUS_LOCK_DEBUG_EXECUTE_CHECK((status & DEV_BG_MASK(dev_handle)) == 0);
return ESP_OK;
}
IRAM_ATTR esp_err_t spi_bus_lock_acquire_end(spi_bus_lock_dev_t *dev_handle)
{
//release the bus
spi_bus_lock_t* lock = dev_handle->parent;
LOCK_CHECK(lock->acquiring_dev == dev_handle, "Cannot release a lock that hasn't been acquired.", ESP_ERR_INVALID_STATE);
acquire_end_core(dev_handle);
ESP_LOGV(TAG, "dev %d released.", dev_lock_get_id(dev_handle));
return ESP_OK;
}
SPI_MASTER_ISR_ATTR spi_bus_lock_dev_handle_t spi_bus_lock_get_acquiring_dev(spi_bus_lock_t *lock)
{
return lock->acquiring_dev;
}
/*******************************************************************************
* BG (background operation) service
******************************************************************************/
SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_entry(spi_bus_lock_t* lock)
{
return bg_entry_core(lock);
}
SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_exit(spi_bus_lock_t* lock, bool wip, BaseType_t* do_yield)
{
return bg_exit_core(lock, wip, do_yield);
}
SPI_MASTER_ATTR esp_err_t spi_bus_lock_bg_request(spi_bus_lock_dev_t *dev_handle)
{
req_core(dev_handle);
return ESP_OK;
}
IRAM_ATTR esp_err_t spi_bus_lock_wait_bg_done(spi_bus_lock_dev_handle_t dev_handle, TickType_t wait)
{
spi_bus_lock_t *lock = dev_handle->parent;
LOCK_CHECK(lock->acquiring_dev == dev_handle, "Cannot wait for a device that is not acquired", ESP_ERR_INVALID_STATE);
LOCK_CHECK(wait == portMAX_DELAY, "timeout other than portMAX_DELAY not supported", ESP_ERR_INVALID_ARG);
// If no BG bits active, skip quickly. This is ensured by `spi_bus_lock_wait_bg_done`
// cannot be executed with `bg_request` on the same device concurrently.
if (lock_status_fetch(lock) & DEV_BG_MASK(dev_handle)) {
// Clear the semaphore before checking
dev_wait_prepare(dev_handle);
if (lock_status_fetch(lock) & DEV_BG_MASK(dev_handle)) {
//block until becoming the acquiring processor (help by previous acquiring processor)
esp_err_t err = dev_wait(dev_handle, wait);
//TODO: add timeout handling here.
if (err != ESP_OK) return err;
}
}
BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acq_dev_bg_active);
BUS_LOCK_DEBUG_EXECUTE_CHECK((lock_status_fetch(lock) & DEV_BG_MASK(dev_handle)) == 0);
return ESP_OK;
}
SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_clear_req(spi_bus_lock_dev_t *dev_handle)
{
bool finished = clear_pend_core(dev_handle);
ESP_EARLY_LOGV(TAG, "dev %d served from bg.", dev_lock_get_id(dev_handle));
return finished;
}
SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_check_dev_acq(spi_bus_lock_t *lock,
spi_bus_lock_dev_handle_t *out_dev_lock)
{
BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acquiring_dev);
uint32_t status = lock_status_fetch(lock);
return schedule_core(lock, status, out_dev_lock);
}
SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_check_dev_req(spi_bus_lock_dev_t *dev_lock)
{
spi_bus_lock_t* lock = dev_lock->parent;
uint32_t status = lock_status_fetch(lock);
uint32_t dev_status = status & dev_lock->mask;
// move REQ bits of all device to corresponding PEND bits.
// To reduce executing time, only done when the REQ bit of the calling device is set.
if (dev_status & REQ_MASK) {
update_pend_core(lock, status);
return true;
} else {
return dev_status & PEND_MASK;
}
}
SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_req_exist(spi_bus_lock_t *lock)
{
uint32_t status = lock_status_fetch(lock);
return status & BG_MASK;
}
/*******************************************************************************
* Static variables of the locks of the main flash
******************************************************************************/
static StaticSemaphore_t main_flash_semphr;
static spi_bus_lock_dev_t lock_main_flash_dev;
static spi_bus_lock_t main_spi_bus_lock = {
/*
* the main bus cache is permanently required, this flag is set here and never clear so that the
* cache will always be enabled if acquiring devices yield.
*/
.status = ATOMIC_VAR_INIT(WEAK_BG_FLAG),
.acquiring_dev = NULL,
.dev = {ATOMIC_VAR_INIT((intptr_t)&lock_main_flash_dev)},
.new_req = 0,
.periph_cs_num = SOC_SPI_PERIPH_CS_NUM(0),
};
static spi_bus_lock_dev_t lock_main_flash_dev = {
.semphr = NULL,
.parent = &main_spi_bus_lock,
.mask = DEV_MASK(0),
};
const spi_bus_lock_handle_t g_main_spi_bus_lock = &main_spi_bus_lock;
const spi_bus_lock_dev_handle_t g_spi_lock_main_flash_dev = &lock_main_flash_dev;
esp_err_t spi_bus_lock_init_main_dev(void)
{
spi_bus_main_set_lock(g_main_spi_bus_lock);
g_spi_lock_main_flash_dev->semphr = xSemaphoreCreateBinaryStatic(&main_flash_semphr);
if (g_spi_lock_main_flash_dev->semphr == NULL) {
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}

View file

@ -31,6 +31,7 @@
#include "stdatomic.h" #include "stdatomic.h"
#include "hal/spi_hal.h" #include "hal/spi_hal.h"
static const char *SPI_TAG = "spi"; static const char *SPI_TAG = "spi";
#define SPI_CHECK(a, str, ret_val) do { \ #define SPI_CHECK(a, str, ret_val) do { \
@ -53,6 +54,23 @@ typedef struct spi_device_t spi_device_t;
#define DMA_CHANNEL_ENABLED(dma_chan) (BIT(dma_chan-1)) #define DMA_CHANNEL_ENABLED(dma_chan) (BIT(dma_chan-1))
typedef struct {
int host_id;
spi_destroy_func_t destroy_func;
void* destroy_arg;
spi_bus_attr_t bus_attr;
} spicommon_bus_context_t;
#define MAIN_BUS_DEFAULT() { \
.host_id = 0, \
.bus_attr = { \
.dma_chan = 0, \
.max_transfer_sz = SOC_SPI_MAXIMUM_BUFFER_SIZE, \
.dma_desc_num= 0, \
}, \
}
//Periph 1 is 'claimed' by SPI flash code. //Periph 1 is 'claimed' by SPI flash code.
static atomic_bool spi_periph_claimed[SOC_SPI_PERIPH_NUM] = { ATOMIC_VAR_INIT(true), ATOMIC_VAR_INIT(false), ATOMIC_VAR_INIT(false), static atomic_bool spi_periph_claimed[SOC_SPI_PERIPH_NUM] = { ATOMIC_VAR_INIT(true), ATOMIC_VAR_INIT(false), ATOMIC_VAR_INIT(false),
#if SOC_SPI_PERIPH_NUM >= 4 #if SOC_SPI_PERIPH_NUM >= 4
@ -63,6 +81,9 @@ static const char* spi_claiming_func[3] = {NULL, NULL, NULL};
static uint8_t spi_dma_chan_enabled = 0; static uint8_t spi_dma_chan_enabled = 0;
static portMUX_TYPE spi_dma_spinlock = portMUX_INITIALIZER_UNLOCKED; static portMUX_TYPE spi_dma_spinlock = portMUX_INITIALIZER_UNLOCKED;
static spicommon_bus_context_t s_mainbus = MAIN_BUS_DEFAULT();
static spicommon_bus_context_t* bus_ctx[SOC_SPI_PERIPH_NUM] = {&s_mainbus};
//Returns true if this peripheral is successfully claimed, false if otherwise. //Returns true if this peripheral is successfully claimed, false if otherwise.
bool spicommon_periph_claim(spi_host_device_t host, const char* source) bool spicommon_periph_claim(spi_host_device_t host, const char* source)
@ -416,6 +437,173 @@ bool spicommon_bus_using_iomux(spi_host_device_t host)
return true; return true;
} }
void spi_bus_main_set_lock(spi_bus_lock_handle_t lock)
{
bus_ctx[0]->bus_attr.lock = lock;
}
spi_bus_lock_handle_t spi_bus_lock_get_by_id(spi_host_device_t host_id)
{
return bus_ctx[host_id]->bus_attr.lock;
}
static inline bool is_valid_host(spi_host_device_t host)
{
return host >= SPI1_HOST && host <= SPI3_HOST;
}
esp_err_t spi_bus_initialize(spi_host_device_t host_id, const spi_bus_config_t *bus_config, int dma_chan)
{
esp_err_t err = ESP_OK;
spicommon_bus_context_t *ctx = NULL;
spi_bus_attr_t *bus_attr = NULL;
SPI_CHECK(is_valid_host(host_id), "invalid host_id", ESP_ERR_INVALID_ARG);
SPI_CHECK(bus_ctx[host_id] == NULL, "SPI bus already initialized.", ESP_ERR_INVALID_STATE);
#ifdef CONFIG_IDF_TARGET_ESP32
SPI_CHECK( dma_chan >= 0 && dma_chan <= 2, "invalid dma channel", ESP_ERR_INVALID_ARG );
#elif CONFIG_IDF_TARGET_ESP32S2
SPI_CHECK( dma_chan == 0 || dma_chan == host_id, "invalid dma channel", ESP_ERR_INVALID_ARG );
#endif
SPI_CHECK((bus_config->intr_flags & (ESP_INTR_FLAG_HIGH|ESP_INTR_FLAG_EDGE|ESP_INTR_FLAG_INTRDISABLED))==0, "intr flag not allowed", ESP_ERR_INVALID_ARG);
#ifndef CONFIG_SPI_MASTER_ISR_IN_IRAM
SPI_CHECK((bus_config->intr_flags & ESP_INTR_FLAG_IRAM)==0, "ESP_INTR_FLAG_IRAM should be disabled when CONFIG_SPI_MASTER_ISR_IN_IRAM is not set.", ESP_ERR_INVALID_ARG);
#endif
bool spi_chan_claimed = spicommon_periph_claim(host_id, "spi master");
SPI_CHECK(spi_chan_claimed, "host_id already in use", ESP_ERR_INVALID_STATE);
if (dma_chan != 0) {
bool dma_chan_claimed=spicommon_dma_chan_claim(dma_chan);
if (!dma_chan_claimed) {
spicommon_periph_free(host_id);
SPI_CHECK(false, "dma channel already in use", ESP_ERR_INVALID_STATE);
}
}
//clean and initialize the context
ctx = (spicommon_bus_context_t*)malloc(sizeof(spicommon_bus_context_t));
if (!ctx) {
err = ESP_ERR_NO_MEM;
goto cleanup;
}
*ctx = (spicommon_bus_context_t) {
.host_id = host_id,
.bus_attr = {
.bus_cfg = *bus_config,
.dma_chan = dma_chan,
},
};
bus_attr = &ctx->bus_attr;
if (dma_chan == 0) {
bus_attr->max_transfer_sz = SOC_SPI_MAXIMUM_BUFFER_SIZE;
bus_attr->dma_desc_num = 0;
} else {
//See how many dma descriptors we need and allocate them
int dma_desc_ct = lldesc_get_required_num(bus_config->max_transfer_sz);
if (dma_desc_ct == 0) dma_desc_ct = 1; //default to 4k when max is not given
bus_attr->max_transfer_sz = dma_desc_ct * LLDESC_MAX_NUM_PER_DESC;
bus_attr->dmadesc_tx = heap_caps_malloc(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA);
bus_attr->dmadesc_rx = heap_caps_malloc(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA);
if (bus_attr->dmadesc_tx == NULL || bus_attr->dmadesc_rx == NULL) {
err = ESP_ERR_NO_MEM;
goto cleanup;
}
bus_attr->dma_desc_num = dma_desc_ct;
}
spi_bus_lock_config_t lock_config = {
.host_id = host_id,
.cs_num = SOC_SPI_PERIPH_CS_NUM(host_id),
};
err = spi_bus_init_lock(&bus_attr->lock, &lock_config);
if (err != ESP_OK) {
goto cleanup;
}
#ifdef CONFIG_PM_ENABLE
err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "spi_master",
&bus_attr->pm_lock);
if (err != ESP_OK) {
goto cleanup;
}
#endif //CONFIG_PM_ENABLE
err = spicommon_bus_initialize_io(host_id, bus_config, dma_chan, SPICOMMON_BUSFLAG_MASTER | bus_config->flags, &bus_attr->flags);
if (err != ESP_OK) {
goto cleanup;
}
bus_ctx[host_id] = ctx;
return ESP_OK;
cleanup:
if (bus_attr) {
#ifdef CONFIG_PM_ENABLE
esp_pm_lock_delete(bus_attr->pm_lock);
#endif
if (bus_attr->lock) {
spi_bus_deinit_lock(bus_attr->lock);
}
free(bus_attr->dmadesc_tx);
free(bus_attr->dmadesc_rx);
}
free(ctx);
if (dma_chan) {
spicommon_dma_chan_free(dma_chan);
}
spicommon_periph_free(host_id);
return err;
}
const spi_bus_attr_t* spi_bus_get_attr(spi_host_device_t host_id)
{
if (bus_ctx[host_id] == NULL) return NULL;
return &bus_ctx[host_id]->bus_attr;
}
esp_err_t spi_bus_free(spi_host_device_t host_id)
{
esp_err_t err = ESP_OK;
spicommon_bus_context_t* ctx = bus_ctx[host_id];
spi_bus_attr_t* bus_attr = &ctx->bus_attr;
if (ctx->destroy_func) {
err = ctx->destroy_func(ctx->destroy_arg);
}
spicommon_bus_free_io_cfg(&bus_attr->bus_cfg);
#ifdef CONFIG_PM_ENABLE
esp_pm_lock_delete(bus_attr->pm_lock);
#endif
spi_bus_deinit_lock(bus_attr->lock);
free(bus_attr->dmadesc_rx);
free(bus_attr->dmadesc_tx);
if (bus_attr->dma_chan > 0) {
spicommon_dma_chan_free (bus_attr->dma_chan);
}
spicommon_periph_free(host_id);
free(ctx);
bus_ctx[host_id] = NULL;
return err;
}
esp_err_t spi_bus_register_destroy_func(spi_host_device_t host_id,
spi_destroy_func_t f, void *arg)
{
bus_ctx[host_id]->destroy_func = f;
bus_ctx[host_id]->destroy_arg = arg;
return ESP_OK;
}
/* /*
Code for workaround for DMA issue in ESP32 v0/v1 silicon Code for workaround for DMA issue in ESP32 v0/v1 silicon
*/ */

File diff suppressed because it is too large Load diff

View file

@ -41,8 +41,6 @@ static const char *SPI_TAG = "spi_slave";
return (ret_val); \ return (ret_val); \
} }
#define VALID_HOST(x) (x > SPI1_HOST && x <= SPI3_HOST)
#ifdef CONFIG_SPI_SLAVE_ISR_IN_IRAM #ifdef CONFIG_SPI_SLAVE_ISR_IN_IRAM
#define SPI_SLAVE_ISR_ATTR IRAM_ATTR #define SPI_SLAVE_ISR_ATTR IRAM_ATTR
#else #else
@ -75,6 +73,16 @@ static spi_slave_t *spihost[SOC_SPI_PERIPH_NUM];
static void IRAM_ATTR spi_intr(void *arg); static void IRAM_ATTR spi_intr(void *arg);
static inline bool is_valid_host(spi_host_device_t host)
{
#if CONFIG_IDF_TARGET_ESP32
return host >= SPI1_HOST && host <= SPI3_HOST;
#elif CONFIG_IDF_TARGET_ESP32S2
// SPI_HOST (SPI1_HOST) is not supported by the SPI Slave driver on ESP32-S2
return host >= SPI2_HOST && host <= SPI3_HOST;
#endif
}
static inline bool bus_is_iomux(spi_slave_t *host) static inline bool bus_is_iomux(spi_slave_t *host)
{ {
return host->flags&SPICOMMON_BUSFLAG_IOMUX_PINS; return host->flags&SPICOMMON_BUSFLAG_IOMUX_PINS;
@ -102,7 +110,7 @@ esp_err_t spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t *b
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
esp_err_t err; esp_err_t err;
//We only support HSPI/VSPI, period. //We only support HSPI/VSPI, period.
SPI_CHECK(VALID_HOST(host), "invalid host", ESP_ERR_INVALID_ARG); SPI_CHECK(is_valid_host(host), "invalid host", ESP_ERR_INVALID_ARG);
#if defined(CONFIG_IDF_TARGET_ESP32) #if defined(CONFIG_IDF_TARGET_ESP32)
SPI_CHECK( dma_chan >= 0 && dma_chan <= 2, "invalid dma channel", ESP_ERR_INVALID_ARG ); SPI_CHECK( dma_chan >= 0 && dma_chan <= 2, "invalid dma channel", ESP_ERR_INVALID_ARG );
#elif defined(CONFIG_IDF_TARGET_ESP32S2) #elif defined(CONFIG_IDF_TARGET_ESP32S2)
@ -224,7 +232,7 @@ cleanup:
esp_err_t spi_slave_free(spi_host_device_t host) esp_err_t spi_slave_free(spi_host_device_t host)
{ {
SPI_CHECK(VALID_HOST(host), "invalid host", ESP_ERR_INVALID_ARG); SPI_CHECK(is_valid_host(host), "invalid host", ESP_ERR_INVALID_ARG);
SPI_CHECK(spihost[host], "host not slave", ESP_ERR_INVALID_ARG); SPI_CHECK(spihost[host], "host not slave", ESP_ERR_INVALID_ARG);
if (spihost[host]->trans_queue) vQueueDelete(spihost[host]->trans_queue); if (spihost[host]->trans_queue) vQueueDelete(spihost[host]->trans_queue);
if (spihost[host]->ret_queue) vQueueDelete(spihost[host]->ret_queue); if (spihost[host]->ret_queue) vQueueDelete(spihost[host]->ret_queue);
@ -248,7 +256,7 @@ esp_err_t spi_slave_free(spi_host_device_t host)
esp_err_t SPI_SLAVE_ATTR spi_slave_queue_trans(spi_host_device_t host, const spi_slave_transaction_t *trans_desc, TickType_t ticks_to_wait) esp_err_t SPI_SLAVE_ATTR spi_slave_queue_trans(spi_host_device_t host, const spi_slave_transaction_t *trans_desc, TickType_t ticks_to_wait)
{ {
BaseType_t r; BaseType_t r;
SPI_CHECK(VALID_HOST(host), "invalid host", ESP_ERR_INVALID_ARG); SPI_CHECK(is_valid_host(host), "invalid host", ESP_ERR_INVALID_ARG);
SPI_CHECK(spihost[host], "host not slave", ESP_ERR_INVALID_ARG); SPI_CHECK(spihost[host], "host not slave", ESP_ERR_INVALID_ARG);
SPI_CHECK(spihost[host]->dma_chan == 0 || trans_desc->tx_buffer==NULL || esp_ptr_dma_capable(trans_desc->tx_buffer), SPI_CHECK(spihost[host]->dma_chan == 0 || trans_desc->tx_buffer==NULL || esp_ptr_dma_capable(trans_desc->tx_buffer),
"txdata not in DMA-capable memory", ESP_ERR_INVALID_ARG); "txdata not in DMA-capable memory", ESP_ERR_INVALID_ARG);
@ -268,7 +276,7 @@ esp_err_t SPI_SLAVE_ATTR spi_slave_queue_trans(spi_host_device_t host, const spi
esp_err_t SPI_SLAVE_ATTR spi_slave_get_trans_result(spi_host_device_t host, spi_slave_transaction_t **trans_desc, TickType_t ticks_to_wait) esp_err_t SPI_SLAVE_ATTR spi_slave_get_trans_result(spi_host_device_t host, spi_slave_transaction_t **trans_desc, TickType_t ticks_to_wait)
{ {
BaseType_t r; BaseType_t r;
SPI_CHECK(VALID_HOST(host), "invalid host", ESP_ERR_INVALID_ARG); SPI_CHECK(is_valid_host(host), "invalid host", ESP_ERR_INVALID_ARG);
SPI_CHECK(spihost[host], "host not slave", ESP_ERR_INVALID_ARG); SPI_CHECK(spihost[host], "host not slave", ESP_ERR_INVALID_ARG);
r = xQueueReceive(spihost[host]->ret_queue, (void *)trans_desc, ticks_to_wait); r = xQueueReceive(spihost[host]->ret_queue, (void *)trans_desc, ticks_to_wait);
if (!r) return ESP_ERR_TIMEOUT; if (!r) return ESP_ERR_TIMEOUT;

View file

@ -0,0 +1,344 @@
#include "sdkconfig.h"
#include "esp_log.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_flash_spi_init.h"
#include "test/test_common_spi.h"
#include "unity.h"
#if CONFIG_IDF_TARGET_ESP32
// The VSPI pins on UT_T1_ESP_FLASH are connected to a external flash
#define TEST_BUS_PIN_NUM_MISO VSPI_IOMUX_PIN_NUM_MISO
#define TEST_BUS_PIN_NUM_MOSI VSPI_IOMUX_PIN_NUM_MOSI
#define TEST_BUS_PIN_NUM_CLK VSPI_IOMUX_PIN_NUM_CLK
#define TEST_BUS_PIN_NUM_CS VSPI_IOMUX_PIN_NUM_CS
#define TEST_BUS_PIN_NUM_WP VSPI_IOMUX_PIN_NUM_WP
#define TEST_BUS_PIN_NUM_HD VSPI_IOMUX_PIN_NUM_HD
#elif CONFIG_IDF_TARGET_ESP32S2
#define TEST_BUS_PIN_NUM_MISO FSPI_IOMUX_PIN_NUM_MISO
#define TEST_BUS_PIN_NUM_MOSI FSPI_IOMUX_PIN_NUM_MOSI
#define TEST_BUS_PIN_NUM_CLK FSPI_IOMUX_PIN_NUM_CLK
#define TEST_BUS_PIN_NUM_CS FSPI_IOMUX_PIN_NUM_CS
#define TEST_BUS_PIN_NUM_WP FSPI_IOMUX_PIN_NUM_WP
#define TEST_BUS_PIN_NUM_HD FSPI_IOMUX_PIN_NUM_HD
#endif
typedef struct {
union {
spi_device_handle_t handle;
esp_flash_t* chip;
};
bool finished;
} task_context_t;
#ifndef CONFIG_ESP32_SPIRAM_SUPPORT
const static char TAG[] = "test_spi";
void spi_task1(void* arg)
{
//task1 send 50 polling transactions, acquire the bus and send another 50
int count=0;
spi_transaction_t t = {
.flags = SPI_TRANS_USE_TXDATA,
.tx_data = { 0x80, 0x12, 0x34, 0x56 },
.length = 4*8,
};
spi_device_handle_t handle = ((task_context_t*)arg)->handle;
for( int j = 0; j < 50; j ++ ) {
TEST_ESP_OK(spi_device_polling_transmit( handle, &t ));
ESP_LOGI(TAG, "task1:%d", count++ );
}
TEST_ESP_OK(spi_device_acquire_bus( handle, portMAX_DELAY ));
for( int j = 0; j < 50; j ++ ) {
TEST_ESP_OK(spi_device_polling_transmit( handle, &t ));
ESP_LOGI(TAG, "task1:%d", count++ );
}
spi_device_release_bus(handle);
ESP_LOGI(TAG, "task1 terminates");
((task_context_t*)arg)->finished = true;
vTaskDelete(NULL);
}
void spi_task2(void* arg)
{
int count=0;
//task2 acquire the bus, send 50 polling transactions and then 50 non-polling
spi_transaction_t t = {
.flags = SPI_TRANS_USE_TXDATA,
.tx_data = { 0x80, 0x12, 0x34, 0x56 },
.length = 4*8,
};
spi_transaction_t *ret_t;
spi_device_handle_t handle = ((task_context_t*)arg)->handle;
TEST_ESP_OK(spi_device_acquire_bus( handle, portMAX_DELAY ));
for (int i = 0; i < 50; i ++) {
TEST_ESP_OK(spi_device_polling_transmit(handle, &t));
ESP_LOGI( TAG, "task2: %d", count++ );
}
for( int j = 0; j < 50; j ++ ) {
TEST_ESP_OK(spi_device_queue_trans(handle, &t, portMAX_DELAY));
}
for( int j = 0; j < 50; j ++ ) {
TEST_ESP_OK(spi_device_get_trans_result(handle, &ret_t, portMAX_DELAY));
assert(ret_t == &t);
ESP_LOGI( TAG, "task2: %d", count++ );
}
spi_device_release_bus(handle);
vTaskDelay(1);
ESP_LOGI(TAG, "task2 terminates");
((task_context_t*)arg)->finished = true;
vTaskDelete(NULL);
}
void spi_task3(void* arg)
{
//task3 send 30 polling transactions, acquire the bus, send 20 polling transactions and then 50 non-polling
int count=0;
spi_transaction_t t = {
.flags = SPI_TRANS_USE_TXDATA,
.tx_data = { 0x80, 0x12, 0x34, 0x56 },
.length = 4*8,
};
spi_transaction_t *ret_t;
spi_device_handle_t handle = ((task_context_t*)arg)->handle;
for (int i = 0; i < 30; i ++) {
TEST_ESP_OK(spi_device_polling_transmit(handle, &t));
ESP_LOGI( TAG, "task3: %d", count++ );
}
TEST_ESP_OK(spi_device_acquire_bus( handle, portMAX_DELAY ));
for (int i = 0; i < 20; i ++) {
TEST_ESP_OK(spi_device_polling_transmit(handle, &t));
ESP_LOGI( TAG, "task3: %d", count++ );
}
for (int j = 0; j < 50; j++) {
TEST_ESP_OK(spi_device_queue_trans(handle, &t, portMAX_DELAY));
}
for (int j = 0; j < 50; j++) {
TEST_ESP_OK(spi_device_get_trans_result(handle, &ret_t, portMAX_DELAY));
assert(ret_t == &t);
ESP_LOGI(TAG, "task3: %d", count++);
}
spi_device_release_bus(handle);
ESP_LOGI(TAG, "task3 terminates");
((task_context_t*)arg)->finished = true;
vTaskDelete(NULL);
}
static void write_large_buffer(esp_flash_t *chip, const esp_partition_t *part, const uint8_t *source, size_t length)
{
printf("Erasing chip %p, %d bytes\n", chip, length);
TEST_ESP_OK(esp_flash_erase_region(chip, part->address, (length + SPI_FLASH_SEC_SIZE) & ~(SPI_FLASH_SEC_SIZE - 1)) );
printf("Writing chip %p, %d bytes from source %p\n", chip, length, source);
// note writing to unaligned address
TEST_ESP_OK(esp_flash_write(chip, source, part->address + 1, length) );
printf("Write done.\n");
}
static void read_and_check(esp_flash_t *chip, const esp_partition_t *part, const uint8_t *source, size_t length)
{
printf("Checking chip %p, %d bytes\n", chip, length);
uint8_t *buf = malloc(length);
TEST_ASSERT_NOT_NULL(buf);
TEST_ESP_OK(esp_flash_read(chip, buf, part->address + 1, length) );
TEST_ASSERT_EQUAL_HEX8_ARRAY(source, buf, length);
free(buf);
// check nothing was written at beginning or end
uint8_t ends[8];
TEST_ESP_OK(esp_flash_read(chip, ends, part->address, sizeof(ends)) );
TEST_ASSERT_EQUAL_HEX8(0xFF, ends[0]);
TEST_ASSERT_EQUAL_HEX8(source[0], ends[1]);
TEST_ESP_OK(esp_flash_read(chip, ends, part->address + length, sizeof(ends)) );
TEST_ASSERT_EQUAL_HEX8(source[length - 1], ends[0]);
TEST_ASSERT_EQUAL_HEX8(0xFF, ends[1]);
TEST_ASSERT_EQUAL_HEX8(0xFF, ends[2]);
TEST_ASSERT_EQUAL_HEX8(0xFF, ends[3]);
}
void spi_task4(void* arg)
{
esp_flash_t *chip = ((task_context_t*)arg)->chip;
// buffer in RAM
const int test_len = 16400;
uint8_t *source_buf = heap_caps_malloc(test_len, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
TEST_ASSERT_NOT_NULL(source_buf);
srand(676);
for (int i = 0; i < test_len; i++) {
source_buf[i] = rand();
}
ESP_LOGI(TAG, "Testing chip %p...", chip);
const esp_partition_t *part = get_test_data_partition();
TEST_ASSERT(part->size > test_len + 2 + SPI_FLASH_SEC_SIZE);
write_large_buffer(chip, part, source_buf, test_len);
read_and_check(chip, part, source_buf, test_len);
free(source_buf);
ESP_LOGI(TAG, "task4 terminates");
((task_context_t*)arg)->finished = true;
vTaskDelete(NULL);
}
static void test_bus_lock(bool test_flash)
{
task_context_t context1={};
task_context_t context2={};
task_context_t context3={};
task_context_t context4={};
TaskHandle_t task1, task2, task3, task4;
esp_err_t ret;
spi_bus_config_t buscfg=SPI_BUS_TEST_DEFAULT_CONFIG();
buscfg.miso_io_num = TEST_BUS_PIN_NUM_MISO;
buscfg.mosi_io_num = TEST_BUS_PIN_NUM_MOSI;
buscfg.sclk_io_num = TEST_BUS_PIN_NUM_CLK;
spi_device_interface_config_t devcfg=SPI_DEVICE_TEST_DEFAULT_CONFIG();
devcfg.queue_size = 100;
//Initialize the SPI bus and 3 devices
ret=spi_bus_initialize(TEST_SPI_HOST, &buscfg, 1);
TEST_ESP_OK(ret);
ret=spi_bus_add_device(TEST_SPI_HOST, &devcfg, &context1.handle);
TEST_ESP_OK(ret);
ret=spi_bus_add_device(TEST_SPI_HOST, &devcfg, &context2.handle);
TEST_ESP_OK(ret);
//only have 3 cs pins, leave one for the flash
devcfg.spics_io_num = -1;
ret=spi_bus_add_device(TEST_SPI_HOST, &devcfg, &context3.handle);
TEST_ESP_OK(ret);
esp_flash_spi_device_config_t flash_cfg = {
.host_id = TEST_SPI_HOST,
.cs_id = 2,
.cs_io_num = TEST_BUS_PIN_NUM_CS,
.io_mode = SPI_FLASH_DIO,
.speed = ESP_FLASH_5MHZ,
.input_delay_ns = 0,
};
//Clamp the WP and HD pins to VDD to make it work in DIO mode
gpio_set_direction(TEST_BUS_PIN_NUM_HD, GPIO_MODE_OUTPUT);
gpio_set_direction(TEST_BUS_PIN_NUM_WP, GPIO_MODE_OUTPUT);
gpio_set_level(TEST_BUS_PIN_NUM_HD, 1);
gpio_set_level(TEST_BUS_PIN_NUM_WP, 1);
esp_flash_t *chip;
(void) chip;
if (test_flash) {
ret = spi_bus_add_flash_device(&chip, &flash_cfg);
TEST_ESP_OK(ret);
ret = esp_flash_init(chip);
TEST_ESP_OK(ret);
context4.chip = chip;
}
ESP_LOGI(TAG, "Start testing...");
xTaskCreate( spi_task1, "task1", 2048, &context1, 0, &task1 );
xTaskCreate( spi_task2, "task2", 2048, &context2, 0, &task2 );
xTaskCreate( spi_task3, "task3", 2048, &context3, 0, &task3 );
if (test_flash) {
xTaskCreate( spi_task4, "task4", 2048, &context4, 0, &task4 );
} else {
context4.finished = true;
}
for(;;){
vTaskDelay(10);
if (context1.finished && context2.finished && context3.finished && context4.finished) break;
}
TEST_ESP_OK(spi_bus_remove_device(context1.handle));
TEST_ESP_OK(spi_bus_remove_device(context2.handle));
TEST_ESP_OK(spi_bus_remove_device(context3.handle));
if (test_flash) {
TEST_ESP_OK(spi_bus_remove_flash_device(chip));
}
TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST) );
}
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2)
//no runners
TEST_CASE("spi bus lock, with flash","[spi][test_env=UT_T1_ESP_FLASH]")
{
test_bus_lock(true);
}
#endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2)
TEST_CASE("spi bus lock","[spi]")
{
test_bus_lock(false);
}
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2)
//SPI1 not supported by driver
static IRAM_ATTR esp_err_t test_polling_send(spi_device_handle_t handle)
{
for (int i = 0; i < 10; i++) {
spi_transaction_t trans = {
.length = 16,
.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA,
};
esp_err_t err = spi_device_polling_transmit(handle, &trans);
if (err != ESP_OK) {
return err;
}
}
return ESP_OK;
}
static IRAM_ATTR NOINLINE_ATTR void test_acquire(spi_device_handle_t handle)
{
esp_err_t err = spi_device_acquire_bus(handle, portMAX_DELAY);
if (err == ESP_OK) {
err = test_polling_send(handle);
spi_device_release_bus(handle);
}
TEST_ESP_OK(err);
}
TEST_CASE("spi master can be used on SPI1", "[spi]")
{
spi_device_interface_config_t dev_cfg = {
.mode = 1,
.clock_speed_hz = 1*1000*1000,
.spics_io_num = -1,
.queue_size = 1,
};
spi_device_handle_t handle;
esp_err_t err;
err = spi_bus_add_device(SPI1_HOST, &dev_cfg, &handle);
TEST_ESP_OK(err);
err = test_polling_send(handle);
TEST_ESP_OK(err);
test_acquire(handle);
}
#endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2)
//TODO: add a case when a non-polling transaction happened in the bus-acquiring time and then release the bus then queue a new trans
#endif

View file

@ -26,6 +26,7 @@
#include "soc/soc_memory_layout.h" #include "soc/soc_memory_layout.h"
#include "driver/spi_common_internal.h" #include "driver/spi_common_internal.h"
const static char TAG[] = "test_spi"; const static char TAG[] = "test_spi";
static void check_spi_pre_n_for(int clk, int pre, int n) static void check_spi_pre_n_for(int clk, int pre, int n)
@ -773,7 +774,7 @@ void test_cmd_addr(spi_slave_task_context_t *slave_context, bool lsb_first)
}; };
ESP_LOGI( MASTER_TAG, "===== test%d =====", i ); ESP_LOGI( MASTER_TAG, "===== test%d =====", i );
ESP_LOGI(MASTER_TAG, "cmd_bits %d, addr_bits: %d", cmd_bits, addr_bits); ESP_LOGI(MASTER_TAG, "cmd_bits: %d, addr_bits: %d", cmd_bits, addr_bits);
TEST_ESP_OK(spi_device_transmit(spi, (spi_transaction_t*)&trans)); TEST_ESP_OK(spi_device_transmit(spi, (spi_transaction_t*)&trans));
//wait for both master and slave end //wait for both master and slave end
@ -984,7 +985,7 @@ static void sorted_array_insert(uint32_t* array, int* size, uint32_t item)
#define TEST_TIMES 11 #define TEST_TIMES 11
static IRAM_ATTR void spi_transmit_measure(spi_device_handle_t spi, spi_transaction_t* trans, uint32_t* t_flight) static IRAM_ATTR NOINLINE_ATTR void spi_transmit_measure(spi_device_handle_t spi, spi_transaction_t* trans, uint32_t* t_flight)
{ {
RECORD_TIME_PREPARE(); RECORD_TIME_PREPARE();
spi_device_transmit(spi, trans); // prime the flash cache spi_device_transmit(spi, trans); // prime the flash cache
@ -993,7 +994,7 @@ static IRAM_ATTR void spi_transmit_measure(spi_device_handle_t spi, spi_transact
RECORD_TIME_END(t_flight); RECORD_TIME_END(t_flight);
} }
static IRAM_ATTR void spi_transmit_polling_measure(spi_device_handle_t spi, spi_transaction_t* trans, uint32_t* t_flight) static IRAM_ATTR NOINLINE_ATTR void spi_transmit_polling_measure(spi_device_handle_t spi, spi_transaction_t* trans, uint32_t* t_flight)
{ {
spi_flash_disable_interrupts_caches_and_other_cpu(); //this can test the code are all in the IRAM at the same time spi_flash_disable_interrupts_caches_and_other_cpu(); //this can test the code are all in the IRAM at the same time
RECORD_TIME_PREPARE(); RECORD_TIME_PREPARE();
@ -1031,7 +1032,9 @@ TEST_CASE("spi_speed","[spi]")
for (int i = 0; i < TEST_TIMES; i++) { for (int i = 0; i < TEST_TIMES; i++) {
ESP_LOGI(TAG, "%.2lf", GET_US_BY_CCOUNT(t_flight_sorted[i])); ESP_LOGI(TAG, "%.2lf", GET_US_BY_CCOUNT(t_flight_sorted[i]));
} }
#ifndef CONFIG_SPIRAM_SUPPORT
TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_NO_POLLING, "%d us", (int)GET_US_BY_CCOUNT(t_flight_sorted[(TEST_TIMES+1)/2])); TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_NO_POLLING, "%d us", (int)GET_US_BY_CCOUNT(t_flight_sorted[(TEST_TIMES+1)/2]));
#endif
//acquire the bus to send polling transactions faster //acquire the bus to send polling transactions faster
ret = spi_device_acquire_bus(spi, portMAX_DELAY); ret = spi_device_acquire_bus(spi, portMAX_DELAY);
@ -1046,7 +1049,9 @@ TEST_CASE("spi_speed","[spi]")
for (int i = 0; i < TEST_TIMES; i++) { for (int i = 0; i < TEST_TIMES; i++) {
ESP_LOGI(TAG, "%.2lf", GET_US_BY_CCOUNT(t_flight_sorted[i])); ESP_LOGI(TAG, "%.2lf", GET_US_BY_CCOUNT(t_flight_sorted[i]));
} }
#ifndef CONFIG_SPIRAM_SUPPORT
TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_POLLING, "%d us", (int)GET_US_BY_CCOUNT(t_flight_sorted[(TEST_TIMES+1)/2])); TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_POLLING, "%d us", (int)GET_US_BY_CCOUNT(t_flight_sorted[(TEST_TIMES+1)/2]));
#endif
//release the bus //release the bus
spi_device_release_bus(spi); spi_device_release_bus(spi);
@ -1064,7 +1069,9 @@ TEST_CASE("spi_speed","[spi]")
for (int i = 0; i < TEST_TIMES; i++) { for (int i = 0; i < TEST_TIMES; i++) {
ESP_LOGI(TAG, "%.2lf", GET_US_BY_CCOUNT(t_flight_sorted[i])); ESP_LOGI(TAG, "%.2lf", GET_US_BY_CCOUNT(t_flight_sorted[i]));
} }
#ifndef CONFIG_SPIRAM_SUPPORT
TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_NO_POLLING_NO_DMA, "%d us", (int)GET_US_BY_CCOUNT(t_flight_sorted[(TEST_TIMES+1)/2])); TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_NO_POLLING_NO_DMA, "%d us", (int)GET_US_BY_CCOUNT(t_flight_sorted[(TEST_TIMES+1)/2]));
#endif
//acquire the bus to send polling transactions faster //acquire the bus to send polling transactions faster
ret = spi_device_acquire_bus(spi, portMAX_DELAY); ret = spi_device_acquire_bus(spi, portMAX_DELAY);
@ -1078,150 +1085,12 @@ TEST_CASE("spi_speed","[spi]")
for (int i = 0; i < TEST_TIMES; i++) { for (int i = 0; i < TEST_TIMES; i++) {
ESP_LOGI(TAG, "%.2lf", GET_US_BY_CCOUNT(t_flight_sorted[i])); ESP_LOGI(TAG, "%.2lf", GET_US_BY_CCOUNT(t_flight_sorted[i]));
} }
#ifndef CONFIG_SPIRAM_SUPPORT
TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_POLLING_NO_DMA, "%d us", (int)GET_US_BY_CCOUNT(t_flight_sorted[(TEST_TIMES+1)/2])); TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_POLLING_NO_DMA, "%d us", (int)GET_US_BY_CCOUNT(t_flight_sorted[(TEST_TIMES+1)/2]));
#endif
//release the bus //release the bus
spi_device_release_bus(spi); spi_device_release_bus(spi);
master_free_device_bus(spi); master_free_device_bus(spi);
} }
#endif #endif
typedef struct {
spi_device_handle_t handle;
bool finished;
} task_context_t;
void spi_task1(void* arg)
{
//task1 send 50 polling transactions, acquire the bus and send another 50
int count=0;
spi_transaction_t t = {
.flags = SPI_TRANS_USE_TXDATA,
.tx_data = { 0x80, 0x12, 0x34, 0x56 },
.length = 4*8,
};
spi_device_handle_t handle = ((task_context_t*)arg)->handle;
for( int j = 0; j < 50; j ++ ) {
TEST_ESP_OK(spi_device_polling_transmit( handle, &t ));
ESP_LOGI( TAG, "task1:%d", count++ );
}
TEST_ESP_OK(spi_device_acquire_bus( handle, portMAX_DELAY ));
for( int j = 0; j < 50; j ++ ) {
TEST_ESP_OK(spi_device_polling_transmit( handle, &t ));
ESP_LOGI( TAG, "task1:%d", count++ );
}
spi_device_release_bus(handle);
ESP_LOGI(TAG, "task1 terminates");
((task_context_t*)arg)->finished = true;
vTaskDelete(NULL);
}
void spi_task2(void* arg)
{
int count=0;
//task2 acquire the bus, send 50 polling transactions and then 50 non-polling
spi_transaction_t t = {
.flags = SPI_TRANS_USE_TXDATA,
.tx_data = { 0x80, 0x12, 0x34, 0x56 },
.length = 4*8,
};
spi_transaction_t *ret_t;
spi_device_handle_t handle = ((task_context_t*)arg)->handle;
TEST_ESP_OK(spi_device_acquire_bus( handle, portMAX_DELAY ));
for (int i = 0; i < 50; i ++) {
TEST_ESP_OK(spi_device_polling_transmit(handle, &t));
ESP_LOGI( TAG, "task2: %d", count++ );
}
for( int j = 0; j < 50; j ++ ) {
TEST_ESP_OK(spi_device_queue_trans( handle, &t, portMAX_DELAY ));
}
for( int j = 0; j < 50; j ++ ) {
TEST_ESP_OK(spi_device_get_trans_result(handle, &ret_t, portMAX_DELAY));
assert(ret_t == &t);
ESP_LOGI( TAG, "task2: %d", count++ );
}
spi_device_release_bus(handle);
vTaskDelay(1);
ESP_LOGI(TAG, "task2 terminates");
((task_context_t*)arg)->finished = true;
vTaskDelete(NULL);
}
void spi_task3(void* arg)
{
//task3 send 30 polling transactions, acquire the bus, send 20 polling transactions and then 50 non-polling
int count=0;
spi_transaction_t t = {
.flags = SPI_TRANS_USE_TXDATA,
.tx_data = { 0x80, 0x12, 0x34, 0x56 },
.length = 4*8,
};
spi_transaction_t *ret_t;
spi_device_handle_t handle = ((task_context_t*)arg)->handle;
for (int i = 0; i < 30; i ++) {
TEST_ESP_OK(spi_device_polling_transmit(handle, &t));
ESP_LOGI( TAG, "task3: %d", count++ );
}
TEST_ESP_OK(spi_device_acquire_bus( handle, portMAX_DELAY ));
for (int i = 0; i < 20; i ++) {
TEST_ESP_OK(spi_device_polling_transmit(handle, &t));
ESP_LOGI( TAG, "task3: %d", count++ );
}
for (int j = 0; j < 50; j++) {
TEST_ESP_OK(spi_device_queue_trans(handle, &t, portMAX_DELAY));
}
for (int j = 0; j < 50; j++) {
TEST_ESP_OK(spi_device_get_trans_result(handle, &ret_t, portMAX_DELAY));
assert(ret_t == &t);
ESP_LOGI(TAG, "task3: %d", count++);
}
spi_device_release_bus(handle);
ESP_LOGI(TAG, "task3 terminates");
((task_context_t*)arg)->finished = true;
vTaskDelete(NULL);
}
TEST_CASE("spi poll tasks","[spi]")
{
task_context_t context1={};
task_context_t context2={};
task_context_t context3={};
TaskHandle_t task1, task2, task3;
esp_err_t ret;
spi_bus_config_t buscfg=SPI_BUS_TEST_DEFAULT_CONFIG();
spi_device_interface_config_t devcfg=SPI_DEVICE_TEST_DEFAULT_CONFIG();
devcfg.queue_size = 100;
//Initialize the SPI bus and 3 devices
ret=spi_bus_initialize(TEST_SPI_HOST, &buscfg, 1);
TEST_ASSERT(ret==ESP_OK);
ret=spi_bus_add_device(TEST_SPI_HOST, &devcfg, &context1.handle);
TEST_ASSERT(ret==ESP_OK);
ret=spi_bus_add_device(TEST_SPI_HOST, &devcfg, &context2.handle);
TEST_ASSERT(ret==ESP_OK);
ret=spi_bus_add_device(TEST_SPI_HOST, &devcfg, &context3.handle);
TEST_ASSERT(ret==ESP_OK);
xTaskCreate( spi_task1, "task1", 2048, &context1, 0, &task1 );
xTaskCreate( spi_task2, "task2", 2048, &context2, 0, &task2 );
xTaskCreate( spi_task3, "task3", 2048, &context3, 0, &task3 );
for(;;){
vTaskDelay(10);
if (context1.finished && context2.finished && context3.finished) break;
}
TEST_ESP_OK( spi_bus_remove_device(context1.handle) );
TEST_ESP_OK( spi_bus_remove_device(context2.handle) );
TEST_ESP_OK( spi_bus_remove_device(context3.handle) );
TEST_ESP_OK( spi_bus_free(TEST_SPI_HOST) );
}
//TODO: add a case when a non-polling transaction happened in the bus-acquiring time and then release the bus then queue a new trans

View file

@ -204,7 +204,7 @@ menu "FreeRTOS"
config FREERTOS_SUPPORT_STATIC_ALLOCATION config FREERTOS_SUPPORT_STATIC_ALLOCATION
bool "Enable FreeRTOS static allocation API" bool "Enable FreeRTOS static allocation API"
default n default y
help help
FreeRTOS gives the application writer the ability to instead provide the memory FreeRTOS gives the application writer the ability to instead provide the memory
themselves, allowing the following objects to optionally be created without any themselves, allowing the following objects to optionally be created without any

View file

@ -21,12 +21,10 @@
* @brief Enum with the three SPI peripherals that are software-accessible in it * @brief Enum with the three SPI peripherals that are software-accessible in it
*/ */
typedef enum { typedef enum {
// SPI_HOST (SPI1_HOST) is not supported by the SPI Master and SPI Slave driver on ESP32-S2
SPI1_HOST=0, ///< SPI1 SPI1_HOST=0, ///< SPI1
SPI2_HOST=1, ///< SPI2 SPI2_HOST=1, ///< SPI2
SPI3_HOST=2, ///< SPI3 SPI3_HOST=2, ///< SPI3
#if SOC_SPI_PERIPH_NUM > 3
SPI4_HOST=3, ///< SPI4
#endif
} spi_host_device_t; } spi_host_device_t;
//alias for different chips //alias for different chips
@ -35,6 +33,7 @@ typedef enum {
#define HSPI_HOST SPI2_HOST #define HSPI_HOST SPI2_HOST
#define VSPI_HOST SPI3_HOST #define VSPI_HOST SPI3_HOST
#elif CONFIG_IDF_TARGET_ESP32S2 #elif CONFIG_IDF_TARGET_ESP32S2
// SPI_HOST (SPI1_HOST) is not supported by the SPI Master and SPI Slave driver on ESP32-S2
#define SPI_HOST SPI1_HOST #define SPI_HOST SPI1_HOST
#define FSPI_HOST SPI2_HOST #define FSPI_HOST SPI2_HOST
#define HSPI_HOST SPI3_HOST #define HSPI_HOST SPI3_HOST

View file

@ -10,17 +10,17 @@ entries:
rtc_sleep (noflash_text) rtc_sleep (noflash_text)
rtc_time (noflash_text) rtc_time (noflash_text)
rtc_wdt (noflash_text) rtc_wdt (noflash_text)
spi_hal_iram (noflash_text) spi_hal_iram (noflash)
spi_slave_hal_iram (noflash_text) spi_slave_hal_iram (noflash)
if UART_ISR_IN_IRAM = y: if UART_ISR_IN_IRAM = y:
uart_hal_iram (noflash_text) uart_hal_iram (noflash)
else: else:
uart_hal_iram (default) uart_hal_iram (default)
spi_flash_hal_iram (noflash) spi_flash_hal_iram (noflash)
ledc_hal_iram (noflash_text) ledc_hal_iram (noflash)
i2c_hal_iram (noflash) i2c_hal_iram (noflash)
spi_flash_hal_gpspi (noflash) spi_flash_hal_gpspi (noflash)
lldesc (noflash_text) lldesc (noflash)
cpu_hal (noflash) cpu_hal (noflash)
soc_hal (noflash) soc_hal (noflash)
wdt_hal_iram (noflash) wdt_hal_iram (noflash)

View file

@ -16,8 +16,7 @@
#define SOC_SPI_PERIPH_NUM 3 #define SOC_SPI_PERIPH_NUM 3
#define SOC_SPI_DMA_CHAN_NUM 2 #define SOC_SPI_DMA_CHAN_NUM 2
#define SOC_SPI_PERIPH_CS_NUM(i) 3
#define SPI_PERIPH_NUM 3
#define SPI_FUNC_NUM 1 #define SPI_FUNC_NUM 1
#define SPI_IOMUX_PIN_NUM_MISO 7 #define SPI_IOMUX_PIN_NUM_MISO 7

View file

@ -14,8 +14,9 @@
#pragma once #pragma once
#define SOC_SPI_PERIPH_NUM 4 #define SOC_SPI_PERIPH_NUM 3
#define SOC_SPI_DMA_CHAN_NUM 3 #define SOC_SPI_DMA_CHAN_NUM 3
#define SOC_SPI_PERIPH_CS_NUM(i) 3
#define SPI_FUNC_NUM 0 #define SPI_FUNC_NUM 0
#define SPI_IOMUX_PIN_NUM_HD 27 #define SPI_IOMUX_PIN_NUM_HD 27
@ -35,7 +36,7 @@
#define FSPI_IOMUX_PIN_NUM_WP 14 #define FSPI_IOMUX_PIN_NUM_WP 14
//TODO: add the next slot //TODO: add the next slot
//HSPI and VSPI have no iomux pins //HSPI has no iomux pins
#define SOC_SPI_MAXIMUM_BUFFER_SIZE 72 #define SOC_SPI_MAXIMUM_BUFFER_SIZE 72

View file

@ -132,7 +132,10 @@ esp_err_t spi_bus_add_flash_device(esp_flash_t **out_chip, const esp_flash_spi_d
.read_mode = config->io_mode, .read_mode = config->io_mode,
.host = host, .host = host,
}; };
esp_err_t err = esp_flash_init_os_functions(chip, config->host_id);
int dev_id;
esp_err_t err = esp_flash_init_os_functions(chip, config->host_id, &dev_id);
assert(dev_id < SOC_SPI_PERIPH_CS_NUM(config->host_id) && dev_id >= 0);
if (err != ESP_OK) { if (err != ESP_OK) {
ret = err; ret = err;
goto fail; goto fail;
@ -141,7 +144,7 @@ esp_err_t spi_bus_add_flash_device(esp_flash_t **out_chip, const esp_flash_spi_d
bool use_iomux = spicommon_bus_using_iomux(config->host_id); bool use_iomux = spicommon_bus_using_iomux(config->host_id);
memspi_host_config_t host_cfg = { memspi_host_config_t host_cfg = {
.host_id = config->host_id, .host_id = config->host_id,
.cs_num = config->cs_id, .cs_num = dev_id,
.iomux = use_iomux, .iomux = use_iomux,
.input_delay_ns = config->input_delay_ns, .input_delay_ns = config->input_delay_ns,
.speed = config->speed, .speed = config->speed,
@ -165,6 +168,7 @@ esp_err_t spi_bus_remove_flash_device(esp_flash_t *chip)
if (chip==NULL) { if (chip==NULL) {
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
esp_flash_deinit_os_functions(chip);
if (chip->host) { if (chip->host) {
free(chip->host->driver_data); free(chip->host->driver_data);
free(chip->host); free(chip->host);

View file

@ -16,6 +16,7 @@
#include "esp_err.h" #include "esp_err.h"
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <driver/spi_common_internal.h>
#include "sdkconfig.h" #include "sdkconfig.h"
#include "esp_flash.h" #include "esp_flash.h"
@ -68,12 +69,21 @@ esp_err_t esp_flash_app_disable_protect(bool disable);
* *
* @param chip The chip to init os functions. * @param chip The chip to init os functions.
* @param host_id Which SPI host to use, 1 for SPI1, 2 for SPI2 (HSPI), 3 for SPI3 (VSPI) * @param host_id Which SPI host to use, 1 for SPI1, 2 for SPI2 (HSPI), 3 for SPI3 (VSPI)
* @param out_dev_id Output of occupied device slot
* *
* @return * @return
* - ESP_OK if success * - ESP_OK if success
* - ESP_ERR_INVALID_ARG if host_id is invalid * - ESP_ERR_INVALID_ARG if host_id is invalid
*/ */
esp_err_t esp_flash_init_os_functions(esp_flash_t *chip, int host_id); esp_err_t esp_flash_init_os_functions(esp_flash_t *chip, int host_id, int *out_dev_id);
/**
* @brief Deinitialize OS-level functions
*
* @param chip The chip to deinit os functions
* @return always ESP_OK.
*/
esp_err_t esp_flash_deinit_os_functions(esp_flash_t* chip);
/** /**
* Initialize OS-level functions for the main flash chip. * Initialize OS-level functions for the main flash chip.

View file

@ -26,6 +26,9 @@
#include "esp32s2/rom/ets_sys.h" #include "esp32s2/rom/ets_sys.h"
#endif #endif
#include "driver/spi_common_internal.h"
/* /*
* OS functions providing delay service and arbitration among chips, and with the cache. * OS functions providing delay service and arbitration among chips, and with the cache.
* *
@ -34,52 +37,43 @@
*/ */
typedef struct { typedef struct {
int host_id; spi_bus_lock_dev_handle_t dev_lock;
} app_func_arg_t; } app_func_arg_t;
typedef struct { typedef struct {
int host_id; app_func_arg_t common_arg; //shared args, must be the first item
bool no_protect; //to decide whether to check protected region (for the main chip) or not. bool no_protect; //to decide whether to check protected region (for the main chip) or not.
} spi1_app_func_arg_t; } spi1_app_func_arg_t;
// in the future we will have arbitration among devices, including flash on the same flash bus // in the future we will have arbitration among devices, including flash on the same flash bus
static IRAM_ATTR esp_err_t spi_bus_acquire(int host_id) static IRAM_ATTR esp_err_t spi_bus_acquire(spi_bus_lock_dev_handle_t dev_lock)
{ {
// was in BG operation (cache). Disable it and schedule
esp_err_t ret = spi_bus_lock_acquire_start(dev_lock, portMAX_DELAY);
if (ret != ESP_OK) {
return ret;
}
return ESP_OK; return ESP_OK;
} }
static IRAM_ATTR esp_err_t spi_bus_release(int host_id) static IRAM_ATTR esp_err_t spi_bus_release(spi_bus_lock_dev_handle_t dev_lock)
{ {
return ESP_OK; return spi_bus_lock_acquire_end(dev_lock);
} }
//for SPI1, we have to disable the cache and interrupts before using the SPI bus //for SPI1, we have to disable the cache and interrupts before using the SPI bus
static IRAM_ATTR esp_err_t spi1_start(void *arg) static IRAM_ATTR esp_err_t spi_start(void *arg)
{ {
g_flash_guard_default_ops.start(); spi_bus_lock_dev_handle_t dev_lock = ((app_func_arg_t *)arg)->dev_lock;
spi_bus_acquire(dev_lock);
spi_bus_acquire(((spi1_app_func_arg_t *)arg)->host_id); spi_bus_lock_touch(dev_lock);
return ESP_OK;
}
static IRAM_ATTR esp_err_t spi1_end(void *arg)
{
g_flash_guard_default_ops.end();
spi_bus_release(((spi1_app_func_arg_t *)arg)->host_id);
return ESP_OK; return ESP_OK;
} }
static esp_err_t spi23_start(void *arg) static IRAM_ATTR esp_err_t spi_end(void *arg)
{ {
spi_bus_acquire(((app_func_arg_t *)arg)->host_id); spi_bus_release(((app_func_arg_t *)arg)->dev_lock);
return ESP_OK;
}
static esp_err_t spi23_end(void *arg)
{
spi_bus_release(((app_func_arg_t *)arg)->host_id);
return ESP_OK; return ESP_OK;
} }
@ -99,73 +93,107 @@ static IRAM_ATTR esp_err_t main_flash_region_protected(void* arg, size_t start_a
} }
} }
static DRAM_ATTR spi1_app_func_arg_t spi1_arg = { static DRAM_ATTR spi1_app_func_arg_t main_flash_arg = {};
.host_id = SPI1_HOST, //for SPI1,
.no_protect = true,
};
static DRAM_ATTR spi1_app_func_arg_t main_flash_arg = {
.host_id = SPI1_HOST, //for SPI1,
.no_protect = false,
};
static app_func_arg_t spi2_arg = {
.host_id = SPI2_HOST, //for SPI2,
};
static app_func_arg_t spi3_arg = {
.host_id = SPI3_HOST, //for SPI3,
};
#ifdef CONFIG_IDF_TARGET_ESP32S2
static app_func_arg_t spi4_arg = {
.host_id = SPI4_HOST, //for SPI4,
};
#endif
//for SPI1, we have to disable the cache and interrupts before using the SPI bus //for SPI1, we have to disable the cache and interrupts before using the SPI bus
const DRAM_ATTR esp_flash_os_functions_t esp_flash_spi1_default_os_functions = { const DRAM_ATTR esp_flash_os_functions_t esp_flash_spi1_default_os_functions = {
.start = spi1_start, .start = spi_start,
.end = spi1_end, .end = spi_end,
.delay_ms = delay_ms, .delay_ms = delay_ms,
.region_protected = main_flash_region_protected, .region_protected = main_flash_region_protected,
}; };
const esp_flash_os_functions_t esp_flash_spi23_default_os_functions = { const esp_flash_os_functions_t esp_flash_spi23_default_os_functions = {
.start = spi23_start, .start = spi_start,
.end = spi23_end, .end = spi_end,
.delay_ms = delay_ms, .delay_ms = delay_ms,
}; };
esp_err_t esp_flash_init_os_functions(esp_flash_t *chip, int host_id) esp_err_t esp_flash_init_os_functions(esp_flash_t *chip, int host_id, int* out_dev_id)
{ {
spi_bus_lock_handle_t lock = spi_bus_lock_get_by_id(host_id);
spi_bus_lock_dev_handle_t dev_handle;
spi_bus_lock_dev_config_t config = {.flags = SPI_BUS_LOCK_DEV_FLAG_CS_REQUIRED};
esp_err_t err = spi_bus_lock_register_dev(lock, &config, &dev_handle);
if (err != ESP_OK) {
return err;
}
if (host_id == SPI1_HOST) { if (host_id == SPI1_HOST) {
//SPI1 //SPI1
chip->os_func = &esp_flash_spi1_default_os_functions; chip->os_func = &esp_flash_spi1_default_os_functions;
chip->os_func_data = &spi1_arg; chip->os_func_data = heap_caps_malloc(sizeof(spi1_app_func_arg_t),
} else if (host_id == SPI2_HOST || host_id == SPI3_HOST MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
#ifdef CONFIG_IDF_TARGET_ESP32S2 if (chip->os_func_data == NULL) {
|| host_id == SPI4_HOST return ESP_ERR_NO_MEM;
#endif }
) { *(spi1_app_func_arg_t*) chip->os_func_data = (spi1_app_func_arg_t) {
//SPI2,3,4 .common_arg = {
.dev_lock = dev_handle,
},
.no_protect = true,
};
} else if (host_id == SPI2_HOST || host_id == SPI3_HOST) {
//SPI2, SPI3
chip->os_func = &esp_flash_spi23_default_os_functions; chip->os_func = &esp_flash_spi23_default_os_functions;
#if CONFIG_IDF_TARGET_ESP32 chip->os_func_data = heap_caps_malloc(sizeof(app_func_arg_t),
chip->os_func_data = (host_id == SPI2_HOST) ? &spi2_arg : &spi3_arg; MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
#elif CONFIG_IDF_TARGET_ESP32S2 if (chip->os_func_data == NULL) {
chip->os_func_data = (host_id == SPI2_HOST) ? &spi2_arg : ((host_id == SPI3_HOST) ? &spi3_arg : &spi4_arg); return ESP_ERR_NO_MEM;
#endif }
*(app_func_arg_t*) chip->os_func_data = (app_func_arg_t) {
.dev_lock = dev_handle,
};
} else { } else {
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
*out_dev_id = spi_bus_lock_get_dev_id(dev_handle);
return ESP_OK; return ESP_OK;
} }
esp_err_t esp_flash_deinit_os_functions(esp_flash_t* chip)
{
if (chip->os_func_data) {
spi_bus_lock_unregister_dev(((app_func_arg_t*)chip->os_func_data)->dev_lock);
free(chip->os_func_data);
}
chip->os_func = NULL;
chip->os_func_data = NULL;
return ESP_OK;
}
IRAM_ATTR static void cache_enable(void* arg)
{
g_flash_guard_default_ops.end();
}
IRAM_ATTR static void cache_disable(void* arg)
{
g_flash_guard_default_ops.start();
}
esp_err_t esp_flash_app_init_os_functions(esp_flash_t* chip) esp_err_t esp_flash_app_init_os_functions(esp_flash_t* chip)
{ {
esp_err_t err = spi_bus_lock_init_main_dev();
if (err != ESP_OK) {
return err;
}
spi_bus_lock_set_bg_control(g_main_spi_bus_lock,
cache_enable, cache_disable, NULL);
chip->os_func = &esp_flash_spi1_default_os_functions; chip->os_func = &esp_flash_spi1_default_os_functions;
chip->os_func_data = &main_flash_arg; chip->os_func_data = &main_flash_arg;
main_flash_arg = (spi1_app_func_arg_t) {
.common_arg = {
.dev_lock = g_spi_lock_main_flash_dev, //for SPI1,
},
.no_protect = false,
};
return ESP_OK; return ESP_OK;
} }

View file

@ -1,4 +1,4 @@
TEST_COMPONENTS=driver esp32 esp_timer freertos mbedtls spi_flash TEST_COMPONENTS=esp32 esp_timer freertos mbedtls spi_flash
CONFIG_ESP32_SPIRAM_SUPPORT=y CONFIG_ESP32_SPIRAM_SUPPORT=y
CONFIG_ESP_INT_WDT_TIMEOUT_MS=800 CONFIG_ESP_INT_WDT_TIMEOUT_MS=800
CONFIG_SPIRAM_OCCUPY_NO_HOST=y CONFIG_SPIRAM_OCCUPY_NO_HOST=y

View file

@ -0,0 +1,5 @@
TEST_COMPONENTS=driver
CONFIG_ESP32_SPIRAM_SUPPORT=y
CONFIG_ESP_INT_WDT_TIMEOUT_MS=800
CONFIG_SPIRAM_OCCUPY_NO_HOST=y
CONFIG_ESP32_WIFI_RX_IRAM_OPT=n