← Documents Documentation/locking/hwspinlock.rst GitHub 원문 ↗

Linux 6.18.37 · Locking

Hardware spinlock framework

Linux, RTOS와 DSP처럼 서로 다른 OS를 실행하는 processor들이 shared memory를 직렬화하는 hardware spinlock API와 driver callback을 설명합니다.

Source pathDocumentation/locking/hwspinlock.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

서로 다른 processor 사이의 lock

hwspinlock.rst:5-36

Hardware 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를 별도로 지켜야 합니다.

Lock ID 예약과 수명

hwspinlock.rst:38-87
int 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_timeoutpreemption disabled일반 non-sleeping critical section
hwspin_lock_timeout_irqpreemption, local IRQ disabled같은 CPU interrupt path도 lock을 사용할 때
hwspin_lock_timeout_irqsaveIRQ 이전 상태 저장 후 disabled호출 전 IRQ 상태를 정확히 복원해야 할 때
hwspin_lock_timeout_rawframework의 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-302

hwspin_trylock 계열은 한 번만 시도하여 이미 보유 중이면 -EBUSY를 반환합니다. timeout과 마찬가지로 plain, irq, irqsave, raw, in_atomic variant가 있으며 성공 후 local state와 정확히 맞는 unlock 함수를 사용해야 합니다.

Acquire반드시 대응할 release
hwspin_lock_timeout / hwspin_trylockhwspin_unlock
*_irqhwspin_unlock_irq
*_irqsavehwspin_unlock_irqrestore
*_rawhwspin_unlock_raw
*_in_atomichwspin_unlock_in_atomic

이미 unlocked인 hardware lock을 다시 unlock하는 보호 장치는 없습니다. 잘못된 release는 다른 processor가 획득한 lock을 풀거나 hardware state를 손상시킬 수 있는 bug입니다.

일반적인 consumer 흐름

hwspinlock.rst:304-346
hwlock = 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-442

Provider는 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 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 ===========================
2 Hardware Spinlock Framework
3 ===========================
4
5 Introduction
6 ============
7
8 Hardware spinlock modules provide hardware assistance for synchronization
9 and mutual exclusion between heterogeneous processors and those not operating
10 under a single, shared operating system.
11
12 For example, OMAP4 has dual Cortex-A9, dual Cortex-M3 and a C64x+ DSP,
13 each of which is running a different Operating System (the master, A9,
14 is usually running Linux and the slave processors, the M3 and the DSP,
15 are running some flavor of RTOS).
16
17 A generic hwspinlock framework allows platform-independent drivers to use
18 the hwspinlock device in order to access data structures that are shared
19 between remote processors, that otherwise have no alternative mechanism
20 to accomplish synchronization and mutual exclusion operations.
21
22 This is necessary, for example, for Inter-processor communications:
23 on OMAP4, cpu-intensive multimedia tasks are offloaded by the host to the
24 remote M3 and/or C64x+ slave processors (by an IPC subsystem called Syslink).
25
26 To achieve fast message-based communications, a minimal kernel support
27 is needed to deliver messages arriving from a remote processor to the
28 appropriate user process.
29
30 This communication is based on simple data structures that is shared between
31 the remote processors, and access to it is synchronized using the hwspinlock
32 module (remote processor directly places new messages in this shared data
33 structure).
34
35 A common hwspinlock interface makes it possible to have generic, platform-
36 independent, drivers.
37
38 User API
39 ========
40
41 ::
42
43 struct hwspinlock *hwspin_lock_request_specific(unsigned int id);
44
45 Assign a specific hwspinlock id and return its address, or NULL
46 if that hwspinlock is already in use. Usually board code will
47 be calling this function in order to reserve specific hwspinlock
48 ids for predefined purposes.
49
50 Should be called from a process context (might sleep).
51
52 ::
53
54 int of_hwspin_lock_get_id(struct device_node *np, int index);
55
56 Retrieve the global lock id for an OF phandle-based specific lock.
57 This function provides a means for DT users of a hwspinlock module
58 to get the global lock id of a specific hwspinlock, so that it can
59 be requested using the normal hwspin_lock_request_specific() API.
60
61 The function returns a lock id number on success, -EPROBE_DEFER if
62 the hwspinlock device is not yet registered with the core, or other
63 error values.
64
65 Should be called from a process context (might sleep).
66
67 ::
68
69 int hwspin_lock_free(struct hwspinlock *hwlock);
70
71 Free a previously-assigned hwspinlock; returns 0 on success, or an
72 appropriate error code on failure (e.g. -EINVAL if the hwspinlock
73 is already free).
74
75 Should be called from a process context (might sleep).
76
77 ::
78
79 int hwspin_lock_bust(struct hwspinlock *hwlock, unsigned int id);
80
81 After verifying the owner of the hwspinlock, release a previously acquired
82 hwspinlock; returns 0 on success, or an appropriate error code on failure
83 (e.g. -EOPNOTSUPP if the bust operation is not defined for the specific
84 hwspinlock).
85
86 Should be called from a process context (might sleep).
87
88 ::
89
90 int hwspin_lock_timeout(struct hwspinlock *hwlock, unsigned int timeout);
91
92 Lock a previously-assigned hwspinlock with a timeout limit (specified in
93 msecs). If the hwspinlock is already taken, the function will busy loop
94 waiting for it to be released, but give up when the timeout elapses.
95 Upon a successful return from this function, preemption is disabled so
96 the caller must not sleep, and is advised to release the hwspinlock as
97 soon as possible, in order to minimize remote cores polling on the
98 hardware interconnect.
99
100 Returns 0 when successful and an appropriate error code otherwise (most
101 notably -ETIMEDOUT if the hwspinlock is still busy after timeout msecs).
102 The function will never sleep.
103
104 ::
105
106 int hwspin_lock_timeout_irq(struct hwspinlock *hwlock, unsigned int timeout);
107
108 Lock a previously-assigned hwspinlock with a timeout limit (specified in
109 msecs). If the hwspinlock is already taken, the function will busy loop
110 waiting for it to be released, but give up when the timeout elapses.
111 Upon a successful return from this function, preemption and the local
112 interrupts are disabled, so the caller must not sleep, and is advised to
113 release the hwspinlock as soon as possible.
114
115 Returns 0 when successful and an appropriate error code otherwise (most
116 notably -ETIMEDOUT if the hwspinlock is still busy after timeout msecs).
117 The function will never sleep.
118
119 ::
120
121 int hwspin_lock_timeout_irqsave(struct hwspinlock *hwlock, unsigned int to,
122 unsigned long *flags);
123
124 Lock a previously-assigned hwspinlock with a timeout limit (specified in
125 msecs). If the hwspinlock is already taken, the function will busy loop
126 waiting for it to be released, but give up when the timeout elapses.
127 Upon a successful return from this function, preemption is disabled,
128 local interrupts are disabled and their previous state is saved at the
129 given flags placeholder. The caller must not sleep, and is advised to
130 release the hwspinlock as soon as possible.
131
132 Returns 0 when successful and an appropriate error code otherwise (most
133 notably -ETIMEDOUT if the hwspinlock is still busy after timeout msecs).
134
135 The function will never sleep.
136
137 ::
138
139 int hwspin_lock_timeout_raw(struct hwspinlock *hwlock, unsigned int timeout);
140
141 Lock a previously-assigned hwspinlock with a timeout limit (specified in
142 msecs). If the hwspinlock is already taken, the function will busy loop
143 waiting for it to be released, but give up when the timeout elapses.
144
145 Caution: User must protect the routine of getting hardware lock with mutex
146 or spinlock to avoid dead-lock, that will let user can do some time-consuming
147 or sleepable operations under the hardware lock.
148
149 Returns 0 when successful and an appropriate error code otherwise (most
150 notably -ETIMEDOUT if the hwspinlock is still busy after timeout msecs).
151
152 The function will never sleep.
153
154 ::
155
156 int hwspin_lock_timeout_in_atomic(struct hwspinlock *hwlock, unsigned int to);
157
158 Lock a previously-assigned hwspinlock with a timeout limit (specified in
159 msecs). If the hwspinlock is already taken, the function will busy loop
160 waiting for it to be released, but give up when the timeout elapses.
161
162 This function shall be called only from an atomic context and the timeout
163 value shall not exceed a few msecs.
164
165 Returns 0 when successful and an appropriate error code otherwise (most
166 notably -ETIMEDOUT if the hwspinlock is still busy after timeout msecs).
167
168 The function will never sleep.
169
170 ::
171
172 int hwspin_trylock(struct hwspinlock *hwlock);
173
174
175 Attempt to lock a previously-assigned hwspinlock, but immediately fail if
176 it is already taken.
177
178 Upon a successful return from this function, preemption is disabled so
179 caller must not sleep, and is advised to release the hwspinlock as soon as
180 possible, in order to minimize remote cores polling on the hardware
181 interconnect.
182
183 Returns 0 on success and an appropriate error code otherwise (most
184 notably -EBUSY if the hwspinlock was already taken).
185 The function will never sleep.
186
187 ::
188
189 int hwspin_trylock_irq(struct hwspinlock *hwlock);
190
191
192 Attempt to lock a previously-assigned hwspinlock, but immediately fail if
193 it is already taken.
194
195 Upon a successful return from this function, preemption and the local
196 interrupts are disabled so caller must not sleep, and is advised to
197 release the hwspinlock as soon as possible.
198
199 Returns 0 on success and an appropriate error code otherwise (most
200 notably -EBUSY if the hwspinlock was already taken).
201
202 The function will never sleep.
203
204 ::
205
206 int hwspin_trylock_irqsave(struct hwspinlock *hwlock, unsigned long *flags);
207
208 Attempt to lock a previously-assigned hwspinlock, but immediately fail if
209 it is already taken.
210
211 Upon a successful return from this function, preemption is disabled,
212 the local interrupts are disabled and their previous state is saved
213 at the given flags placeholder. The caller must not sleep, and is advised
214 to release the hwspinlock as soon as possible.
215
216 Returns 0 on success and an appropriate error code otherwise (most
217 notably -EBUSY if the hwspinlock was already taken).
218 The function will never sleep.
219
220 ::
221
222 int hwspin_trylock_raw(struct hwspinlock *hwlock);
223
224 Attempt to lock a previously-assigned hwspinlock, but immediately fail if
225 it is already taken.
226
227 Caution: User must protect the routine of getting hardware lock with mutex
228 or spinlock to avoid dead-lock, that will let user can do some time-consuming
229 or sleepable operations under the hardware lock.
230
231 Returns 0 on success and an appropriate error code otherwise (most
232 notably -EBUSY if the hwspinlock was already taken).
233 The function will never sleep.
234
235 ::
236
237 int hwspin_trylock_in_atomic(struct hwspinlock *hwlock);
238
239 Attempt to lock a previously-assigned hwspinlock, but immediately fail if
240 it is already taken.
241
242 This function shall be called only from an atomic context.
243
244 Returns 0 on success and an appropriate error code otherwise (most
245 notably -EBUSY if the hwspinlock was already taken).
246 The function will never sleep.
247
248 ::
249
250 void hwspin_unlock(struct hwspinlock *hwlock);
251
252 Unlock a previously-locked hwspinlock. Always succeed, and can be called
253 from any context (the function never sleeps).
254
255 .. note::
256
257 code should **never** unlock an hwspinlock which is already unlocked
258 (there is no protection against this).
259
260 ::
261
262 void hwspin_unlock_irq(struct hwspinlock *hwlock);
263
264 Unlock a previously-locked hwspinlock and enable local interrupts.
265 The caller should **never** unlock an hwspinlock which is already unlocked.
266
267 Doing so is considered a bug (there is no protection against this).
268 Upon a successful return from this function, preemption and local
269 interrupts are enabled. This function will never sleep.
270
271 ::
272
273 void
274 hwspin_unlock_irqrestore(struct hwspinlock *hwlock, unsigned long *flags);
275
276 Unlock a previously-locked hwspinlock.
277
278 The caller should **never** unlock an hwspinlock which is already unlocked.
279 Doing so is considered a bug (there is no protection against this).
280 Upon a successful return from this function, preemption is reenabled,
281 and the state of the local interrupts is restored to the state saved at
282 the given flags. This function will never sleep.
283
284 ::
285
286 void hwspin_unlock_raw(struct hwspinlock *hwlock);
287
288 Unlock a previously-locked hwspinlock.
289
290 The caller should **never** unlock an hwspinlock which is already unlocked.
291 Doing so is considered a bug (there is no protection against this).
292 This function will never sleep.
293
294 ::
295
296 void hwspin_unlock_in_atomic(struct hwspinlock *hwlock);
297
298 Unlock a previously-locked hwspinlock.
299
300 The caller should **never** unlock an hwspinlock which is already unlocked.
301 Doing so is considered a bug (there is no protection against this).
302 This function will never sleep.
303
304 Typical usage
305 =============
306
307 ::
308
309 #include <linux/hwspinlock.h>
310 #include <linux/err.h>
311
312 int hwspinlock_example(void)
313 {
314 struct hwspinlock *hwlock;
315 int ret;
316
317 /*
318 * assign a specific hwspinlock id - this should be called early
319 * by board init code.
320 */
321 hwlock = hwspin_lock_request_specific(PREDEFINED_LOCK_ID);
322 if (!hwlock)
323 ...
324
325 /* try to take it, but don't spin on it */
326 ret = hwspin_trylock(hwlock);
327 if (!ret) {
328 pr_info("lock is already taken\n");
329 return -EBUSY;
330 }
331
332 /*
333 * we took the lock, do our thing now, but do NOT sleep
334 */
335
336 /* release the lock */
337 hwspin_unlock(hwlock);
338
339 /* free the lock */
340 ret = hwspin_lock_free(hwlock);
341 if (ret)
342 ...
343
344 return ret;
345 }
346
347
348 API for implementors
349 ====================
350
351 ::
352
353 int hwspin_lock_register(struct hwspinlock_device *bank, struct device *dev,
354 const struct hwspinlock_ops *ops, int base_id, int num_locks);
355
356 To be called from the underlying platform-specific implementation, in
357 order to register a new hwspinlock device (which is usually a bank of
358 numerous locks). Should be called from a process context (this function
359 might sleep).
360
361 Returns 0 on success, or appropriate error code on failure.
362
363 ::
364
365 int hwspin_lock_unregister(struct hwspinlock_device *bank);
366
367 To be called from the underlying vendor-specific implementation, in order
368 to unregister an hwspinlock device (which is usually a bank of numerous
369 locks).
370
371 Should be called from a process context (this function might sleep).
372
373 Returns the address of hwspinlock on success, or NULL on error (e.g.
374 if the hwspinlock is still in use).
375
376 Important structs
377 =================
378
379 struct hwspinlock_device is a device which usually contains a bank
380 of hardware locks. It is registered by the underlying hwspinlock
381 implementation using the hwspin_lock_register() API.
382
383 ::
384
385 /**
386 * struct hwspinlock_device - a device which usually spans numerous hwspinlocks
387 * @dev: underlying device, will be used to invoke runtime PM api
388 * @ops: platform-specific hwspinlock handlers
389 * @base_id: id index of the first lock in this device
390 * @num_locks: number of locks in this device
391 * @lock: dynamically allocated array of 'struct hwspinlock'
392 */
393 struct hwspinlock_device {
394 struct device *dev;
395 const struct hwspinlock_ops *ops;
396 int base_id;
397 int num_locks;
398 struct hwspinlock lock[0];
399 };
400
401 struct hwspinlock_device contains an array of hwspinlock structs, each
402 of which represents a single hardware lock::
403
404 /**
405 * struct hwspinlock - this struct represents a single hwspinlock instance
406 * @bank: the hwspinlock_device structure which owns this lock
407 * @lock: initialized and used by hwspinlock core
408 * @priv: private data, owned by the underlying platform-specific hwspinlock drv
409 */
410 struct hwspinlock {
411 struct hwspinlock_device *bank;
412 spinlock_t lock;
413 void *priv;
414 };
415
416 When registering a bank of locks, the hwspinlock driver only needs to
417 set the priv members of the locks. The rest of the members are set and
418 initialized by the hwspinlock core itself.
419
420 Implementation callbacks
421 ========================
422
423 There are three possible callbacks defined in 'struct hwspinlock_ops'::
424
425 struct hwspinlock_ops {
426 int (*trylock)(struct hwspinlock *lock);
427 void (*unlock)(struct hwspinlock *lock);
428 void (*relax)(struct hwspinlock *lock);
429 };
430
431 The first two callbacks are mandatory:
432
433 The ->trylock() callback should make a single attempt to take the lock, and
434 return 0 on failure and 1 on success. This callback may **not** sleep.
435
436 The ->unlock() callback releases the lock. It always succeed, and it, too,
437 may **not** sleep.
438
439 The ->relax() callback is optional. It is called by hwspinlock core while
440 spinning on a lock, and can be used by the underlying implementation to force
441 a delay between two successive invocations of ->trylock(). It may **not** sleep.
442

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

서로 다른 processor와 OS 사이의 hardware lock

1-36

Hardware 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-86
struct 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-168

hwspin_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 disabledsleep 금지, 가능한 빨리 unlock
hwspin_lock_timeout_irq(hwlock, timeout)preemption disabled, local IRQ disabledsleep 금지, 가능한 빨리 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-246

hwspin_trylock*() family는 이미 할당된 hwspinlock을 한 번 획득해 본다. Hardware lock이 이미 사용 중이면 기다리지 않고 즉시 실패한다. 성공 시 0을 반환하며, 이미 점유된 경우의 대표적인 error code는 -EBUSY다. 모든 variant는 sleep하지 않는다.

API성공 후 local 상태주의점
hwspin_trylock(hwlock)preemption disabledsleep 금지, remote polling을 줄이도록 즉시 release
hwspin_trylock_irq(hwlock)preemption disabled, local IRQ disabledhwspin_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-302

hwspin_unlock*() family는 획득한 hardware lock을 release한다. 이미 unlocked 상태인 lock을 다시 unlock해서는 절대 안 된다. Framework에는 이를 막는 보호 장치가 없으며 그런 호출은 bug로 간주된다. 모든 unlock variant는 sleep하지 않는다.

APIrelease 뒤의 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-374
int 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-418

struct 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-441
struct 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다. 두 계층을 혼동하면 획득 성공 여부를 반대로 처리하게 된다.