diff --git a/components/esp_driver_spi/CMakeLists.txt b/components/esp_driver_spi/CMakeLists.txt index 2610635b54..38aa46cc61 100644 --- a/components/esp_driver_spi/CMakeLists.txt +++ b/components/esp_driver_spi/CMakeLists.txt @@ -4,7 +4,7 @@ if(${target} STREQUAL "linux") return() # This component is not supported by the POSIX/Linux simulator endif() -set(srcs "spi_bus_lock.c") +set(srcs "") set(public_include "include") diff --git a/components/esp_driver_spi/Kconfig b/components/esp_driver_spi/Kconfig index 6ff1be13d5..6ef7191211 100644 --- a/components/esp_driver_spi/Kconfig +++ b/components/esp_driver_spi/Kconfig @@ -5,6 +5,7 @@ menu "ESP-Driver:SPI Configurations" default n depends on !FREERTOS_PLACE_FUNCTIONS_INTO_FLASH select SPI_MASTER_ISR_IN_IRAM + select ESP_SPI_BUS_LOCK_FUNCS_IN_IRAM help Normally only the ISR of SPI master is placed in the IRAM, so that it can work without the flash when interrupt is triggered. @@ -24,6 +25,7 @@ menu "ESP-Driver:SPI Configurations" depends on !HEAP_PLACE_FUNCTION_INTO_FLASH select PERIPH_CTRL_FUNC_IN_IRAM select HAL_SPI_MASTER_FUNC_IN_IRAM + select ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM help Place the SPI master ISR in to IRAM to avoid possible cache miss. diff --git a/components/esp_driver_spi/include/esp_private/spi_common_internal.h b/components/esp_driver_spi/include/esp_private/spi_common_internal.h index c2d180900e..0b4a7ac68e 100644 --- a/components/esp_driver_spi/include/esp_private/spi_common_internal.h +++ b/components/esp_driver_spi/include/esp_private/spi_common_internal.h @@ -15,6 +15,7 @@ #include "hal/dma_types.h" #include "soc/gdma_channel.h" #include "esp_pm.h" +#include "esp_private/spi_share_hw_ctrl.h" #if SOC_GDMA_SUPPORTED #include "esp_private/gdma.h" #endif @@ -36,14 +37,6 @@ extern "C" #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 - #if SOC_GDMA_TRIG_PERIPH_SPI2_BUS == SOC_GDMA_BUS_AHB #define DMA_DESC_MEM_ALIGN_SIZE 4 #define SPI_GDMA_NEW_CHANNEL gdma_new_ahb_channel @@ -54,16 +47,6 @@ typedef dma_descriptor_align4_t spi_dma_desc_t; typedef dma_descriptor_align8_t spi_dma_desc_t; #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 @@ -85,36 +68,6 @@ typedef struct { /// Destructor called when a bus is deinitialized. typedef esp_err_t (*spi_destroy_func_t)(void*); -/** - * @brief Try to claim a SPI peripheral - * - * Call this if your driver wants to manage a SPI peripheral. - * - * @param host Peripheral to claim - * @param source The caller indentification string. - * - * @return True if peripheral is claimed successfully; false if peripheral already is claimed. - */ -bool spicommon_periph_claim(spi_host_device_t host, const char* source); - -/** - * @brief Check whether the spi periph is in use. - * - * @param host Peripheral to check. - * - * @return True if in use, otherwise false. - */ -bool spicommon_periph_in_use(spi_host_device_t host); - -/** - * @brief Return the SPI peripheral so another driver can claim it. - * - * @param host Peripheral to return - * - * @return True if peripheral is returned successfully; false if peripheral was free to claim already. - */ -bool spicommon_periph_free(spi_host_device_t host); - /** * @brief Alloc DMA for SPI * @@ -330,449 +283,6 @@ const spi_bus_attr_t* spi_bus_get_attr(spi_host_device_t host_id); 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 bus -extern const spi_bus_lock_handle_t g_main_spi_bus_lock; - -/** - * @brief Initialize the main SPI bus, called during chip startup. - * - * @return always ESP_OK - */ -esp_err_t spi_bus_lock_init_main_bus(void); - -/// The lock for the main flash device -extern const spi_bus_lock_dev_handle_t g_spi_lock_main_flash_dev; - -/** - * @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 } #endif diff --git a/components/esp_driver_spi/src/gpspi/spi_common.c b/components/esp_driver_spi/src/gpspi/spi_common.c index 6f2931920a..787c227cbe 100644 --- a/components/esp_driver_spi/src/gpspi/spi_common.c +++ b/components/esp_driver_spi/src/gpspi/spi_common.c @@ -17,6 +17,7 @@ #include "driver/spi_master.h" #include "esp_private/periph_ctrl.h" #include "esp_private/spi_common_internal.h" +#include "esp_private/spi_share_hw_ctrl.h" #include "hal/spi_hal.h" #include "hal/gpio_hal.h" #if CONFIG_IDF_TARGET_ESP32 @@ -28,12 +29,6 @@ #include "hal/cache_ll.h" #endif -#if !SOC_RCC_IS_INDEPENDENT -#define SPI_COMMON_RCC_CLOCK_ATOMIC() PERIPH_RCC_ATOMIC() -#else -#define SPI_COMMON_RCC_CLOCK_ATOMIC() -#endif - static const char *SPI_TAG = "spi"; #define SPI_CHECK(a, str, ret_val) ESP_RETURN_ON_FALSE(a, ret_val, SPI_TAG, str) @@ -67,20 +62,19 @@ typedef struct { #endif } spicommon_bus_context_t; -//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), -#if (SOC_SPI_PERIPH_NUM >= 3) - ATOMIC_VAR_INIT(false), -#endif -#if (SOC_SPI_PERIPH_NUM >= 4) - ATOMIC_VAR_INIT(false), -#endif - }; - -static const char* spi_claiming_func[3] = {NULL, NULL, NULL}; static spicommon_bus_context_t s_mainbus = SPI_MAIN_BUS_DEFAULT(); static spicommon_bus_context_t* bus_ctx[SOC_SPI_PERIPH_NUM] = {&s_mainbus}; +#if CONFIG_SPI_FLASH_SHARE_SPI1_BUS +/* The lock for the share SPI1 bus is registered here in a constructor due to need to access the context + This way we are able to decouple the SPI-flash driver from the spi-master driver */ +static __attribute__((constructor)) void spi_bus_lock_init_main_bus(void) +{ + /* Initialize bus context about the main SPI bus lock, called during chip startup. */ + spi_bus_main_set_lock(g_main_spi_bus_lock); +} +#endif + #if !SOC_GDMA_SUPPORTED //Each bit stands for 1 dma channel, BIT(0) should be used for SPI1 static uint8_t spi_dma_chan_enabled = 0; @@ -96,42 +90,6 @@ static inline bool is_valid_host(spi_host_device_t host) #endif } -//----------------------------------------------------------alloc spi periph-------------------------------------------------------// -//Returns true if this peripheral is successfully claimed, false if otherwise. -bool spicommon_periph_claim(spi_host_device_t host, const char* source) -{ - bool false_var = false; - bool ret = atomic_compare_exchange_strong(&spi_periph_claimed[host], &false_var, true); - if (ret) { - spi_claiming_func[host] = source; - SPI_COMMON_RCC_CLOCK_ATOMIC() { - spi_ll_enable_bus_clock(host, true); - spi_ll_reset_register(host); - } - } else { - ESP_EARLY_LOGE(SPI_TAG, "SPI%d already claimed by %s.", host + 1, spi_claiming_func[host]); - } - return ret; -} - -bool spicommon_periph_in_use(spi_host_device_t host) -{ - return atomic_load(&spi_periph_claimed[host]); -} - -//Returns true if this peripheral is successfully freed, false if otherwise. -bool spicommon_periph_free(spi_host_device_t host) -{ - bool true_var = true; - bool ret = atomic_compare_exchange_strong(&spi_periph_claimed[host], &true_var, false); - if (ret) { - SPI_COMMON_RCC_CLOCK_ATOMIC() { - spi_ll_enable_bus_clock(host, false); - } - } - return ret; -} - int spicommon_irqsource_for_host(spi_host_device_t host) { return spi_periph_signal[host].irq; diff --git a/components/esp_driver_spi/src/gpspi/spi_slave.c b/components/esp_driver_spi/src/gpspi/spi_slave.c index 23990f0e17..14dbe4ecad 100644 --- a/components/esp_driver_spi/src/gpspi/spi_slave.c +++ b/components/esp_driver_spi/src/gpspi/spi_slave.c @@ -31,6 +31,7 @@ #include "esp_private/spi_slave_internal.h" #include "esp_private/spi_common_internal.h" #include "esp_private/esp_cache_private.h" +#include "esp_private/spi_share_hw_ctrl.h" static const char *SPI_TAG = "spi_slave"; diff --git a/components/esp_driver_spi/src/gpspi/spi_slave_hd.c b/components/esp_driver_spi/src/gpspi/spi_slave_hd.c index c50c76c79c..cdc8f7fd42 100644 --- a/components/esp_driver_spi/src/gpspi/spi_slave_hd.c +++ b/components/esp_driver_spi/src/gpspi/spi_slave_hd.c @@ -13,6 +13,7 @@ #include "freertos/ringbuf.h" #include "driver/gpio.h" #include "esp_private/spi_common_internal.h" +#include "esp_private/spi_share_hw_ctrl.h" #include "esp_private/esp_cache_private.h" #include "driver/spi_slave_hd.h" #include "hal/spi_slave_hd_hal.h" diff --git a/components/esp_hw_support/CMakeLists.txt b/components/esp_hw_support/CMakeLists.txt index 1324a65456..20e0e20918 100644 --- a/components/esp_hw_support/CMakeLists.txt +++ b/components/esp_hw_support/CMakeLists.txt @@ -36,7 +36,9 @@ if(NOT BOOTLOADER_BUILD) "port/${target}/io_mux.c" "port/${target}/esp_clk_tree.c" "port/esp_clk_tree_common.c" - "dma/esp_dma_utils.c") + "dma/esp_dma_utils.c" + "spi_share_hw_ctrl.c" + "spi_bus_lock.c") if(CONFIG_SOC_ADC_SUPPORTED) list(APPEND srcs "adc_share_hw_ctrl.c") diff --git a/components/esp_hw_support/Kconfig b/components/esp_hw_support/Kconfig index 063f57cefa..e7a36c540a 100644 --- a/components/esp_hw_support/Kconfig +++ b/components/esp_hw_support/Kconfig @@ -341,4 +341,11 @@ menu "Hardware Settings" clock support isn't done yet. So with this option, we use xtal on FPGA as the clock source. + config ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM + bool + default n + + config ESP_SPI_BUS_LOCK_FUNCS_IN_IRAM + bool + default n endmenu diff --git a/components/esp_hw_support/include/esp_private/spi_share_hw_ctrl.h b/components/esp_hw_support/include/esp_private/spi_share_hw_ctrl.h new file mode 100644 index 0000000000..cc86465590 --- /dev/null +++ b/components/esp_hw_support/include/esp_private/spi_share_hw_ctrl.h @@ -0,0 +1,511 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#include "soc/soc_caps.h" +#include "hal/spi_types.h" +#include "esp_private/periph_ctrl.h" +#include "freertos/FreeRTOS.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if !SOC_RCC_IS_INDEPENDENT +#define SPI_COMMON_RCC_CLOCK_ATOMIC() PERIPH_RCC_ATOMIC() +#else +#define SPI_COMMON_RCC_CLOCK_ATOMIC() +#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*); + +/** + * @brief Try to claim a SPI peripheral + * + * Call this if your driver wants to manage a SPI peripheral. + * + * @param host Peripheral to claim + * @param source The caller indentification string. + * + * @return True if peripheral is claimed successfully; false if peripheral already is claimed. + */ +bool spicommon_periph_claim(spi_host_device_t host, const char* source); + +/** + * @brief Check whether the spi periph is in use. + * + * @param host Peripheral to check. + * + * @return True if in use, otherwise false. + */ +bool spicommon_periph_in_use(spi_host_device_t host); + +/** + * @brief Return the SPI peripheral so another driver can claim it. + * + * @param host Peripheral to return + * + * @return True if peripheral is returned successfully; false if peripheral was free to claim already. + */ +bool spicommon_periph_free(spi_host_device_t host); + +/******************************************************************************* + * 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 bus +extern const spi_bus_lock_handle_t g_main_spi_bus_lock; + +/// The lock for the main flash device +extern const spi_bus_lock_dev_handle_t g_spi_lock_main_flash_dev; + +/** + * @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 +} +#endif diff --git a/components/esp_driver_spi/spi_bus_lock.c b/components/esp_hw_support/spi_bus_lock.c similarity index 94% rename from components/esp_driver_spi/spi_bus_lock.c rename to components/esp_hw_support/spi_bus_lock.c index e01c7f74af..ab3a20ec65 100644 --- a/components/esp_driver_spi/spi_bus_lock.c +++ b/components/esp_hw_support/spi_bus_lock.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,7 +8,7 @@ #include "freertos/semphr.h" #include #include "sdkconfig.h" -#include "esp_private/spi_common_internal.h" +#include "esp_private/spi_share_hw_ctrl.h" #include "esp_intr_alloc.h" #include "soc/soc_caps.h" #include "stdatomic.h" @@ -17,6 +17,18 @@ #include #include "esp_heap_caps.h" +#ifdef CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM +#define SPI_BUS_LOCK_ISR_ATTR IRAM_ATTR +#else +#define SPI_BUS_LOCK_ISR_ATTR +#endif + +#ifdef CONFIG_ESP_SPI_BUS_LOCK_FUNCS_IN_IRAM +#define SPI_BUSLOCK_ATTR IRAM_ATTR +#else +#define SPI_BUSLOCK_ATTR +#endif + /* * This lock is designed to solve the conflicts between SPI devices (used in tasks) and * the background operations (ISR or cache access). @@ -262,7 +274,7 @@ static inline int 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) +SPI_BUS_LOCK_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); } @@ -277,7 +289,7 @@ 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) +SPI_BUS_LOCK_ISR_ATTR static inline void lock_status_init(spi_bus_lock_t *lock) { atomic_store(&lock->status, 0); } @@ -300,7 +312,7 @@ IRAM_ATTR static inline uint32_t lock_status_clear(spi_bus_lock_t* lock, uint32_ * 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) +SPI_BUS_LOCK_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); } @@ -310,7 +322,7 @@ 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) +SPI_BUS_LOCK_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); @@ -324,7 +336,7 @@ IRAM_ATTR static inline void bg_enable(spi_bus_lock_t* lock) // 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_BUSLOCK_ATTR static inline void req_core(spi_bus_lock_dev_t *dev_handle) { spi_bus_lock_t *lock = dev_handle->parent; @@ -351,7 +363,7 @@ SPI_MASTER_ATTR static inline void req_core(spi_bus_lock_dev_t *dev_handle) } //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_ISR_ATTR static inline bool acquire_core(spi_bus_lock_dev_t *dev_handle) { spi_bus_lock_t* lock = dev_handle->parent; @@ -455,7 +467,7 @@ IRAM_ATTR static inline void acquire_end_core(spi_bus_lock_dev_t *dev_handle) // 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) +SPI_BUS_LOCK_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 @@ -472,7 +484,7 @@ SPI_MASTER_ISR_ATTR static inline void update_pend_core(spi_bus_lock_t *lock, ui // 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) +SPI_BUS_LOCK_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; @@ -495,7 +507,7 @@ SPI_MASTER_ISR_ATTR static inline bool clear_pend_core(spi_bus_lock_dev_t *dev_h // 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) +SPI_BUS_LOCK_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); /* @@ -521,7 +533,7 @@ SPI_MASTER_ISR_ATTR static inline bool bg_entry_core(spi_bus_lock_t *lock) // 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) +SPI_BUS_LOCK_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) { @@ -558,7 +570,7 @@ 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) +SPI_BUS_LOCK_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); @@ -748,7 +760,7 @@ IRAM_ATTR esp_err_t spi_bus_lock_acquire_end(spi_bus_lock_dev_t *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) +SPI_BUS_LOCK_ISR_ATTR spi_bus_lock_dev_handle_t spi_bus_lock_get_acquiring_dev(spi_bus_lock_t *lock) { return lock->acquiring_dev; } @@ -756,17 +768,17 @@ SPI_MASTER_ISR_ATTR spi_bus_lock_dev_handle_t spi_bus_lock_get_acquiring_dev(spi /******************************************************************************* * BG (background operation) service ******************************************************************************/ -SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_entry(spi_bus_lock_t* lock) +SPI_BUS_LOCK_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) +SPI_BUS_LOCK_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) +SPI_BUSLOCK_ATTR esp_err_t spi_bus_lock_bg_request(spi_bus_lock_dev_t *dev_handle) { req_core(dev_handle); return ESP_OK; @@ -799,14 +811,14 @@ IRAM_ATTR esp_err_t spi_bus_lock_wait_bg_done(spi_bus_lock_dev_handle_t dev_hand return ESP_OK; } -SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_clear_req(spi_bus_lock_dev_t *dev_handle) +SPI_BUS_LOCK_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_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); @@ -814,7 +826,7 @@ SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_check_dev_acq(spi_bus_lock_t *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_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); @@ -830,7 +842,7 @@ SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_check_dev_req(spi_bus_lock_dev_t *dev_l } } -SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_req_exist(spi_bus_lock_t *lock) +SPI_BUS_LOCK_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; @@ -855,12 +867,6 @@ static spi_bus_lock_t main_spi_bus_lock = { }; const spi_bus_lock_handle_t g_main_spi_bus_lock = &main_spi_bus_lock; -esp_err_t spi_bus_lock_init_main_bus(void) -{ - spi_bus_main_set_lock(g_main_spi_bus_lock); - return ESP_OK; -} - static StaticSemaphore_t main_flash_semphr; static spi_bus_lock_dev_t lock_main_flash_dev = { diff --git a/components/esp_hw_support/spi_share_hw_ctrl.c b/components/esp_hw_support/spi_share_hw_ctrl.c new file mode 100644 index 0000000000..3d610bb49f --- /dev/null +++ b/components/esp_hw_support/spi_share_hw_ctrl.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "esp_private/spi_share_hw_ctrl.h" + +#include + +#include "sdkconfig.h" +#include "soc/soc_caps.h" +#include "hal/spi_ll.h" +#include "esp_log.h" + +static const char* SPI_TAG = "spi_share_hw_ctrl"; + +//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), +#if (SOC_SPI_PERIPH_NUM >= 3) + ATOMIC_VAR_INIT(false), +#endif +#if (SOC_SPI_PERIPH_NUM >= 4) + ATOMIC_VAR_INIT(false), +#endif + }; + +static const char* spi_claiming_func[3] = {NULL, NULL, NULL}; + +//----------------------------------------------------------alloc spi periph-------------------------------------------------------// +//Returns true if this peripheral is successfully claimed, false if otherwise. +bool spicommon_periph_claim(spi_host_device_t host, const char* source) +{ + bool false_var = false; + bool ret = atomic_compare_exchange_strong(&spi_periph_claimed[host], &false_var, true); + if (ret) { + spi_claiming_func[host] = source; + SPI_COMMON_RCC_CLOCK_ATOMIC() { + spi_ll_enable_bus_clock(host, true); + spi_ll_reset_register(host); + } + } else { + ESP_EARLY_LOGE(SPI_TAG, "SPI%d already claimed by %s.", host + 1, spi_claiming_func[host]); + } + return ret; +} + +bool spicommon_periph_in_use(spi_host_device_t host) +{ + return atomic_load(&spi_periph_claimed[host]); +} + +//Returns true if this peripheral is successfully freed, false if otherwise. +bool spicommon_periph_free(spi_host_device_t host) +{ + bool true_var = true; + bool ret = atomic_compare_exchange_strong(&spi_periph_claimed[host], &true_var, false); + if (ret) { + SPI_COMMON_RCC_CLOCK_ATOMIC() { + spi_ll_enable_bus_clock(host, false); + } + } + return ret; +} diff --git a/components/esp_system/CMakeLists.txt b/components/esp_system/CMakeLists.txt index bee467e389..ef194aa29c 100644 --- a/components/esp_system/CMakeLists.txt +++ b/components/esp_system/CMakeLists.txt @@ -63,11 +63,9 @@ else() # [refactor-todo] requirements due to init code, # should be removable once using component init functions # link-time registration is used. - # [refactor-todo] requires "esp_driver_spi" for headers: - # - spi_common_internal.h # [refactor-todo] esp_partition required for virtual efuse # init code. Move to esp_efuse component. - pthread bootloader_support efuse esp_driver_spi esp_partition + pthread bootloader_support efuse esp_partition esp_pm LDFRAGMENTS "linker.lf" "app.lf") add_subdirectory(port) diff --git a/components/esp_system/port/soc/esp32/clk.c b/components/esp_system/port/soc/esp32/clk.c index 8772a3158b..d414d68e4a 100644 --- a/components/esp_system/port/soc/esp32/clk.c +++ b/components/esp_system/port/soc/esp32/clk.c @@ -12,7 +12,7 @@ #include "bootloader_clock.h" #include "hal/wdt_hal.h" -#include "esp_private/spi_common_internal.h" // [refactor-todo]: for spicommon_periph_in_use +#include "esp_private/spi_share_hw_ctrl.h" #include "esp_log.h" #include "esp_cpu.h" diff --git a/components/spi_flash/CMakeLists.txt b/components/spi_flash/CMakeLists.txt index 42b3306d93..af29e02de5 100644 --- a/components/spi_flash/CMakeLists.txt +++ b/components/spi_flash/CMakeLists.txt @@ -49,7 +49,7 @@ else() list(APPEND srcs ${cache_srcs}) set(priv_requires bootloader_support app_update soc esp_mm - esp_driver_gpio esp_driver_spi # TODO: IDF-8503 move spi_bus_lock to esp_hw_support component + esp_driver_gpio ) endif() diff --git a/components/spi_flash/esp_flash_spi_init.c b/components/spi_flash/esp_flash_spi_init.c index 65407c70c3..f6ddb52b93 100644 --- a/components/spi_flash/esp_flash_spi_init.c +++ b/components/spi_flash/esp_flash_spi_init.c @@ -14,7 +14,7 @@ #include "esp_log.h" #include "esp_heap_caps.h" #include "hal/spi_types.h" -#include "esp_private/spi_common_internal.h" +#include "esp_private/spi_share_hw_ctrl.h" #include "hal/spi_flash_hal.h" #include "hal/gpio_hal.h" #include "esp_flash_internal.h" @@ -165,6 +165,17 @@ static bool use_bus_lock(int host_id) #endif } +static bool bus_using_iomux(spi_host_device_t host) +{ +#define CHECK_IOMUX_PIN(HOST, PIN_NAME) if (GPIO.func_in_sel_cfg[spi_periph_signal[(HOST)].PIN_NAME##_in].sig_in_sel) return false + + CHECK_IOMUX_PIN(host, spid); + CHECK_IOMUX_PIN(host, spiq); + CHECK_IOMUX_PIN(host, spiwp); + CHECK_IOMUX_PIN(host, spihd); + return true; +} + static esp_err_t acquire_spi_device(const esp_flash_spi_device_config_t *config, int* out_dev_id, spi_bus_lock_dev_handle_t* out_dev_handle) { esp_err_t ret = ESP_OK; @@ -246,7 +257,7 @@ esp_err_t spi_bus_add_flash_device(esp_flash_t **out_chip, const esp_flash_spi_d //avoid conflicts with main flash assert(config->host_id != SPI1_HOST || dev_id != 0); - bool use_iomux = spicommon_bus_using_iomux(config->host_id); + bool use_iomux = bus_using_iomux(config->host_id); memspi_host_config_t host_cfg = { .host_id = config->host_id, .cs_num = dev_id, diff --git a/components/spi_flash/include/esp_flash_internal.h b/components/spi_flash/include/esp_flash_internal.h index e329b4d5f0..e7d35b0dfb 100644 --- a/components/spi_flash/include/esp_flash_internal.h +++ b/components/spi_flash/include/esp_flash_internal.h @@ -8,7 +8,7 @@ #include "esp_err.h" #include #include -#include "esp_private/spi_common_internal.h" +#include "esp_private/spi_share_hw_ctrl.h" #include "sdkconfig.h" #include "esp_flash.h" diff --git a/components/spi_flash/spi_flash_os_func_app.c b/components/spi_flash/spi_flash_os_func_app.c index 31aa11b8a9..1cae387281 100644 --- a/components/spi_flash/spi_flash_os_func_app.c +++ b/components/spi_flash/spi_flash_os_func_app.c @@ -20,7 +20,7 @@ #include "esp_private/spi_flash_os.h" #include "esp_private/cache_utils.h" -#include "esp_private/spi_common_internal.h" +#include "esp_private/spi_share_hw_ctrl.h" #define SPI_FLASH_CACHE_NO_DISABLE (CONFIG_SPI_FLASH_AUTO_SUSPEND || (CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_RODATA) || CONFIG_APP_BUILD_TYPE_RAM) static const char TAG[] = "spi_flash"; @@ -326,7 +326,7 @@ esp_err_t esp_flash_init_main_bus_lock(void) * is set. Thus, we must not call them if the macro is not defined, else the linker * would trigger errors. */ #if CONFIG_SPI_FLASH_SHARE_SPI1_BUS - spi_bus_lock_init_main_bus(); + /* bus_lock is registered by `spi_bus_lock_init_main_bus` constructor in spi_common.c */ spi_bus_lock_set_bg_control(g_main_spi_bus_lock, cache_enable, cache_disable, NULL); esp_err_t err = spi_bus_lock_init_main_dev(); diff --git a/tools/test_apps/system/g1_components/CMakeLists.txt b/tools/test_apps/system/g1_components/CMakeLists.txt index 894d696d9d..09757deb18 100644 --- a/tools/test_apps/system/g1_components/CMakeLists.txt +++ b/tools/test_apps/system/g1_components/CMakeLists.txt @@ -33,9 +33,9 @@ set(extra_components_which_shouldnt_be_included bootloader_support # [refactor-todo]: should cxx be in G1? Can it exist without FreeRTOS? cxx - # [refactor-todo]: driver is a dependency of esp_pm, spi_flash, vfs, esp_wifi - # all of these should be removed from G1 except for spi_flash. - esp_driver_gpio esp_driver_spi + # [refactor-todo]: esp_driver_gpio is a dependency of esp_pm (should be removed from g1 builds), + # spi_flash, esp_hw_support + esp_driver_gpio # esp_app_format is dependency of bootloader_support, app_update esp_app_format # esp_bootloader_format is dependency of bootloader_support, app_update @@ -48,8 +48,6 @@ set(extra_components_which_shouldnt_be_included # conditional on related Kconfig option. It is also used by esp_wifi, driver, mbedtls, # all of which should be removed from G1-only build. esp_pm - # esp_ringbuf is a dependency of driver, which should be removed. - esp_ringbuf # esp_timer is a dependency of freertos, esp_event, esp_wifi, driver. # For freertos, it can be made a weak dependency conditional on FREERTOS_RUN_TIME_STATS_USING_ESP_TIMER esp_timer