요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
============================
The Common Mailbox Framework
============================
:Author: Jassi Brar <jaswinder.singh@linaro.org>
This document aims to help developers write client and controller
drivers for the API. But before we start, let us note that the
client (especially) and controller drivers are likely going to be
very platform specific because the remote firmware is likely to be
proprietary and implement non-standard protocol. So even if two
platforms employ, say, PL320 controller, the client drivers can't
be shared across them. Even the PL320 driver might need to accommodate
some platform specific quirks. So the API is meant mainly to avoid
similar copies of code written for each platform. Having said that,
nothing prevents the remote f/w to also be Linux based and use the
same api there. However none of that helps us locally because we only
ever deal at client's protocol level.
Some of the choices made during implementation are the result of this
peculiarity of this "common" framework.
Controller Driver (See include/linux/mailbox_controller.h)
==========================================================
Allocate mbox_controller and the array of mbox_chan.
Populate mbox_chan_ops, except peek_data() all are mandatory.
The controller driver might know a message has been consumed
by the remote by getting an IRQ or polling some hardware flag
or it can never know (the client knows by way of the protocol).
The method in order of preference is IRQ -> Poll -> None, which
the controller driver should set via 'txdone_irq' or 'txdone_poll'
or neither.
Client Driver (See include/linux/mailbox_client.h)
==================================================
The client might want to operate in blocking mode (synchronously
send a message through before returning) or non-blocking/async mode (submit
a message and a callback function to the API and return immediately).
::
struct demo_client {
struct mbox_client cl;
struct mbox_chan *mbox;
struct completion c;
bool async;
/* ... */
};
/*
* This is the handler for data received from remote. The behaviour is purely
* dependent upon the protocol. This is just an example.
*/
static void message_from_remote(struct mbox_client *cl, void *mssg)
{
struct demo_client *dc = container_of(cl, struct demo_client, cl);
if (dc->async) {
if (is_an_ack(mssg)) {
/* An ACK to our last sample sent */
return; /* Or do something else here */
} else { /* A new message from remote */
queue_req(mssg);
}
} else {
/* Remote f/w sends only ACK packets on this channel */
return;
}
}
static void sample_sent(struct mbox_client *cl, void *mssg, int r)
{
struct demo_client *dc = container_of(cl, struct demo_client, cl);
complete(&dc->c);
}
static void client_demo(struct platform_device *pdev)
{
struct demo_client *dc_sync, *dc_async;
/* The controller already knows async_pkt and sync_pkt */
struct async_pkt ap;
struct sync_pkt sp;
dc_sync = kzalloc(sizeof(*dc_sync), GFP_KERNEL);
dc_async = kzalloc(sizeof(*dc_async), GFP_KERNEL);
/* Populate non-blocking mode client */
dc_async->cl.dev = &pdev->dev;
dc_async->cl.rx_callback = message_from_remote;
dc_async->cl.tx_done = sample_sent;
dc_async->cl.tx_block = false;
dc_async->cl.tx_tout = 0; /* doesn't matter here */
dc_async->cl.knows_txdone = false; /* depending upon protocol */
dc_async->async = true;
init_completion(&dc_async->c);
/* Populate blocking mode client */
dc_sync->cl.dev = &pdev->dev;
dc_sync->cl.rx_callback = message_from_remote;
dc_sync->cl.tx_done = NULL; /* operate in blocking mode */
dc_sync->cl.tx_block = true;
dc_sync->cl.tx_tout = 500; /* by half a second */
dc_sync->cl.knows_txdone = false; /* depending upon protocol */
dc_sync->async = false;
/* ASync mailbox is listed second in 'mboxes' property */
dc_async->mbox = mbox_request_channel(&dc_async->cl, 1);
/* Populate data packet */
/* ap.xxx = 123; etc */
/* Send async message to remote */
mbox_send_message(dc_async->mbox, &ap);
/* Sync mailbox is listed first in 'mboxes' property */
dc_sync->mbox = mbox_request_channel(&dc_sync->cl, 0);
/* Populate data packet */
/* sp.abc = 123; etc */
/* Send message to remote in blocking mode */
mbox_send_message(dc_sync->mbox, &sp);
/* At this point 'sp' has been sent */
/* Now wait for async chan to be done */
wait_for_completion(&dc_async->c);
}
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Common Mailbox Framework 개요
1-24문서 제목은 `The Common Mailbox Framework`이며 저자는 Jassi Brar입니다. 이 문서는 API용 client driver와 controller driver 작성자를 돕습니다.
Remote firmware가 proprietary이고 non-standard protocol을 구현하는 경우가 많으므로 client driver와 controller driver는 대체로 platform-specific입니다. 두 platform이 같은 PL320 controller를 사용해도 client driver를 공유할 수 없고 PL320 driver 자체에도 platform quirk 처리가 필요할 수 있습니다.
이 API의 주목적은 platform마다 비슷한 code copy를 반복하는 일을 줄이는 것입니다. Remote firmware가 Linux와 같은 API를 사용할 수도 있지만 local side는 client protocol level만 다루므로 그 사실만으로 local implementation을 공유할 수는 없습니다. Framework 구현 선택 일부는 이런 특성에서 비롯됩니다.
Common framework가 transport API를 통합하지만 protocol은 client에 남깁니다.
Controller driver
25-38Controller driver는 `include/linux/mailbox_controller.h`를 따릅니다. `mbox_controller`와 `mbox_chan` array를 allocate하고 `mbox_chan_ops`를 채웁니다. `peek_data()`만 optional이고 나머지 operation은 필수입니다.
Remote가 message를 consume했다는 사실을 controller가 IRQ로 알거나 hardware flag polling으로 알 수 있으며, protocol을 아는 client만 완료를 알고 controller는 전혀 모를 수도 있습니다. 선호 순서는 IRQ, polling, none입니다. Driver는 각각 `txdone_irq`, `txdone_poll`, 둘 다 설정하지 않음으로 표시합니다.
Allocation과 mandatory operation을 정리했습니다.
Controller가 선택해야 할 가장 신뢰도 높은 방식입니다.
Client driver mode
39-46Client driver는 `include/linux/mailbox_client.h`를 사용합니다. Blocking mode에서는 message 전송을 완료한 뒤 caller로 돌아옵니다. Non-blocking 또는 asynchronous mode에서는 message와 callback을 API에 제출하고 즉시 반환합니다.
Return 시점과 completion notification 차이입니다.
Client structure와 callback 예제
47-82예제 `struct demo_client`는 `struct mbox_client`, channel pointer, completion, async flag를 보관합니다. 실제 packet format과 ACK 구분은 framework가 아니라 remote protocol에 달려 있습니다.
`message_from_remote()`는 async client에서 ACK면 마지막 sample의 acknowledgment로 처리하고, 새 remote message면 `queue_req()`로 넘깁니다. Synchronous channel 예시는 remote firmware가 ACK packet만 보낸다고 가정합니다.
`sample_sent()`는 async transmission이 끝나면 client의 completion을 signal합니다.
struct demo_client {
struct mbox_client cl;
struct mbox_chan *mbox;
struct completion c;
bool async;
/* ... */
};
/*
* This is the handler for data received from remote. The behaviour is purely
* dependent upon the protocol. This is just an example.
*/
static void message_from_remote(struct mbox_client *cl, void *mssg)
{
struct demo_client *dc = container_of(cl, struct demo_client, cl);
if (dc->async) {
if (is_an_ack(mssg)) {
/* An ACK to our last sample sent */
return; /* Or do something else here */
} else { /* A new message from remote */
queue_req(mssg);
}
} else {
/* Remote f/w sends only ACK packets on this channel */
return;
}
}
static void sample_sent(struct mbox_client *cl, void *mssg, int r)
{
struct demo_client *dc = container_of(cl, struct demo_client, cl);
complete(&dc->c);
}
Framework callback 안에서 client protocol이 ACK와 새 request를 구분합니다.
예제에서 필요한 client-side state입니다.
Sync·async channel 사용 예제
83-129`client_demo()`는 synchronous client와 asynchronous client를 각각 allocate합니다. Controller는 `async_pkt`과 `sync_pkt`의 format을 이미 알고 있다고 가정합니다.
Async client는 `rx_callback=message_from_remote`, `tx_done=sample_sent`, `tx_block=false`, `tx_tout=0`, protocol에 따라 `knows_txdone=false`, `async=true`로 설정하고 completion을 initialize합니다.
Blocking client는 같은 RX callback을 쓰지만 `tx_done=NULL`, `tx_block=true`, timeout 500ms, `knows_txdone=false`, `async=false`로 설정합니다.
Device tree 등의 `mboxes` property에서 async mailbox가 두 번째이므로 channel index 1을, sync mailbox가 첫 번째이므로 index 0을 `mbox_request_channel()`로 요청합니다. 각 packet을 채운 뒤 `mbox_send_message()`로 전송합니다.
Blocking send가 반환한 시점에는 `sp`가 전송된 상태입니다. Async channel은 별도로 `wait_for_completion()`을 호출해 `sample_sent()`의 signal을 기다립니다.
static void client_demo(struct platform_device *pdev)
{
struct demo_client *dc_sync, *dc_async;
/* The controller already knows async_pkt and sync_pkt */
struct async_pkt ap;
struct sync_pkt sp;
dc_sync = kzalloc(sizeof(*dc_sync), GFP_KERNEL);
dc_async = kzalloc(sizeof(*dc_async), GFP_KERNEL);
/* Populate non-blocking mode client */
dc_async->cl.dev = &pdev->dev;
dc_async->cl.rx_callback = message_from_remote;
dc_async->cl.tx_done = sample_sent;
dc_async->cl.tx_block = false;
dc_async->cl.tx_tout = 0; /* doesn't matter here */
dc_async->cl.knows_txdone = false; /* depending upon protocol */
dc_async->async = true;
init_completion(&dc_async->c);
/* Populate blocking mode client */
dc_sync->cl.dev = &pdev->dev;
dc_sync->cl.rx_callback = message_from_remote;
dc_sync->cl.tx_done = NULL; /* operate in blocking mode */
dc_sync->cl.tx_block = true;
dc_sync->cl.tx_tout = 500; /* by half a second */
dc_sync->cl.knows_txdone = false; /* depending upon protocol */
dc_sync->async = false;
/* ASync mailbox is listed second in 'mboxes' property */
dc_async->mbox = mbox_request_channel(&dc_async->cl, 1);
/* Populate data packet */
/* ap.xxx = 123; etc */
/* Send async message to remote */
mbox_send_message(dc_async->mbox, &ap);
/* Sync mailbox is listed first in 'mboxes' property */
dc_sync->mbox = mbox_request_channel(&dc_sync->cl, 0);
/* Populate data packet */
/* sp.abc = 123; etc */
/* Send message to remote in blocking mode */
mbox_send_message(dc_sync->mbox, &sp);
/* At this point 'sp' has been sent */
/* Now wait for async chan to be done */
wait_for_completion(&dc_async->c);
}
동일 API에서 blocking과 async 설정을 비교합니다.
Submit과 completion wait가 분리됩니다.
Send call의 반환 자체가 전송 완료 boundary입니다.
요약과 해설
mailbox.rst:1-129Common Mailbox Framework는 controller transport code의 중복을 줄이지만 remote packet protocol은 client driver 책임으로 남깁니다. Controller는 IRQ·polling·client-known completion 중 하나를 명시하고, client는 `tx_block`, callback, timeout과 channel index로 sync·async semantics를 구성합니다.