diff --git a/components/drivers/Kconfig b/components/drivers/Kconfig index 3ffa4c3f83b..a7e4fa521bb 100755 --- a/components/drivers/Kconfig +++ b/components/drivers/Kconfig @@ -23,6 +23,7 @@ rsource "hwcrypto/Kconfig" rsource "wlan/Kconfig" rsource "led/Kconfig" rsource "mailbox/Kconfig" +rsource "hwspinlock/Kconfig" rsource "phye/Kconfig" rsource "ata/Kconfig" rsource "nvme/Kconfig" diff --git a/components/drivers/hwspinlock/Kconfig b/components/drivers/hwspinlock/Kconfig new file mode 100644 index 00000000000..de0d13c948e --- /dev/null +++ b/components/drivers/hwspinlock/Kconfig @@ -0,0 +1,15 @@ +menuconfig RT_USING_HWSPINLOCK + bool "Using Hardware Spinlock device drivers" + depends on RT_USING_DM + depends on RT_USING_OFW + select RT_USING_ADT + select RT_USING_ADT_REF + default n + help + Hardware spinlock modules provide hardware assistance for + synchronization and mutual exclusion between heterogeneous processors + and those not operating under a single, shared operating system. + +if RT_USING_HWSPINLOCK + osource "$(SOC_DM_HWSPINLOCK_DIR)/Kconfig" +endif diff --git a/components/drivers/hwspinlock/SConscript b/components/drivers/hwspinlock/SConscript new file mode 100644 index 00000000000..81ed9adcca6 --- /dev/null +++ b/components/drivers/hwspinlock/SConscript @@ -0,0 +1,15 @@ +from building import * + +group = [] + +if not GetDepend(['RT_USING_HWSPINLOCK']): + Return('group') + +cwd = GetCurrentDir() +CPPPATH = [cwd + '/../include'] + +src = ['hwspinlock.c'] + +group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH) + +Return('group') diff --git a/components/drivers/hwspinlock/hwspinlock.c b/components/drivers/hwspinlock/hwspinlock.c new file mode 100644 index 00000000000..1e8ca635da5 --- /dev/null +++ b/components/drivers/hwspinlock/hwspinlock.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-09-23 GuEe-GUI first version + */ + +#include + +#include + +#define DBG_TAG "rtdm.hwspinlock" +#define DBG_LVL DBG_INFO +#include + +#include "hwspinlock_dm.h" + +static RT_DEFINE_SPINLOCK(hwspinlock_ops_lock); +static rt_list_t hwspinlock_bank_nodes = RT_LIST_OBJECT_INIT(hwspinlock_bank_nodes); + +rt_err_t rt_hwspinlock_bank_register(struct rt_hwspinlock_bank *bank) +{ + struct rt_hwspinlock *hwlock; + + if (!bank || !bank->ops || bank->locks_nr <= 0 || !bank->dev) + { + return -RT_EINVAL; + } + + rt_list_init(&bank->list); + rt_ref_init(&bank->ref); + + hwlock = &bank->locks[0]; + + for (int i = 0; i < bank->locks_nr; ++i, ++hwlock) + { + hwlock->bank = bank; + hwlock->used = RT_FALSE; + rt_spin_lock_init(&hwlock->lock); + } + + rt_spin_lock(&hwspinlock_ops_lock); + rt_list_insert_after(&hwspinlock_bank_nodes, &bank->list); + rt_spin_unlock(&hwspinlock_ops_lock); + + rt_dm_dev_bind_fwdata(bank->dev, RT_NULL, bank); + + return RT_EOK; +} + +rt_err_t rt_hwspinlock_bank_unregister(struct rt_hwspinlock_bank *bank) +{ + rt_err_t err; + + if (!bank) + { + return -RT_EINVAL; + } + + rt_spin_lock(&hwspinlock_ops_lock); + + if (rt_ref_read(&bank->ref) == 1) + { + rt_list_remove(&bank->list); + rt_dm_dev_unbind_fwdata(bank->dev, RT_NULL); + + err = RT_EOK; + } + else + { + err = -RT_EBUSY; + } + + rt_spin_unlock(&hwspinlock_ops_lock); + + return err; +} + +rt_err_t rt_hwspin_trylock_raw(struct rt_hwspinlock *hwlock, + rt_ubase_t *out_irq_level) +{ + rt_err_t err; + + if (!hwlock) + { + return -RT_EINVAL; + } + + if (out_irq_level) + { + *out_irq_level = rt_spin_lock_irqsave(&hwlock->lock); + } + else + { + rt_spin_lock(&hwlock->lock); + } + + err = hwlock->bank->ops->trylock(hwlock); + + if (err) + { + if (out_irq_level) + { + rt_spin_unlock_irqrestore(&hwlock->lock, *out_irq_level); + } + else + { + rt_spin_unlock(&hwlock->lock); + } + } + + rt_hw_dmb(); + + return err; +} + +rt_err_t rt_hwspin_lock_timeout_raw(struct rt_hwspinlock *hwlock, + rt_uint32_t timeout_ms, rt_ubase_t *out_irq_level) +{ + rt_err_t err; + rt_tick_t timeout = rt_tick_get() + rt_tick_from_millisecond(timeout_ms); + + for (;;) + { + err = rt_hwspin_trylock_raw(hwlock, out_irq_level); + + if (err != -RT_EBUSY) + { + break; + } + + if (timeout < rt_tick_get()) + { + return -RT_ETIMEOUT; + } + + if (hwlock->bank->ops->relax) + { + hwlock->bank->ops->relax(hwlock); + } + } + + return err; +} + +void rt_hwspin_unlock_raw(struct rt_hwspinlock *hwlock, + rt_ubase_t *out_irq_level) +{ + if (!hwlock) + { + return; + } + + rt_hw_dmb(); + + hwlock->bank->ops->unlock(hwlock); + + if (out_irq_level) + { + rt_spin_unlock_irqrestore(&hwlock->lock, *out_irq_level); + } + else + { + rt_spin_unlock(&hwlock->lock); + } +} + +static struct rt_hwspinlock *hwspinlock_get(struct rt_hwspinlock_bank *bank, int id) +{ + struct rt_hwspinlock *hwlock = RT_NULL; + + if (bank) + { + int offset = id - bank->base_id; + + if (!bank->locks[offset].used) + { + hwlock = &bank->locks[offset]; + } + } + else + { + rt_list_for_each_entry(bank, &hwspinlock_bank_nodes, list) + { + hwlock = rt_err_ptr(-RT_EBUSY); + + for (int i = 0; i < bank->locks_nr; ++i) + { + if (!bank->locks[i].used) + { + hwlock = &bank->locks[i]; + goto _found; + } + } + } + } + +_found: + if (!rt_is_err_or_null(hwlock)) + { + hwlock->used = RT_TRUE; + rt_ref_get(&hwlock->bank->ref); + } + + return hwlock; +} + +struct rt_hwspinlock *rt_hwspinlock_get(void) +{ + struct rt_hwspinlock *lock; + + rt_spin_lock(&hwspinlock_ops_lock); + + lock = hwspinlock_get(RT_NULL, -1); + + rt_spin_unlock(&hwspinlock_ops_lock); + + return lock; +} + +struct rt_hwspinlock *rt_hwspinlock_get_by_index(struct rt_device *dev, int index) +{ + return rt_ofw_get_hwspinlock_by_index(dev->ofw_node, index); +} + +struct rt_hwspinlock *rt_hwspinlock_get_by_name(struct rt_device *dev, const char *name) +{ + return rt_ofw_get_hwspinlock_by_name(dev->ofw_node, name); +} + +static void hwspinlock_release(struct rt_ref *r) +{ + struct rt_hwspinlock_bank *bank = rt_container_of(r, struct rt_hwspinlock_bank, ref); + + LOG_E("%s is release", rt_dm_dev_get_name(bank->dev)); + (void)bank; + + RT_ASSERT(0); +} + +void rt_hwspinlock_put(struct rt_hwspinlock *hwlock) +{ + if (hwlock) + { + rt_spin_lock(&hwspinlock_ops_lock); + hwlock->used = RT_FALSE; + rt_spin_unlock(&hwspinlock_ops_lock); + + rt_ref_put(&hwlock->bank->ref, &hwspinlock_release); + } +} + +struct rt_hwspinlock *rt_ofw_get_hwspinlock_by_index(struct rt_ofw_node *np, int index) +{ + rt_err_t err; + struct rt_ofw_node *bank_np; + struct rt_ofw_cell_args args; + struct rt_hwspinlock *lock; + struct rt_hwspinlock_bank *bank; + + if (!np || index < 0) + { + return rt_err_ptr(-RT_EINVAL); + } + + err = rt_ofw_parse_phandle_cells(np, "hwlocks", "#hwlock-cells", index, &args); + + if (err) + { + return rt_err_ptr(err); + } + + bank_np = args.data; + + if (!rt_ofw_data(bank_np)) + { + rt_platform_ofw_request(bank_np); + } + + rt_spin_lock(&hwspinlock_ops_lock); + + bank = rt_ofw_data(bank_np); + rt_ofw_node_put(bank_np); + + if (!bank || args.args_count != 1) + { + lock = rt_err_ptr(-RT_ENOSYS); + } + else + { + lock = hwspinlock_get(bank, bank->base_id + args.args[0]); + } + + rt_spin_unlock(&hwspinlock_ops_lock); + + return lock; +} + +struct rt_hwspinlock *rt_ofw_get_hwspinlock_by_name(struct rt_ofw_node *np, const char *name) +{ + int index; + + if (!np || !name) + { + return rt_err_ptr(-RT_EINVAL); + } + + index = rt_ofw_prop_index_of_string(np, "hwlock-names", name); + + if (index < 0) + { + return rt_err_ptr(index); + } + + return rt_ofw_get_hwspinlock_by_index(np, index); +} diff --git a/components/drivers/hwspinlock/hwspinlock_dm.h b/components/drivers/hwspinlock/hwspinlock_dm.h new file mode 100644 index 00000000000..521b5618b5f --- /dev/null +++ b/components/drivers/hwspinlock/hwspinlock_dm.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-09-23 GuEe-GUI first version + */ + +#ifndef __HWSPINLOCK_DM_H__ +#define __HWSPINLOCK_DM_H__ + +#include +#include +#include +#include +#include + +/** + * @brief Hardware spinlock instance + * + * Represents a single hardware spinlock managed by a bank. Each lock + * maintains a local software spinlock to protect internal state and a + * usage flag to indicate whether it has been allocated to clients. + */ +struct rt_hwspinlock +{ + struct rt_hwspinlock_bank *bank; + struct rt_spinlock lock; /**< Local software spinlock to guard structure access */ + + rt_bool_t used; /**< Indicates whether this lock is currently in use */ + void *priv; /**< Driver-specific private data */ +}; + +/** + * @brief Hardware spinlock operations + * + * A hardware spinlock driver must provide these low-level operations. + * trylock() performs a non-blocking acquire. relax() is called inside + * busy-wait loops to reduce power usage or yield the bus. + */ +struct rt_hwspinlock_ops +{ + /** + * @brief Attempt to acquire the hardware spinlock (non-blocking) + * + * Should immediately return RT_EOK on success or -RT_EBUSY if the lock + * is already taken by another CPU or hardware requester. + */ + rt_err_t (*trylock)(struct rt_hwspinlock *hwlock); + + /** + * @brief Release the hardware spinlock + */ + void (*unlock)(struct rt_hwspinlock *hwlock); + + /** + * @brief Relax operation called during spinning + * + * Typically implemented using power-saving instructions (e.g., WFE), + * yielding the CPU, or other hardware-supported wait mechanisms. + */ + void (*relax)(struct rt_hwspinlock *hwlock); +}; + +/** + * @brief Hardware spinlock bank + * + * Represents a group of hardware spinlocks exported by the same device + * or hardware module. A bank is registered globally so that locks can be + * retrieved by index, name, or device tree bindings. + */ +struct rt_hwspinlock_bank +{ + rt_list_t list; + struct rt_ref ref; + + struct rt_device *dev; + const struct rt_hwspinlock_ops *ops; + + int base_id; /**< Global base ID used to calculate lock identifiers */ + rt_size_t locks_nr; /**< Number of hardware spinlocks in this bank */ + + /** + * @brief Array of hardware spinlock instances + * + * Implemented as a flexible array member. Memory is allocated using + * hwspinlock_bank_alloc(), ensuring space for all lock structures. + */ + struct rt_hwspinlock locks[]; +}; + +/** + * @brief Allocate a hardware spinlock bank with a given number of locks + * + * The allocation includes both the bank structure and the flexible array + * of hardware spinlock objects. + * + * @param obj The object pointer used to store the bank + * @param locks_nr Number of hardware spinlocks to allocate + * + * @return Pointer to the allocated memory or NULL on failure + */ +#define hwspinlock_bank_alloc(obj, locks_nr) \ + rt_calloc(1, sizeof(typeof(*obj)) + sizeof(struct rt_hwspinlock) * (locks_nr)) + +/** + * @brief Compute the global hardware spinlock ID + * + * The global ID is calculated as: + * bank->base_id + index + * where the index is derived from the hardware spinlock's position inside + * the bank->locks array. + * + * @param hwlock Pointer to a hardware spinlock + * + * @return The global lock identifier + */ +rt_inline int hwspinlock_find_id(struct rt_hwspinlock *hwlock) +{ + return hwlock->bank->base_id + (hwlock - &hwlock->bank->locks[0]); +} + +#endif /* __HWSPINLOCK_DM_H__ */ diff --git a/components/drivers/include/drivers/hwspinlock.h b/components/drivers/include/drivers/hwspinlock.h new file mode 100644 index 00000000000..f89c6af517a --- /dev/null +++ b/components/drivers/include/drivers/hwspinlock.h @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-09-23 GuEe-GUI first version + */ + +#ifndef __HWSPINLOCK_H__ +#define __HWSPINLOCK_H__ + +#include + +struct rt_hwspinlock; +struct rt_hwspinlock_ops; +struct rt_hwspinlock_bank; + +/** + * @brief Register a hardware spinlock bank + * + * A bank represents a contiguous set of hardware spinlocks provided by + * a hardware module. This API installs the bank into the system-wide list + * so that each individual lock can be retrieved and used by clients. + * + * @param bank Pointer to the hardware spinlock bank + * @return RT_EOK on success, otherwise an error code + */ +rt_err_t rt_hwspinlock_bank_register(struct rt_hwspinlock_bank *bank); + +/** + * @brief Unregister a hardware spinlock bank + * + * Removes the bank from the global registry. All locks under the bank + * become unavailable. Caller must ensure no active users remain. + * + * @param bank Pointer to the hardware spinlock bank + * @return RT_EOK on success, otherwise an error code + */ +rt_err_t rt_hwspinlock_bank_unregister(struct rt_hwspinlock_bank *bank); + +/** + * @brief Try to acquire a hardware spinlock (raw interface) + * + * The raw interface allows hardware drivers to retrieve the IRQ level + * before acquiring the lock, enabling IRQ-safe usage scenarios. + * + * @param hwlock Pointer to the hardware spinlock + * @param out_irq_level If non-NULL, returns saved IRQ level + * + * @return RT_EOK if lock is acquired, -RT_EBUSY if lock is held by others + */ +rt_err_t rt_hwspin_trylock_raw(struct rt_hwspinlock *hwlock, + rt_ubase_t *out_irq_level); + +/** + * @brief Acquire a hardware spinlock with timeout (raw interface) + * + * Spins or relaxes according to the hardware implementation until the + * lock becomes available or timeout expires. IRQ level handling is optional. + * + * @param hwlock Pointer to the hardware spinlock + * @param timeout_ms Timeout in milliseconds + * @param out_irq_level If non-NULL, returns saved IRQ level + * + * @return RT_EOK on success, -RT_ETIMEOUT if timed out + */ +rt_err_t rt_hwspin_lock_timeout_raw(struct rt_hwspinlock *hwlock, + rt_uint32_t timeout_ms, rt_ubase_t *out_irq_level); + +/** + * @brief Release a hardware spinlock (raw interface) + * + * Restores IRQ state if @p out_irq_level is provided. + * + * @param hwlock Pointer to the hardware spinlock + * @param out_irq_level IRQ level saved during lock operation + */ +void rt_hwspin_unlock_raw(struct rt_hwspinlock *hwlock, + rt_ubase_t *out_irq_level); + +/** + * @brief Try to acquire a hardware spinlock (no IRQ management) + */ +rt_inline rt_err_t rt_hwspin_trylock(struct rt_hwspinlock *hwlock) +{ + return rt_hwspin_trylock_raw(hwlock, RT_NULL); +} + +/** + * @brief Try to acquire a hardware spinlock with IRQ saving + */ +rt_inline rt_err_t rt_hwspin_trylock_irqsave(struct rt_hwspinlock *hwlock, + rt_ubase_t *out_irq_level) +{ + return rt_hwspin_trylock_raw(hwlock, out_irq_level); +} + +/** + * @brief Acquire a hardware spinlock with timeout (no IRQ management) + */ +rt_inline rt_err_t rt_hwspin_lock_timeout(struct rt_hwspinlock *hwlock, + rt_uint32_t timeout_ms) +{ + return rt_hwspin_lock_timeout_raw(hwlock, timeout_ms, RT_NULL); +} + +/** + * @brief Acquire a hardware spinlock with timeout and IRQ saving + */ +rt_inline rt_err_t rt_hwspin_lock_timeout_irqsave(struct rt_hwspinlock *hwlock, + rt_uint32_t timeout_ms, rt_ubase_t *out_level) +{ + return rt_hwspin_lock_timeout_raw(hwlock, timeout_ms, out_level); +} + +/** + * @brief Release a hardware spinlock (no IRQ management) + */ +rt_inline void rt_hwspin_unlock(struct rt_hwspinlock *hwlock) +{ + rt_hwspin_unlock_raw(hwlock, RT_NULL); +} + +/** + * @brief Release a hardware spinlock and restore IRQ level + */ +rt_inline void rt_hwspin_unlock_irqsave(struct rt_hwspinlock *hwlock, + rt_ubase_t *out_level) +{ + rt_hwspin_unlock_raw(hwlock, out_level); +} + +/** + * @brief Acquire a free hardware spinlock from any registered bank + * + * The returned lock must later be released via rt_hwspinlock_put(). + * + * @return Pointer to a hardware spinlock, or NULL if none available + */ +struct rt_hwspinlock *rt_hwspinlock_get(void); + +/** + * @brief Get hardware spinlock by index from a specific device + * + * @param dev Device providing hardware spinlocks + * @param index Hardware spinlock index within device/bank + * + * @return Pointer to the hardware spinlock, or NULL if invalid index + */ +struct rt_hwspinlock *rt_hwspinlock_get_by_index(struct rt_device *dev, int index); + +/** + * @brief Get hardware spinlock by name from a specific device + * + * Names are assigned by device drivers and mapped to lock indices. + * + * @return Pointer to the hardware spinlock, or NULL if name not found + */ +struct rt_hwspinlock *rt_hwspinlock_get_by_name(struct rt_device *dev, const char *name); + +/** + * @brief Release hardware spinlock reference obtained from get() + * + * Does not unlock the spinlock; only decreases usage count. + */ +void rt_hwspinlock_put(struct rt_hwspinlock *hwlock); + +/** + * @brief Get hardware spinlock by index from device tree node + */ +struct rt_hwspinlock *rt_ofw_get_hwspinlock_by_index(struct rt_ofw_node *np, int index); + +/** + * @brief Get hardware spinlock by name from device tree node + */ +struct rt_hwspinlock *rt_ofw_get_hwspinlock_by_name(struct rt_ofw_node *np, const char *name); + +#endif /* __HWSPINLOCK_H__ */ diff --git a/components/drivers/include/rtdevice.h b/components/drivers/include/rtdevice.h index 7007441c6bc..137e6ba5e91 100644 --- a/components/drivers/include/rtdevice.h +++ b/components/drivers/include/rtdevice.h @@ -59,6 +59,10 @@ extern "C" { #include "drivers/mailbox.h" #endif /* RT_USING_MBOX */ +#ifdef RT_USING_HWSPINLOCK +#include "drivers/hwspinlock.h" +#endif /* RT_USING_HWSPINLOCK */ + #ifdef RT_USING_BLK #include "drivers/blk.h" #endif /* RT_USING_BLK */