요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
Lock ID 예약과 수명
hwspinlock.rst:38-87int id = of_hwspin_lock_get_id(dev->of_node, 0);
struct hwspinlock *hwlock;
if (id == -EPROBE_DEFER)
return -EPROBE_DEFER;
hwlock = hwspin_lock_request_specific(id);
if (!hwlock)
return -EBUSY;
/* 사용 종료 후 */
hwspin_lock_free(hwlock);
of_hwspin_lock_get_id()는 Devicetree phandle과 index를 global lock ID로 변환합니다. Provider가 아직 core에 등록되지 않았으면 -EPROBE_DEFER를 반환합니다. request_specific()은 해당 ID를 Linux 내부 사용자에게 예약하고 이미 사용 중이면 NULL을 반환합니다.
ID 조회, request, free, bust는 process context에서 호출하며 sleep할 수 있습니다. bust는 hardware owner ID를 확인한 뒤 비정상 owner의 lock을 강제로 놓는 선택 기능입니다. Provider가 지원하지 않으면 -EOPNOTSUPP입니다.
Timeout 획득 variant
hwspinlock.rst:88-168| API | 성공 후 local 상태 | 사용 조건 |
|---|---|---|
| hwspin_lock_timeout | preemption disabled | 일반 non-sleeping critical section |
| hwspin_lock_timeout_irq | preemption, local IRQ disabled | 같은 CPU interrupt path도 lock을 사용할 때 |
| hwspin_lock_timeout_irqsave | IRQ 이전 상태 저장 후 disabled | 호출 전 IRQ 상태를 정확히 복원해야 할 때 |
| hwspin_lock_timeout_raw | framework의 local software serialization 없음 | caller가 별도 mutex/spinlock으로 경쟁을 막을 때 |
| hwspin_lock_timeout_in_atomic | 기존 atomic context 유지 | timeout을 수 ms 이하로 제한 |
Timeout 함수는 remote processor가 lock을 놓을 때까지 busy-loop하며 절대로 sleep하지 않습니다. 성공 뒤에는 가능한 빨리 unlock해야 합니다. 길게 보유하면 상대 core도 interconnect를 반복 polling하여 bus traffic과 worst-case latency가 함께 증가합니다.
raw variant는 hardware lock 획득을 둘러싼 Linux 내부 경쟁을 core가 보호하지 않습니다. 같은 Linux kernel 안의 여러 caller가 동시에 raw acquire를 시도할 수 있다면 외부 mutex 또는 spinlock으로 획득 절차를 직렬화해야 합니다.
Trylock과 unlock 짝
hwspinlock.rst:170-302hwspin_trylock 계열은 한 번만 시도하여 이미 보유 중이면 -EBUSY를 반환합니다. timeout과 마찬가지로 plain, irq, irqsave, raw, in_atomic variant가 있으며 성공 후 local state와 정확히 맞는 unlock 함수를 사용해야 합니다.
| Acquire | 반드시 대응할 release |
|---|---|
| hwspin_lock_timeout / hwspin_trylock | hwspin_unlock |
| *_irq | hwspin_unlock_irq |
| *_irqsave | hwspin_unlock_irqrestore |
| *_raw | hwspin_unlock_raw |
| *_in_atomic | hwspin_unlock_in_atomic |
이미 unlocked인 hardware lock을 다시 unlock하는 보호 장치는 없습니다. 잘못된 release는 다른 processor가 획득한 lock을 풀거나 hardware state를 손상시킬 수 있는 bug입니다.
일반적인 consumer 흐름
hwspinlock.rst:304-346hwlock = hwspin_lock_request_specific(PREDEFINED_LOCK_ID);
if (!hwlock)
return -EBUSY;
ret = hwspin_trylock(hwlock);
if (ret)
goto out_free;
/* shared memory metadata 갱신. 절대로 sleep하지 않는다. */
hwspin_unlock(hwlock);
out_free:
hwspin_lock_free(hwlock);
Board 또는 probe 단계에서 ID를 예약하고 data path에서는 짧은 trylock 또는 bounded timeout을 사용합니다. Lock을 보유한 채 firmware 응답, DMA completion, mutex, allocation처럼 sleep하거나 오래 걸릴 수 있는 작업을 기다려서는 안 됩니다.
Provider driver 구조와 callback
hwspinlock.rst:348-442Provider는 hardware lock bank를 hwspin_lock_register()로 등록합니다. hwspinlock_device에는 device, ops, 첫 global ID, lock 개수와 개별 hwspinlock array가 들어갑니다. 개별 lock의 bank와 core spinlock은 framework가 초기화하고 provider는 보통 priv에 register address나 lock index를 저장합니다.
static const struct hwspinlock_ops ops = {
.trylock = vendor_trylock, /* 1 성공, 0 실패 */
.unlock = vendor_unlock, /* 항상 성공해야 함 */
.relax = vendor_relax, /* 선택: 재시도 사이 delay */
};
trylock과 unlock callback은 필수이며 sleep할 수 없습니다. relax는 polling loop에서 연속 register access를 줄이기 위한 선택 callback이고 역시 sleep할 수 없습니다. unregister는 bank의 lock이 아직 consumer에게 할당되어 있으면 실패해야 합니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
===========================
Hardware Spinlock Framework
===========================
Introduction
============
Hardware spinlock modules provide hardware assistance for synchronization
and mutual exclusion between heterogeneous processors and those not operating
under a single, shared operating system.
For example, OMAP4 has dual Cortex-A9, dual Cortex-M3 and a C64x+ DSP,
each of which is running a different Operating System (the master, A9,
is usually running Linux and the slave processors, the M3 and the DSP,
are running some flavor of RTOS).
A generic hwspinlock framework allows platform-independent drivers to use
the hwspinlock device in order to access data structures that are shared
between remote processors, that otherwise have no alternative mechanism
to accomplish synchronization and mutual exclusion operations.
This is necessary, for example, for Inter-processor communications:
on OMAP4, cpu-intensive multimedia tasks are offloaded by the host to the
remote M3 and/or C64x+ slave processors (by an IPC subsystem called Syslink).
To achieve fast message-based communications, a minimal kernel support
is needed to deliver messages arriving from a remote processor to the
appropriate user process.
This communication is based on simple data structures that is shared between
the remote processors, and access to it is synchronized using the hwspinlock
module (remote processor directly places new messages in this shared data
structure).
A common hwspinlock interface makes it possible to have generic, platform-
independent, drivers.
User API
========
::
struct hwspinlock *hwspin_lock_request_specific(unsigned int id);
Assign a specific hwspinlock id and return its address, or NULL
if that hwspinlock is already in use. Usually board code will
be calling this function in order to reserve specific hwspinlock
ids for predefined purposes.
Should be called from a process context (might sleep).
::
int of_hwspin_lock_get_id(struct device_node *np, int index);
Retrieve the global lock id for an OF phandle-based specific lock.
This function provides a means for DT users of a hwspinlock module
to get the global lock id of a specific hwspinlock, so that it can
be requested using the normal hwspin_lock_request_specific() API.
The function returns a lock id number on success, -EPROBE_DEFER if
the hwspinlock device is not yet registered with the core, or other
error values.
Should be called from a process context (might sleep).
::
int hwspin_lock_free(struct hwspinlock *hwlock);
Free a previously-assigned hwspinlock; returns 0 on success, or an
appropriate error code on failure (e.g. -EINVAL if the hwspinlock
is already free).
Should be called from a process context (might sleep).
::
int hwspin_lock_bust(struct hwspinlock *hwlock, unsigned int id);
After verifying the owner of the hwspinlock, release a previously acquired
hwspinlock; returns 0 on success, or an appropriate error code on failure
(e.g. -EOPNOTSUPP if the bust operation is not defined for the specific
hwspinlock).
Should be called from a process context (might sleep).
::
int hwspin_lock_timeout(struct hwspinlock *hwlock, unsigned int timeout);
Lock a previously-assigned hwspinlock with a timeout limit (specified in
msecs). If the hwspinlock is already taken, the function will busy loop
waiting for it to be released, but give up when the timeout elapses.
Upon a successful return from this function, preemption is disabled so
the caller must not sleep, and is advised to release the hwspinlock as
soon as possible, in order to minimize remote cores polling on the
hardware interconnect.
Returns 0 when successful and an appropriate error code otherwise (most
notably -ETIMEDOUT if the hwspinlock is still busy after timeout msecs).
The function will never sleep.
::
int hwspin_lock_timeout_irq(struct hwspinlock *hwlock, unsigned int timeout);
Lock a previously-assigned hwspinlock with a timeout limit (specified in
msecs). If the hwspinlock is already taken, the function will busy loop
waiting for it to be released, but give up when the timeout elapses.
Upon a successful return from this function, preemption and the local
interrupts are disabled, so the caller must not sleep, and is advised to
release the hwspinlock as soon as possible.
Returns 0 when successful and an appropriate error code otherwise (most
notably -ETIMEDOUT if the hwspinlock is still busy after timeout msecs).
The function will never sleep.
::
int hwspin_lock_timeout_irqsave(struct hwspinlock *hwlock, unsigned int to,
unsigned long *flags);
Lock a previously-assigned hwspinlock with a timeout limit (specified in
msecs). If the hwspinlock is already taken, the function will busy loop
waiting for it to be released, but give up when the timeout elapses.
Upon a successful return from this function, preemption is disabled,
local interrupts are disabled and their previous state is saved at the
given flags placeholder. The caller must not sleep, and is advised to
release the hwspinlock as soon as possible.
Returns 0 when successful and an appropriate error code otherwise (most
notably -ETIMEDOUT if the hwspinlock is still busy after timeout msecs).
The function will never sleep.
::
int hwspin_lock_timeout_raw(struct hwspinlock *hwlock, unsigned int timeout);
Lock a previously-assigned hwspinlock with a timeout limit (specified in
msecs). If the hwspinlock is already taken, the function will busy loop
waiting for it to be released, but give up when the timeout elapses.
Caution: User must protect the routine of getting hardware lock with mutex
or spinlock to avoid dead-lock, that will let user can do some time-consuming
or sleepable operations under the hardware lock.
Returns 0 when successful and an appropriate error code otherwise (most
notably -ETIMEDOUT if the hwspinlock is still busy after timeout msecs).
The function will never sleep.
::
int hwspin_lock_timeout_in_atomic(struct hwspinlock *hwlock, unsigned int to);
Lock a previously-assigned hwspinlock with a timeout limit (specified in
msecs). If the hwspinlock is already taken, the function will busy loop
waiting for it to be released, but give up when the timeout elapses.
This function shall be called only from an atomic context and the timeout
value shall not exceed a few msecs.
Returns 0 when successful and an appropriate error code otherwise (most
notably -ETIMEDOUT if the hwspinlock is still busy after timeout msecs).
The function will never sleep.
::
int hwspin_trylock(struct hwspinlock *hwlock);
Attempt to lock a previously-assigned hwspinlock, but immediately fail if
it is already taken.
Upon a successful return from this function, preemption is disabled so
caller must not sleep, and is advised to release the hwspinlock as soon as
possible, in order to minimize remote cores polling on the hardware
interconnect.
Returns 0 on success and an appropriate error code otherwise (most
notably -EBUSY if the hwspinlock was already taken).
The function will never sleep.
::
int hwspin_trylock_irq(struct hwspinlock *hwlock);
Attempt to lock a previously-assigned hwspinlock, but immediately fail if
it is already taken.
Upon a successful return from this function, preemption and the local
interrupts are disabled so caller must not sleep, and is advised to
release the hwspinlock as soon as possible.
Returns 0 on success and an appropriate error code otherwise (most
notably -EBUSY if the hwspinlock was already taken).
The function will never sleep.
::
int hwspin_trylock_irqsave(struct hwspinlock *hwlock, unsigned long *flags);
Attempt to lock a previously-assigned hwspinlock, but immediately fail if
it is already taken.
Upon a successful return from this function, preemption is disabled,
the local interrupts are disabled and their previous state is saved
at the given flags placeholder. The caller must not sleep, and is advised
to release the hwspinlock as soon as possible.
Returns 0 on success and an appropriate error code otherwise (most
notably -EBUSY if the hwspinlock was already taken).
The function will never sleep.
::
int hwspin_trylock_raw(struct hwspinlock *hwlock);
Attempt to lock a previously-assigned hwspinlock, but immediately fail if
it is already taken.
Caution: User must protect the routine of getting hardware lock with mutex
or spinlock to avoid dead-lock, that will let user can do some time-consuming
or sleepable operations under the hardware lock.
Returns 0 on success and an appropriate error code otherwise (most
notably -EBUSY if the hwspinlock was already taken).
The function will never sleep.
::
int hwspin_trylock_in_atomic(struct hwspinlock *hwlock);
Attempt to lock a previously-assigned hwspinlock, but immediately fail if
it is already taken.
This function shall be called only from an atomic context.
Returns 0 on success and an appropriate error code otherwise (most
notably -EBUSY if the hwspinlock was already taken).
The function will never sleep.
::
void hwspin_unlock(struct hwspinlock *hwlock);
Unlock a previously-locked hwspinlock. Always succeed, and can be called
from any context (the function never sleeps).
.. note::
code should **never** unlock an hwspinlock which is already unlocked
(there is no protection against this).
::
void hwspin_unlock_irq(struct hwspinlock *hwlock);
Unlock a previously-locked hwspinlock and enable local interrupts.
The caller should **never** unlock an hwspinlock which is already unlocked.
Doing so is considered a bug (there is no protection against this).
Upon a successful return from this function, preemption and local
interrupts are enabled. This function will never sleep.
::
void
hwspin_unlock_irqrestore(struct hwspinlock *hwlock, unsigned long *flags);
Unlock a previously-locked hwspinlock.
The caller should **never** unlock an hwspinlock which is already unlocked.
Doing so is considered a bug (there is no protection against this).
Upon a successful return from this function, preemption is reenabled,
and the state of the local interrupts is restored to the state saved at
the given flags. This function will never sleep.
::
void hwspin_unlock_raw(struct hwspinlock *hwlock);
Unlock a previously-locked hwspinlock.
The caller should **never** unlock an hwspinlock which is already unlocked.
Doing so is considered a bug (there is no protection against this).
This function will never sleep.
::
void hwspin_unlock_in_atomic(struct hwspinlock *hwlock);
Unlock a previously-locked hwspinlock.
The caller should **never** unlock an hwspinlock which is already unlocked.
Doing so is considered a bug (there is no protection against this).
This function will never sleep.
Typical usage
=============
::
#include <linux/hwspinlock.h>
#include <linux/err.h>
int hwspinlock_example(void)
{
struct hwspinlock *hwlock;
int ret;
/*
* assign a specific hwspinlock id - this should be called early
* by board init code.
*/
hwlock = hwspin_lock_request_specific(PREDEFINED_LOCK_ID);
if (!hwlock)
...
/* try to take it, but don't spin on it */
ret = hwspin_trylock(hwlock);
if (!ret) {
pr_info("lock is already taken\n");
return -EBUSY;
}
/*
* we took the lock, do our thing now, but do NOT sleep
*/
/* release the lock */
hwspin_unlock(hwlock);
/* free the lock */
ret = hwspin_lock_free(hwlock);
if (ret)
...
return ret;
}
API for implementors
====================
::
int hwspin_lock_register(struct hwspinlock_device *bank, struct device *dev,
const struct hwspinlock_ops *ops, int base_id, int num_locks);
To be called from the underlying platform-specific implementation, in
order to register a new hwspinlock device (which is usually a bank of
numerous locks). Should be called from a process context (this function
might sleep).
Returns 0 on success, or appropriate error code on failure.
::
int hwspin_lock_unregister(struct hwspinlock_device *bank);
To be called from the underlying vendor-specific implementation, in order
to unregister an hwspinlock device (which is usually a bank of numerous
locks).
Should be called from a process context (this function might sleep).
Returns the address of hwspinlock on success, or NULL on error (e.g.
if the hwspinlock is still in use).
Important structs
=================
struct hwspinlock_device is a device which usually contains a bank
of hardware locks. It is registered by the underlying hwspinlock
implementation using the hwspin_lock_register() API.
::
/**
* struct hwspinlock_device - a device which usually spans numerous hwspinlocks
* @dev: underlying device, will be used to invoke runtime PM api
* @ops: platform-specific hwspinlock handlers
* @base_id: id index of the first lock in this device
* @num_locks: number of locks in this device
* @lock: dynamically allocated array of 'struct hwspinlock'
*/
struct hwspinlock_device {
struct device *dev;
const struct hwspinlock_ops *ops;
int base_id;
int num_locks;
struct hwspinlock lock[0];
};
struct hwspinlock_device contains an array of hwspinlock structs, each
of which represents a single hardware lock::
/**
* struct hwspinlock - this struct represents a single hwspinlock instance
* @bank: the hwspinlock_device structure which owns this lock
* @lock: initialized and used by hwspinlock core
* @priv: private data, owned by the underlying platform-specific hwspinlock drv
*/
struct hwspinlock {
struct hwspinlock_device *bank;
spinlock_t lock;
void *priv;
};
When registering a bank of locks, the hwspinlock driver only needs to
set the priv members of the locks. The rest of the members are set and
initialized by the hwspinlock core itself.
Implementation callbacks
========================
There are three possible callbacks defined in 'struct hwspinlock_ops'::
struct hwspinlock_ops {
int (*trylock)(struct hwspinlock *lock);
void (*unlock)(struct hwspinlock *lock);
void (*relax)(struct hwspinlock *lock);
};
The first two callbacks are mandatory:
The ->trylock() callback should make a single attempt to take the lock, and
return 0 on failure and 1 on success. This callback may **not** sleep.
The ->unlock() callback releases the lock. It always succeed, and it, too,
may **not** sleep.
The ->relax() callback is optional. It is called by hwspinlock core while
spinning on a lock, and can be used by the underlying implementation to force
a delay between two successive invocations of ->trylock(). It may **not** sleep.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
서로 다른 processor와 OS 사이의 hardware lock
1-36Hardware spinlock module은 heterogeneous processor 사이, 특히 하나의 공유 operating system 아래에서 실행되지 않는 processor 사이에서 synchronization과 mutual exclusion을 hardware로 지원한다.
예를 들어 OMAP4에는 Cortex-A9 두 개, Cortex-M3 두 개, C64x+ DSP가 함께 들어 있다. Master인 A9에서는 보통 Linux가 실행되고 slave processor인 M3와 DSP에서는 각각 RTOS 계열 operating system이 실행된다. 서로 다른 OS scheduler와 software lock domain을 사용하므로 Linux의 일반 spinlock만으로는 모든 core를 직렬화할 수 없다.
Generic hwspinlock framework를 사용하면 platform-independent driver가 hwspinlock device를 통해 remote processor와 공유하는 data structure에 접근할 수 있다. 별도의 synchronization 수단이 없는 processor들 사이에서도 이 framework가 mutual exclusion을 제공하므로 driver를 특정 SoC 구현에 묶지 않을 수 있다.
대표적인 사용처는 inter-processor communication이다. OMAP4에서는 host가 CPU 사용량이 많은 multimedia task를 Syslink라는 IPC subsystem을 통해 remote M3 또는 C64x+ slave processor로 넘긴다. 빠른 message 기반 통신을 위해서는 remote processor에서 도착한 message를 적절한 user process로 전달하는 최소한의 kernel 지원이 필요하다.
이 통신은 remote processor가 새 message를 직접 기록하는 단순한 shared data structure를 기반으로 한다. 여러 processor의 동시 접근은 hwspinlock module로 조정한다. 공통 hwspinlock interface가 있기 때문에 이 IPC driver를 generic하고 platform-independent하게 작성할 수 있다.
Lock 식별자 조회, 할당, 해제와 강제 회수
38-86struct hwspinlock *hwspin_lock_request_specific(unsigned int id);
지정한 hwspinlock id를 caller에게 할당하고 그 object 주소를 반환한다. 해당 lock이 이미 사용 중이면 NULL을 반환한다. Board code가 미리 정해진 용도로 특정 lock id를 예약할 때 주로 호출한다. 내부에서 sleep할 수 있으므로 process context에서 호출해야 한다.
int of_hwspin_lock_get_id(struct device_node *np, int index);
OF phandle로 지정된 lock의 global lock id를 얻는다. Device Tree에서 hwspinlock module을 참조하는 사용자가 특정 hwspinlock의 global id를 구한 다음 일반 hwspin_lock_request_specific() API로 요청할 수 있게 한다. 성공하면 lock id를, hwspinlock device가 아직 core에 등록되지 않았으면 -EPROBE_DEFER를, 그 밖의 실패에는 해당 error 값을 반환한다. 이 함수도 sleep할 수 있으므로 process context에서 호출해야 한다.
int hwspin_lock_free(struct hwspinlock *hwlock);
앞서 할당한 hwspinlock을 반납한다. 성공하면 0을 반환하고, 이미 free 상태인 lock을 넘긴 경우의 -EINVAL처럼 실패 원인에 맞는 error code를 반환한다. Sleep할 수 있으므로 process context에서 호출한다.
int hwspin_lock_bust(struct hwspinlock *hwlock, unsigned int id);
Hardware lock의 owner를 확인한 뒤 이미 획득된 hwspinlock을 강제로 release한다. 성공하면 0을 반환하며, 해당 hwspinlock 구현에 bust operation이 없으면 -EOPNOTSUPP 같은 error code를 반환한다. 이 함수 역시 sleep할 수 있으므로 process context 전용이다.
free는 framework에서 lock object의 할당 상태를 끝내는 operation이고, bust는 hardware가 owner로 기록한 주체를 확인한 뒤 비정상적으로 남은 lock을 회수하는 operation이다. 일반적인 critical section 종료에는 아래의 hwspin_unlock*() family를 사용한다.
Timeout을 사용하는 blocking-free 획득 API
88-168hwspin_lock_timeout*() family는 이미 할당된 hardware lock을 millisecond 단위 timeout 안에서 획득하려고 한다. 다른 processor가 lock을 잡고 있으면 sleep하지 않고 busy loop로 release를 기다리며, 제한 시간이 지나면 포기한다. 성공 시 0, 실패 시 적절한 error code를 반환하며 대표적인 timeout 결과는 -ETIMEDOUT이다.
| API | 성공 후 local 상태 | 호출 규칙 |
|---|---|---|
| hwspin_lock_timeout(hwlock, timeout) | preemption disabled | sleep 금지, 가능한 빨리 unlock |
| hwspin_lock_timeout_irq(hwlock, timeout) | preemption disabled, local IRQ disabled | sleep 금지, 가능한 빨리 unlock |
| hwspin_lock_timeout_irqsave(hwlock, to, flags) | preemption disabled, local IRQ disabled, 이전 IRQ 상태를 *flags에 저장 | 짝이 되는 irqrestore unlock 필요 |
| hwspin_lock_timeout_raw(hwlock, timeout) | framework가 local software serialization을 제공하지 않음 | 사용자가 mutex 또는 spinlock으로 획득 경로 보호 |
| hwspin_lock_timeout_in_atomic(hwlock, to) | atomic context 유지 | atomic context 전용, timeout은 수 ms 이하 |
int hwspin_lock_timeout(struct hwspinlock *hwlock,
unsigned int timeout);
int hwspin_lock_timeout_irq(struct hwspinlock *hwlock,
unsigned int timeout);
int hwspin_lock_timeout_irqsave(struct hwspinlock *hwlock,
unsigned int to,
unsigned long *flags);
int hwspin_lock_timeout_raw(struct hwspinlock *hwlock,
unsigned int timeout);
int hwspin_lock_timeout_in_atomic(struct hwspinlock *hwlock,
unsigned int to);
기본 hwspin_lock_timeout()이 성공하면 preemption이 disabled 상태가 된다. Caller는 sleep할 수 없으며 remote core가 hardware interconnect를 계속 polling하는 시간을 줄이기 위해 critical section을 짧게 끝내고 가능한 빨리 hwspinlock을 release해야 한다.
_irq variant가 성공하면 preemption과 local interrupt가 모두 disabled 상태다. _irqsave variant는 여기에 더해 lock 진입 전 local interrupt 상태를 flags가 가리키는 저장 위치에 기록한다. 따라서 release할 때 같은 flags를 hwspin_unlock_irqrestore()에 전달해야 원래 IRQ 상태를 복원할 수 있다.
_raw variant를 사용할 때는 hardware lock을 얻는 전체 절차를 사용자 쪽 mutex 또는 spinlock으로 보호하여 deadlock을 피해야 한다. 이렇게 별도 local serialization을 책임지는 대신 hardware lock 아래에서 시간이 오래 걸리거나 sleep 가능한 operation을 수행할 수 있다. 단, 이 함수 자체는 sleep하지 않는다.
_in_atomic variant는 atomic context에서만 호출해야 하며 timeout 값을 수 millisecond보다 크게 두어서는 안 된다. 모든 timeout variant는 lock을 기다리는 동안 busy loop하며 함수 자체가 sleep하는 일은 없다.
즉시 성공하거나 -EBUSY로 끝나는 trylock API
170-246hwspin_trylock*() family는 이미 할당된 hwspinlock을 한 번 획득해 본다. Hardware lock이 이미 사용 중이면 기다리지 않고 즉시 실패한다. 성공 시 0을 반환하며, 이미 점유된 경우의 대표적인 error code는 -EBUSY다. 모든 variant는 sleep하지 않는다.
| API | 성공 후 local 상태 | 주의점 |
|---|---|---|
| hwspin_trylock(hwlock) | preemption disabled | sleep 금지, remote polling을 줄이도록 즉시 release |
| hwspin_trylock_irq(hwlock) | preemption disabled, local IRQ disabled | hwspin_unlock_irq()와 대응 |
| hwspin_trylock_irqsave(hwlock, flags) | preemption disabled, local IRQ disabled, 이전 IRQ 상태 저장 | hwspin_unlock_irqrestore()와 대응 |
| hwspin_trylock_raw(hwlock) | raw acquisition | 사용자가 mutex 또는 spinlock으로 획득 경로 보호 |
| hwspin_trylock_in_atomic(hwlock) | atomic context 유지 | atomic context에서만 호출 |
int hwspin_trylock(struct hwspinlock *hwlock);
int hwspin_trylock_irq(struct hwspinlock *hwlock);
int hwspin_trylock_irqsave(struct hwspinlock *hwlock,
unsigned long *flags);
int hwspin_trylock_raw(struct hwspinlock *hwlock);
int hwspin_trylock_in_atomic(struct hwspinlock *hwlock);
기본 variant가 성공하면 preemption이 disabled되므로 caller는 sleep할 수 없다. Remote core가 hardware interconnect를 polling하는 시간을 최소화하려면 hardware lock 보유 시간을 짧게 해야 한다. _irq와 _irqsave는 timeout family와 같은 방식으로 local interrupt 상태까지 관리한다.
_raw variant 사용자는 hardware lock 획득 routine 자체를 mutex 또는 spinlock으로 보호해 deadlock을 피해야 한다. 이 방식을 통해 hardware lock 아래에서 시간이 오래 걸리거나 sleep 가능한 operation을 수행할 수 있다. _in_atomic variant는 atomic context에서만 호출한다.
획득 방식과 짝을 이루는 unlock API
248-302hwspin_unlock*() family는 획득한 hardware lock을 release한다. 이미 unlocked 상태인 lock을 다시 unlock해서는 절대 안 된다. Framework에는 이를 막는 보호 장치가 없으며 그런 호출은 bug로 간주된다. 모든 unlock variant는 sleep하지 않는다.
| API | release 뒤의 local 상태 |
|---|---|
| hwspin_unlock(hwlock) | hardware lock release, 모든 context에서 호출 가능 |
| hwspin_unlock_irq(hwlock) | hardware lock release, preemption과 local IRQ enable |
| hwspin_unlock_irqrestore(hwlock, flags) | preemption enable, local IRQ를 *flags에 저장된 이전 상태로 복원 |
| hwspin_unlock_raw(hwlock) | raw hardware lock release |
| hwspin_unlock_in_atomic(hwlock) | atomic-context용 hardware lock release |
void hwspin_unlock(struct hwspinlock *hwlock);
void hwspin_unlock_irq(struct hwspinlock *hwlock);
void hwspin_unlock_irqrestore(struct hwspinlock *hwlock,
unsigned long *flags);
void hwspin_unlock_raw(struct hwspinlock *hwlock);
void hwspin_unlock_in_atomic(struct hwspinlock *hwlock);
획득 API의 suffix와 release API를 맞춰야 한다. 특히 _irqsave로 저장한 flags는 동일한 critical section의 _irqrestore에 넘겨야 하며, _irq variant는 무조건 local IRQ를 다시 enable한다.
원문의 일반 사용 예제
304-345#include <linux/hwspinlock.h>
#include <linux/err.h>
int hwspinlock_example(void)
{
struct hwspinlock *hwlock;
int ret;
/*
* assign a specific hwspinlock id - this should be called early
* by board init code.
*/
hwlock = hwspin_lock_request_specific(PREDEFINED_LOCK_ID);
if (!hwlock)
...
/* try to take it, but don't spin on it */
ret = hwspin_trylock(hwlock);
if (!ret) {
pr_info("lock is already taken\n");
return -EBUSY;
}
/*
* we took the lock, do our thing now, but do NOT sleep
*/
/* release the lock */
hwspin_unlock(hwlock);
/* free the lock */
ret = hwspin_lock_free(hwlock);
if (ret)
...
return ret;
}
예제는 board 초기화 단계에서 PREDEFINED_LOCK_ID를 예약하고, hwspin_trylock()으로 기다리지 않는 획득을 시도한 뒤, lock을 잡은 동안 sleep하지 않고 작업을 수행하고, hwspin_unlock()과 hwspin_lock_free() 순서로 release와 object 반납을 수행하는 구조를 보여 준다.
Linux v6.18.37 원문은 hwspin_trylock()이 성공하면 0을 반환한다고 앞에서 설명하지만, 이 예제는 if (!ret) 분기에서 "lock is already taken"을 출력한다. 설명과 예제의 판정이 서로 반대이므로 실제 code를 작성할 때 사용하는 kernel version의 include/linux/hwspinlock.h와 driver/core/hwspinlock_core.c 구현을 반드시 확인해야 한다. 위 snippet은 원문 보존을 위해 수정하지 않았다.
Hwspinlock provider 등록과 해제
348-374int hwspin_lock_register(struct hwspinlock_device *bank,
struct device *dev,
const struct hwspinlock_ops *ops,
int base_id, int num_locks);
Platform-specific 구현이 새 hwspinlock device를 core에 등록할 때 호출한다. 하나의 device는 보통 다수의 hardware lock을 포함하는 bank다. 함수가 sleep할 수 있으므로 process context에서 호출해야 하며 성공하면 0, 실패하면 적절한 error code를 반환한다.
int hwspin_lock_unregister(struct hwspinlock_device *bank);
Vendor-specific 구현이 등록된 hwspinlock device bank를 core에서 제거할 때 호출한다. 이 함수도 sleep할 수 있으므로 process context에서 호출해야 한다.
원문은 hwspin_lock_unregister()의 prototype을 int 반환형으로 제시하면서, 성공하면 hwspinlock 주소를 반환하고 실패하면 NULL을 반환한다고 설명한다. 선언과 설명이 일치하지 않는 부분이므로 원문을 그대로 보존했으며, 실제 동작은 해당 kernel source의 구현과 header 선언을 기준으로 판단해야 한다.
핵심 data structure와 ownership
376-418struct hwspinlock_device는 보통 hardware lock bank 하나를 나타낸다. 하위 hwspinlock provider가 hwspin_lock_register()를 호출하여 core에 등록한다.
/**
* struct hwspinlock_device - a device which usually spans numerous hwspinlocks
* @dev: underlying device, will be used to invoke runtime PM api
* @ops: platform-specific hwspinlock handlers
* @base_id: id index of the first lock in this device
* @num_locks: number of locks in this device
* @lock: dynamically allocated array of 'struct hwspinlock'
*/
struct hwspinlock_device {
struct device *dev;
const struct hwspinlock_ops *ops;
int base_id;
int num_locks;
struct hwspinlock lock[0];
};
- dev는 underlying device이며 runtime PM API 호출에 사용된다.
- ops는 platform-specific hardware operation callback 집합이다.
- base_id는 이 bank에 속한 첫 번째 lock의 global id index다.
- num_locks는 bank가 제공하는 lock 수다.
- lock은 struct hwspinlock의 동적 array다.
hwspinlock_device 안의 각 struct hwspinlock은 hardware lock instance 하나를 나타낸다.
/**
* struct hwspinlock - this struct represents a single hwspinlock instance
* @bank: the hwspinlock_device structure which owns this lock
* @lock: initialized and used by hwspinlock core
* @priv: private data, owned by the underlying platform-specific hwspinlock drv
*/
struct hwspinlock {
struct hwspinlock_device *bank;
spinlock_t lock;
void *priv;
};
bank는 이 lock을 소유한 hwspinlock_device를 가리킨다. 내부 spinlock_t lock은 hwspinlock core가 초기화하고 사용한다. priv는 platform-specific provider driver가 소유하는 private data다. Lock bank를 등록할 때 provider driver가 직접 설정해야 하는 것은 각 lock의 priv member뿐이며, 나머지는 hwspinlock core가 설정하고 초기화한다.
Provider callback 구현 규칙
420-441struct hwspinlock_ops {
int (*trylock)(struct hwspinlock *lock);
void (*unlock)(struct hwspinlock *lock);
void (*relax)(struct hwspinlock *lock);
};
struct hwspinlock_ops에는 trylock, unlock, relax 세 callback이 있다. 앞의 두 callback은 필수이고 relax는 선택 사항이다.
- ->trylock(): hardware lock 획득을 정확히 한 번 시도한다. 실패하면 0, 성공하면 1을 반환하며 절대로 sleep해서는 안 된다.
- ->unlock(): hardware lock을 release한다. 항상 성공해야 하며 이 callback도 sleep할 수 없다.
- ->relax(): hwspinlock core가 lock을 얻기 위해 spin하는 동안 호출할 수 있는 선택 callback이다. 연속된 ->trylock() 호출 사이에 platform-specific delay를 넣는 데 사용할 수 있고, sleep해서는 안 된다.
Provider callback의 ->trylock() 반환 규약은 user API인 hwspin_trylock()의 반환 규약과 다르다. Provider callback은 성공 1·실패 0이고, user API 설명은 성공 0·실패 error code다. 두 계층을 혼동하면 획득 성공 여부를 반대로 처리하게 된다.
서로 다른 processor 사이의 lock
hwspinlock.rst:5-36Hardware spinlock은 하나의 kernel scheduler나 동일한 software lock implementation을 공유하지 않는 heterogeneous processor 사이에서 mutual exclusion을 제공합니다. 예를 들어 Linux를 실행하는 Cortex-A 계열 host와 RTOS를 실행하는 Cortex-M, DSP가 같은 SRAM message queue를 접근할 때 일반 spinlock_t는 상대 processor에게 아무 의미가 없습니다.
SoC의 hwspinlock IP는 interconnect를 통해 보이는 lock register bank를 제공합니다. 각 processor가 정해진 register operation으로 lock 소유를 시도하고 한 processor만 성공합니다. Linux framework는 이 vendor-specific operation을 공통 API로 감싸 IPC, remoteproc 연계 driver가 platform에 독립적으로 사용할 수 있게 합니다.
Hwspinlock은 cache coherency, memory barrier, shared buffer lifetime을 대신 해결하지 않습니다. Lock 획득 전후에 hardware manual이 요구하는 memory ordering과 cache maintenance를 별도로 지켜야 합니다.