요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
=======================================
Oracle Data Analytics Accelerator (DAX)
=======================================
DAX is a coprocessor which resides on the SPARC M7 (DAX1) and M8
(DAX2) processor chips, and has direct access to the CPU's L3 caches
as well as physical memory. It can perform several operations on data
streams with various input and output formats. A driver provides a
transport mechanism and has limited knowledge of the various opcodes
and data formats. A user space library provides high level services
and translates these into low level commands which are then passed
into the driver and subsequently the Hypervisor and the coprocessor.
The library is the recommended way for applications to use the
coprocessor, and the driver interface is not intended for general use.
This document describes the general flow of the driver, its
structures, and its programmatic interface. It also provides example
code sufficient to write user or kernel applications that use DAX
functionality.
The user library is open source and available at:
https://oss.oracle.com/git/gitweb.cgi?p=libdax.git
The Hypervisor interface to the coprocessor is described in detail in
the accompanying document, dax-hv-api.txt, which is a plain text
excerpt of the (Oracle internal) "UltraSPARC Virtual Machine
Specification" version 3.0.20+15, dated 2017-09-25.
High Level Overview
===================
A coprocessor request is described by a Command Control Block
(CCB). The CCB contains an opcode and various parameters. The opcode
specifies what operation is to be done, and the parameters specify
options, flags, sizes, and addresses. The CCB (or an array of CCBs)
is passed to the Hypervisor, which handles queueing and scheduling of
requests to the available coprocessor execution units. A status code
returned indicates if the request was submitted successfully or if
there was an error. One of the addresses given in each CCB is a
pointer to a "completion area", which is a 128 byte memory block that
is written by the coprocessor to provide execution status. No
interrupt is generated upon completion; the completion area must be
polled by software to find out when a transaction has finished, but
the M7 and later processors provide a mechanism to pause the virtual
processor until the completion status has been updated by the
coprocessor. This is done using the monitored load and mwait
instructions, which are described in more detail later. The DAX
coprocessor was designed so that after a request is submitted, the
kernel is no longer involved in the processing of it. The polling is
done at the user level, which results in almost zero latency between
completion of a request and resumption of execution of the requesting
thread.
Addressing Memory
=================
The kernel does not have access to physical memory in the Sun4v
architecture, as there is an additional level of memory virtualization
present. This intermediate level is called "real" memory, and the
kernel treats this as if it were physical. The Hypervisor handles the
translations between real memory and physical so that each logical
domain (LDOM) can have a partition of physical memory that is isolated
from that of other LDOMs. When the kernel sets up a virtual mapping,
it specifies a virtual address and the real address to which it should
be mapped.
The DAX coprocessor can only operate on physical memory, so before a
request can be fed to the coprocessor, all the addresses in a CCB must
be converted into physical addresses. The kernel cannot do this since
it has no visibility into physical addresses. So a CCB may contain
either the virtual or real addresses of the buffers or a combination
of them. An "address type" field is available for each address that
may be given in the CCB. In all cases, the Hypervisor will translate
all the addresses to physical before dispatching to hardware. Address
translations are performed using the context of the process initiating
the request.
The Driver API
==============
An application makes requests to the driver via the write() system
call, and gets results (if any) via read(). The completion areas are
made accessible via mmap(), and are read-only for the application.
The request may either be an immediate command or an array of CCBs to
be submitted to the hardware.
Each open instance of the device is exclusive to the thread that
opened it, and must be used by that thread for all subsequent
operations. The driver open function creates a new context for the
thread and initializes it for use. This context contains pointers and
values used internally by the driver to keep track of submitted
requests. The completion area buffer is also allocated, and this is
large enough to contain the completion areas for many concurrent
requests. When the device is closed, any outstanding transactions are
flushed and the context is cleaned up.
On a DAX1 system (M7), the device will be called "oradax1", while on a
DAX2 system (M8) it will be "oradax2". If an application requires one
or the other, it should simply attempt to open the appropriate
device. Only one of the devices will exist on any given system, so the
name can be used to determine what the platform supports.
The immediate commands are CCB_DEQUEUE, CCB_KILL, and CCB_INFO. For
all of these, success is indicated by a return value from write()
equal to the number of bytes given in the call. Otherwise -1 is
returned and errno is set.
CCB_DEQUEUE
-----------
Tells the driver to clean up resources associated with past
requests. Since no interrupt is generated upon the completion of a
request, the driver must be told when it may reclaim resources. No
further status information is returned, so the user should not
subsequently call read().
CCB_KILL
--------
Kills a CCB during execution. The CCB is guaranteed to not continue
executing once this call returns successfully. On success, read() must
be called to retrieve the result of the action.
CCB_INFO
--------
Retrieves information about a currently executing CCB. Note that some
Hypervisors might return 'notfound' when the CCB is in 'inprogress'
state. To ensure a CCB in the 'notfound' state will never be executed,
CCB_KILL must be invoked on that CCB. Upon success, read() must be
called to retrieve the details of the action.
Submission of an array of CCBs for execution
---------------------------------------------
A write() whose length is a multiple of the CCB size is treated as a
submit operation. The file offset is treated as the index of the
completion area to use, and may be set via lseek() or using the
pwrite() system call. If -1 is returned then errno is set to indicate
the error. Otherwise, the return value is the length of the array that
was actually accepted by the coprocessor. If the accepted length is
equal to the requested length, then the submission was completely
successful and there is no further status needed; hence, the user
should not subsequently call read(). Partial acceptance of the CCB
array is indicated by a return value less than the requested length,
and read() must be called to retrieve further status information. The
status will reflect the error caused by the first CCB that was not
accepted, and status_data will provide additional data in some cases.
MMAP
----
The mmap() function provides access to the completion area allocated
in the driver. Note that the completion area is not writeable by the
user process, and the mmap call must not specify PROT_WRITE.
Completion of a Request
=======================
The first byte in each completion area is the command status which is
updated by the coprocessor hardware. Software may take advantage of
new M7/M8 processor capabilities to efficiently poll this status byte.
First, a "monitored load" is achieved via a Load from Alternate Space
(ldxa, lduba, etc.) with ASI 0x84 (ASI_MONITOR_PRIMARY). Second, a
"monitored wait" is achieved via the mwait instruction (a write to
%asr28). This instruction is like pause in that it suspends execution
of the virtual processor for the given number of nanoseconds, but in
addition will terminate early when one of several events occur. If the
block of data containing the monitored location is modified, then the
mwait terminates. This causes software to resume execution immediately
(without a context switch or kernel to user transition) after a
transaction completes. Thus the latency between transaction completion
and resumption of execution may be just a few nanoseconds.
Application Life Cycle of a DAX Submission
==========================================
- open dax device
- call mmap() to get the completion area address
- allocate a CCB and fill in the opcode, flags, parameters, addresses, etc.
- submit CCB via write() or pwrite()
- go into a loop executing monitored load + monitored wait and
terminate when the command status indicates the request is complete
(CCB_KILL or CCB_INFO may be used any time as necessary)
- perform a CCB_DEQUEUE
- call munmap() for completion area
- close the dax device
Memory Constraints
==================
The DAX hardware operates only on physical addresses. Therefore, it is
not aware of virtual memory mappings and the discontiguities that may
exist in the physical memory that a virtual buffer maps to. There is
no I/O TLB or any scatter/gather mechanism. All buffers, whether input
or output, must reside in a physically contiguous region of memory.
The Hypervisor translates all addresses within a CCB to physical
before handing off the CCB to DAX. The Hypervisor determines the
virtual page size for each virtual address given, and uses this to
program a size limit for each address. This prevents the coprocessor
from reading or writing beyond the bound of the virtual page, even
though it is accessing physical memory directly. A simpler way of
saying this is that a DAX operation will never "cross" a virtual page
boundary. If an 8k virtual page is used, then the data is strictly
limited to 8k. If a user's buffer is larger than 8k, then a larger
page size must be used, or the transaction size will be truncated to
8k.
Huge pages. A user may allocate huge pages using standard interfaces.
Memory buffers residing on huge pages may be used to achieve much
larger DAX transaction sizes, but the rules must still be followed,
and no transaction will cross a page boundary, even a huge page. A
major caveat is that Linux on Sparc presents 8Mb as one of the huge
page sizes. Sparc does not actually provide a 8Mb hardware page size,
and this size is synthesized by pasting together two 4Mb pages. The
reasons for this are historical, and it creates an issue because only
half of this 8Mb page can actually be used for any given buffer in a
DAX request, and it must be either the first half or the second half;
it cannot be a 4Mb chunk in the middle, since that crosses a
(hardware) page boundary. Note that this entire issue may be hidden by
higher level libraries.
CCB Structure
-------------
A CCB is an array of 8 64-bit words. Several of these words provide
command opcodes, parameters, flags, etc., and the rest are addresses
for the completion area, output buffer, and various inputs::
struct ccb {
u64 control;
u64 completion;
u64 input0;
u64 access;
u64 input1;
u64 op_data;
u64 output;
u64 table;
};
See libdax/common/sys/dax1/dax1_ccb.h for a detailed description of
each of these fields, and see dax-hv-api.txt for a complete description
of the Hypervisor API available to the guest OS (ie, Linux kernel).
The first word (control) is examined by the driver for the following:
- CCB version, which must be consistent with hardware version
- Opcode, which must be one of the documented allowable commands
- Address types, which must be set to "virtual" for all the addresses
given by the user, thereby ensuring that the application can
only access memory that it owns
Example Code
============
The DAX is accessible to both user and kernel code. The kernel code
can make hypercalls directly while the user code must use wrappers
provided by the driver. The setup of the CCB is nearly identical for
both; the only difference is in preparation of the completion area. An
example of user code is given now, with kernel code afterwards.
In order to program using the driver API, the file
arch/sparc/include/uapi/asm/oradax.h must be included.
First, the proper device must be opened. For M7 it will be
/dev/oradax1 and for M8 it will be /dev/oradax2. The simplest
procedure is to attempt to open both, as only one will succeed::
fd = open("/dev/oradax1", O_RDWR);
if (fd < 0)
fd = open("/dev/oradax2", O_RDWR);
if (fd < 0)
/* No DAX found */
Next, the completion area must be mapped::
completion_area = mmap(NULL, DAX_MMAP_LEN, PROT_READ, MAP_SHARED, fd, 0);
All input and output buffers must be fully contained in one hardware
page, since as explained above, the DAX is strictly constrained by
virtual page boundaries. In addition, the output buffer must be
64-byte aligned and its size must be a multiple of 64 bytes because
the coprocessor writes in units of cache lines.
This example demonstrates the DAX Scan command, which takes as input a
vector and a match value, and produces a bitmap as the output. For
each input element that matches the value, the corresponding bit is
set in the output.
In this example, the input vector consists of a series of single bits,
and the match value is 0. So each 0 bit in the input will produce a 1
in the output, and vice versa, which produces an output bitmap which
is the input bitmap inverted.
For details of all the parameters and bits used in this CCB, please
refer to section 36.2.1.3 of the DAX Hypervisor API document, which
describes the Scan command in detail::
ccb->control = /* Table 36.1, CCB Header Format */
(2L << 48) /* command = Scan Value */
| (3L << 40) /* output address type = primary virtual */
| (3L << 34) /* primary input address type = primary virtual */
/* Section 36.2.1, Query CCB Command Formats */
| (1 << 28) /* 36.2.1.1.1 primary input format = fixed width bit packed */
| (0 << 23) /* 36.2.1.1.2 primary input element size = 0 (1 bit) */
| (8 << 10) /* 36.2.1.1.6 output format = bit vector */
| (0 << 5) /* 36.2.1.3 First scan criteria size = 0 (1 byte) */
| (31 << 0); /* 36.2.1.3 Disable second scan criteria */
ccb->completion = 0; /* Completion area address, to be filled in by driver */
ccb->input0 = (unsigned long) input; /* primary input address */
ccb->access = /* Section 36.2.1.2, Data Access Control */
(2 << 24) /* Primary input length format = bits */
| (nbits - 1); /* number of bits in primary input stream, minus 1 */
ccb->input1 = 0; /* secondary input address, unused */
ccb->op_data = 0; /* scan criteria (value to be matched) */
ccb->output = (unsigned long) output; /* output address */
ccb->table = 0; /* table address, unused */
The CCB submission is a write() or pwrite() system call to the
driver. If the call fails, then a read() must be used to retrieve the
status::
if (pwrite(fd, ccb, 64, 0) != 64) {
struct ccb_exec_result status;
read(fd, &status, sizeof(status));
/* bail out */
}
After a successful submission of the CCB, the completion area may be
polled to determine when the DAX is finished. Detailed information on
the contents of the completion area can be found in section 36.2.2 of
the DAX HV API document::
while (1) {
/* Monitored Load */
__asm__ __volatile__("lduba [%1] 0x84, %0\n"
: "=r" (status)
: "r" (completion_area));
if (status) /* 0 indicates command in progress */
break;
/* MWAIT */
__asm__ __volatile__("wr %%g0, 1000, %%asr28\n" ::); /* 1000 ns */
}
A completion area status of 1 indicates successful completion of the
CCB and validity of the output bitmap, which may be used immediately.
All other non-zero values indicate error conditions which are
described in section 36.2.2::
if (completion_area[0] != 1) { /* section 36.2.2, 1 = command ran and succeeded */
/* completion_area[0] contains the completion status */
/* completion_area[1] contains an error code, see 36.2.2 */
}
After the completion area has been processed, the driver must be
notified that it can release any resources associated with the
request. This is done via the dequeue operation::
struct dax_command cmd;
cmd.command = CCB_DEQUEUE;
if (write(fd, &cmd, sizeof(cmd)) != sizeof(cmd)) {
/* bail out */
}
Finally, normal program cleanup should be done, i.e., unmapping
completion area, closing the dax device, freeing memory etc.
Kernel example
--------------
The only difference in using the DAX in kernel code is the treatment
of the completion area. Unlike user applications which mmap the
completion area allocated by the driver, kernel code must allocate its
own memory to use for the completion area, and this address and its
type must be given in the CCB::
ccb->control |= /* Table 36.1, CCB Header Format */
(3L << 32); /* completion area address type = primary virtual */
ccb->completion = (unsigned long) completion_area; /* Completion area address */
The dax submit hypercall is made directly. The flags used in the
ccb_submit call are documented in the DAX HV API in section 36.3.1/
::
#include <asm/hypervisor.h>
hv_rv = sun4v_ccb_submit((unsigned long)ccb, 64,
HV_CCB_QUERY_CMD |
HV_CCB_ARG0_PRIVILEGED | HV_CCB_ARG0_TYPE_PRIMARY |
HV_CCB_VA_PRIVILEGED,
0, &bytes_accepted, &status_data);
if (hv_rv != HV_EOK) {
/* hv_rv is an error code, status_data contains */
/* potential additional status, see 36.3.1.1 */
}
After the submission, the completion area polling code is identical to
that in user land::
while (1) {
/* Monitored Load */
__asm__ __volatile__("lduba [%1] 0x84, %0\n"
: "=r" (status)
: "r" (completion_area));
if (status) /* 0 indicates command in progress */
break;
/* MWAIT */
__asm__ __volatile__("wr %%g0, 1000, %%asr28\n" ::); /* 1000 ns */
}
if (completion_area[0] != 1) { /* section 36.2.2, 1 = command ran and succeeded */
/* completion_area[0] contains the completion status */
/* completion_area[1] contains an error code, see 36.2.2 */
}
The output bitmap is ready for consumption immediately after the
completion status indicates success.
Excer[t from UltraSPARC Virtual Machine Specification
=====================================================
.. include:: dax-hv-api.txt
:literal:
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Oracle Data Analytics Accelerator
1-27DAX는 SPARC M7의 DAX1과 M8의 DAX2 processor chip에 탑재되는 coprocessor로, CPU L3 cache와 physical memory에 직접 접근합니다. 여러 input/output format의 data stream에 다양한 연산을 수행할 수 있습니다.
driver는 transport mechanism을 제공하지만 opcode와 data format에 대해서는 제한적으로만 이해합니다. user-space library가 high-level service를 low-level command로 변환해 driver, Hypervisor, coprocessor 순으로 전달합니다. application에는 이 library 사용이 권장되며 driver interface는 일반 용도로 설계되지 않았습니다.
이 문서는 driver의 전체 처리 흐름, 자료 구조, programmatic interface를 설명하고 DAX 기능을 사용하는 user application과 kernel application을 작성할 수 있을 정도의 예제 코드를 제공합니다.
open source user library는 `https://oss.oracle.com/git/gitweb.cgi?p=libdax.git`에서 구할 수 있습니다. coprocessor의 Hypervisor interface는 함께 제공되는 `dax-hv-api.txt`에 자세히 설명되어 있으며, 이 파일은 2017-09-25자 Oracle 내부 문서 UltraSPARC Virtual Machine Specification version `3.0.20+15`의 plain-text 발췌본입니다.
CCB 제출과 비동기 완료 흐름
28-54coprocessor request는 Command Control Block(CCB)으로 표현합니다. CCB의 opcode는 수행할 연산을 지정하고 parameter는 option, flag, size, address를 지정합니다. 단일 CCB 또는 CCB array를 Hypervisor에 넘기면 Hypervisor가 사용 가능한 coprocessor execution unit을 대상으로 queueing과 scheduling을 처리합니다. 반환 status code는 제출 성공 여부나 오류를 나타냅니다.
각 CCB의 address 중 하나는 128-byte memory block인 Completion Area를 가리킵니다. coprocessor가 이 영역에 execution status를 기록합니다. 완료 interrupt는 발생하지 않으므로 software가 transaction 종료 여부를 확인하도록 Completion Area를 polling해야 합니다.
M7 이후 processor는 monitored load와 `mwait` instruction으로 coprocessor가 completion status를 갱신할 때까지 virtual processor를 멈출 수 있습니다. request를 제출한 뒤 kernel은 처리에 더 관여하지 않고 user level에서 polling하므로 완료 시점부터 요청 thread가 실행을 재개할 때까지의 latency를 거의 0에 가깝게 줄일 수 있습니다.
Sun4v의 virtual·real·physical address
55-78Sun4v architecture에는 memory virtualization level이 하나 더 있으므로 kernel은 physical memory에 직접 접근할 수 없습니다. 이 중간 계층을 real memory라 하며 kernel은 이를 physical memory처럼 취급합니다. Hypervisor는 real-to-physical translation을 맡아 각 logical domain(LDOM)의 physical memory partition을 다른 LDOM과 격리합니다. kernel이 virtual mapping을 설정할 때는 virtual address와 그 대상 real address를 지정합니다.
DAX coprocessor는 physical memory에서만 동작하므로 hardware에 request를 보내기 전에 CCB의 모든 address를 physical address로 변환해야 합니다. kernel은 physical address를 볼 수 없어 이 변환을 직접 수행할 수 없습니다.
따라서 CCB에는 buffer의 virtual address, real address 또는 두 종류의 조합이 들어갈 수 있으며 각 address에는 address type field가 있습니다. Hypervisor는 어떤 경우든 hardware dispatch 전에 모든 address를 physical address로 변환하고, request를 시작한 process의 context를 address translation에 사용합니다.
driver open context와 device 선택
79-110application은 `write()` system call로 driver에 request를 보내고 결과가 있으면 `read()`로 받습니다. Completion Area는 `mmap()`으로 노출되며 application에서는 read-only입니다. request는 immediate command이거나 hardware에 제출할 CCB array일 수 있습니다.
device를 연 각 instance는 그 device를 연 thread에 독점적으로 귀속되며 이후 모든 operation도 같은 thread가 수행해야 합니다. driver open function은 thread용 context를 만들고 초기화합니다. 이 context에는 제출된 request를 추적하는 driver 내부 pointer와 value가 들어갑니다.
driver는 여러 concurrent request의 Completion Area를 담을 만큼 큰 completion-area buffer도 할당합니다. device를 닫으면 outstanding transaction을 flush하고 context를 정리합니다.
DAX1(M7) system의 device 이름은 `oradax1`, DAX2(M8) system은 `oradax2`입니다. 특정 version이 필요하면 해당 device를 열어 보면 됩니다. 한 system에는 둘 중 하나만 존재하므로 이름으로 platform 지원 수준을 판별할 수 있습니다.
immediate command는 `CCB_DEQUEUE`, `CCB_KILL`, `CCB_INFO`입니다. 모두 성공하면 `write()`가 전달한 byte 수와 같은 값을 반환하며, 실패하면 `-1`을 반환하고 `errno`를 설정합니다.
CCB_DEQUEUE·CCB_KILL·CCB_INFO
111-135| command | 동작 | 성공 후 read() |
|---|---|---|
| `CCB_DEQUEUE` | 지난 request와 연결된 resource를 정리할 수 있다고 driver에 알립니다. | 추가 status가 없으므로 호출하지 않습니다. |
| `CCB_KILL` | 실행 중인 CCB를 중단하며 성공 반환 뒤에는 더 실행되지 않음을 보장합니다. | action 결과를 가져오기 위해 호출해야 합니다. |
| `CCB_INFO` | 현재 실행 중인 CCB 정보를 조회합니다. | action 세부 정보를 가져오기 위해 호출해야 합니다. |
완료 interrupt가 없으므로 driver는 resource를 회수해도 되는 시점을 스스로 알 수 없습니다. application은 완료 처리를 마친 뒤 `CCB_DEQUEUE`를 명시적으로 수행해야 합니다.
일부 Hypervisor는 CCB가 `inprogress`인데도 `CCB_INFO`에 `notfound`를 반환할 수 있습니다. `notfound` 상태의 CCB가 이후 절대 실행되지 않도록 보장하려면 그 CCB에 `CCB_KILL`을 호출해야 합니다.
CCB array 제출과 mmap
136-159길이가 CCB size의 배수인 `write()`는 submit operation으로 취급됩니다. file offset은 사용할 Completion Area index이며 `lseek()` 또는 `pwrite()`로 지정할 수 있습니다.
| 반환 | 의미와 후속 처리 |
|---|---|
| `-1` | `errno`가 오류를 나타냅니다. |
| 요청 길이와 같은 값 | coprocessor가 array 전체를 받아 제출이 완전히 성공했습니다. 이후 `read()`를 호출하지 않습니다. |
| 0 이상이지만 요청 길이보다 작은 값 | CCB array가 일부만 수락되었습니다. `read()`로 첫 미수락 CCB의 오류 status와 경우에 따라 `status_data`를 가져옵니다. |
`mmap()`은 driver가 할당한 Completion Area에 대한 접근을 제공합니다. user process는 이 영역에 쓸 수 없으므로 mmap call에 `PROT_WRITE`를 지정하면 안 됩니다.
monitored load와 mwait로 완료 감지
160-178각 Completion Area의 첫 byte는 coprocessor hardware가 갱신하는 command status입니다. software는 M7/M8 processor 기능으로 이 byte를 효율적으로 polling할 수 있습니다.
monitored load는 ASI `0x84`인 `ASI_MONITOR_PRIMARY`를 사용한 Load from Alternate Space(`ldxa`, `lduba` 등)로 수행합니다. monitored wait는 `%asr28`에 쓰는 `mwait` instruction으로 수행합니다.
`mwait`는 지정한 nanosecond 동안 virtual processor 실행을 멈춘다는 점에서 pause와 비슷하지만 여러 event 중 하나가 발생하면 일찍 끝납니다. 감시 위치를 포함하는 data block이 수정되면 즉시 종료되므로 transaction 완료 뒤 context switch나 kernel-to-user transition 없이 software가 실행을 재개합니다. 이 때문에 완료부터 재개까지의 latency가 불과 몇 nanosecond일 수 있습니다.
DAX 제출의 application 수명주기
179-193- DAX device를 엽니다.
- `mmap()`을 호출해 Completion Area address를 얻습니다.
- CCB를 할당하고 opcode, flag, parameter, address 등을 채웁니다.
- `write()` 또는 `pwrite()`로 CCB를 제출합니다.
- monitored load와 monitored wait를 반복하고 command status가 완료를 나타내면 끝냅니다. 필요하면 언제든 `CCB_KILL` 또는 `CCB_INFO`를 사용할 수 있습니다.
- `CCB_DEQUEUE`를 수행합니다.
- Completion Area에 `munmap()`을 호출합니다.
- DAX device를 닫습니다.
물리 연속성과 page boundary 제약
194-229DAX hardware는 physical address만 사용하므로 virtual memory mapping이나 virtual buffer가 매핑되는 physical memory의 불연속을 알지 못합니다. I/O TLB도 scatter/gather mechanism도 없습니다. input과 output을 포함한 모든 buffer는 physically contiguous memory region 안에 있어야 합니다.
Hypervisor는 CCB의 모든 address를 physical address로 바꾼 뒤 DAX에 넘깁니다. 각 virtual address의 virtual page size를 확인해 address별 size limit을 programming하므로 coprocessor가 physical memory에 직접 접근하더라도 virtual page 경계 밖을 읽거나 쓰지 못합니다.
즉 DAX operation은 virtual page boundary를 절대 넘지 않습니다. 8 KiB virtual page를 쓰면 data는 8 KiB로 엄격히 제한됩니다. user buffer가 더 크다면 더 큰 page size를 쓰지 않는 한 transaction size가 8 KiB로 잘립니다.
표준 interface로 huge page를 할당하면 훨씬 큰 DAX transaction을 수행할 수 있지만 huge page에서도 page boundary를 넘을 수 없다는 규칙은 그대로입니다.
Linux/SPARC가 제공하는 8 MiB huge page는 실제 8 MiB hardware page가 아니라 4 MiB page 두 개를 이어 붙여 합성한 것입니다. DAX request의 한 buffer는 앞쪽 또는 뒤쪽 4 MiB 절반만 쓸 수 있으며 hardware page boundary를 가로지르는 가운데 4 MiB 구간은 쓸 수 없습니다. high-level library가 이 제약을 감출 수도 있습니다.
CCB 구조와 driver 검증
230-258CCB는 64-bit word 8개로 이루어진 array입니다. 일부 word에는 command opcode, parameter, flag 등이 들어가고 나머지는 Completion Area, output buffer, 여러 input의 address입니다.
struct ccb {
u64 control;
u64 completion;
u64 input0;
u64 access;
u64 input1;
u64 op_data;
u64 output;
u64 table;
};
각 field의 상세 설명은 `libdax/common/sys/dax1/dax1_ccb.h`를 참조하고, guest OS인 Linux kernel에 제공되는 전체 Hypervisor API는 `dax-hv-api.txt`를 참조합니다.
driver는 첫 word인 `control`에서 다음 항목을 검사합니다.
- CCB version은 hardware version과 일치해야 합니다.
- Opcode는 문서에 허용된 command 중 하나여야 합니다.
- user가 제공한 모든 address의 address type은 `virtual`이어야 합니다. 이 검증으로 application은 자신이 소유한 memory만 접근할 수 있습니다.
user code의 device·Completion Area 준비
259-291DAX는 user code와 kernel code 모두에서 접근할 수 있습니다. kernel code는 hypercall을 직접 실행하지만 user code는 driver wrapper를 사용해야 합니다. CCB 설정은 거의 같고 Completion Area 준비 방식만 다릅니다. 먼저 user code 예제를 설명한 뒤 kernel code 예제를 설명합니다.
driver API로 programming하려면 `arch/sparc/include/uapi/asm/oradax.h`를 include해야 합니다. M7은 `/dev/oradax1`, M8은 `/dev/oradax2`를 사용하며 한 device만 열리므로 둘을 차례로 시도하는 방법이 가장 단순합니다.
fd = open("/dev/oradax1", O_RDWR);
if (fd < 0)
fd = open("/dev/oradax2", O_RDWR);
if (fd < 0)
/* No DAX found */
다음으로 Completion Area를 read-only mapping합니다.
completion_area = mmap(NULL, DAX_MMAP_LEN, PROT_READ, MAP_SHARED, fd, 0);
모든 input/output buffer는 하나의 hardware page 안에 완전히 들어가야 합니다. DAX가 virtual page boundary에 엄격히 제한되기 때문입니다. 또한 coprocessor는 cache-line 단위로 쓰므로 output buffer는 64-byte aligned여야 하고 size도 64 byte의 배수여야 합니다.
Scan command 예제의 의미
292-305이 예제는 input vector와 match value를 받아 output bitmap을 만드는 DAX Scan command를 보여 줍니다. match value와 일치하는 각 input element에 대응하는 output bit를 설정합니다.
input vector는 1-bit element의 연속이고 match value는 0입니다. 따라서 input의 각 0 bit는 output의 1이 되고 input의 1은 output의 0이 되어 결과 bitmap은 input bitmap을 뒤집은 값입니다.
이 CCB에서 사용하는 모든 parameter와 bit의 세부 의미는 DAX Hypervisor API 문서 section `36.2.1.3`의 Scan command 설명을 참조합니다.
Scan CCB 구성 코드
306-333`control`은 Scan Value command, primary virtual input/output address type, fixed-width bit-packed input, 1-bit element, bit-vector output, 첫 scan criterion과 비활성화된 둘째 criterion을 지정합니다. `access`는 primary input 길이를 bit 단위로 `nbits - 1`에 기록합니다.
driver가 Completion Area address를 채우므로 `completion`은 0으로 두고, secondary input과 table은 사용하지 않습니다. `op_data`의 scan criterion은 match value 0입니다.
원문 307-316줄의 shift expression을 field별로 구조화했습니다. 아래 표와 원문 C code는 같은 값을 나타냅니다.
ccb->control = /* Table 36.1, CCB Header Format */
(2L << 48) /* command = Scan Value */
| (3L << 40) /* output address type = primary virtual */
| (3L << 34) /* primary input address type = primary virtual */
/* Section 36.2.1, Query CCB Command Formats */
| (1 << 28) /* 36.2.1.1.1 primary input format = fixed width bit packed */
| (0 << 23) /* 36.2.1.1.2 primary input element size = 0 (1 bit) */
| (8 << 10) /* 36.2.1.1.6 output format = bit vector */
| (0 << 5) /* 36.2.1.3 First scan criteria size = 0 (1 byte) */
| (31 << 0); /* 36.2.1.3 Disable second scan criteria */
ccb->completion = 0; /* Completion area address, to be filled in by driver */
ccb->input0 = (unsigned long) input; /* primary input address */
ccb->access = /* Section 36.2.1.2, Data Access Control */
(2 << 24) /* Primary input length format = bits */
| (nbits - 1); /* number of bits in primary input stream, minus 1 */
ccb->input1 = 0; /* secondary input address, unused */
ccb->op_data = 0; /* scan criteria (value to be matched) */
ccb->output = (unsigned long) output; /* output address */
ccb->table = 0; /* table address, unused */
CCB 제출과 Completion Area polling
334-360CCB는 `write()` 또는 `pwrite()` system call로 driver에 제출합니다. call이 실패하면 `read()`로 status를 가져옵니다.
if (pwrite(fd, ccb, 64, 0) != 64) {
struct ccb_exec_result status;
read(fd, &status, sizeof(status));
/* bail out */
}
CCB 제출이 성공하면 Completion Area를 polling해 DAX 종료를 확인합니다. Completion Area 내용의 상세 형식은 DAX Hypervisor API section `36.2.2`에 있습니다. loop는 ASI `0x84`의 `lduba` monitored load로 status를 읽고, 0이면 `%asr28`에 1000을 쓰는 `mwait`으로 최대 1000 ns 기다린 뒤 다시 확인합니다.
while (1) {
/* Monitored Load */
__asm__ __volatile__("lduba [%1] 0x84, %0\n"
: "=r" (status)
: "r" (completion_area));
if (status) /* 0 indicates command in progress */
break;
/* MWAIT */
__asm__ __volatile__("wr %%g0, 1000, %%asr28\n" ::); /* 1000 ns */
}
완료 status 확인과 dequeue
361-383Completion Area status 1은 CCB가 성공적으로 완료되어 output bitmap이 valid함을 뜻하며 즉시 사용할 수 있습니다. 그 밖의 non-zero value는 DAX Hypervisor API section `36.2.2`에 정의된 error condition입니다.
if (completion_area[0] != 1) { /* section 36.2.2, 1 = command ran and succeeded */
/* completion_area[0] contains the completion status */
/* completion_area[1] contains an error code, see 36.2.2 */
}
Completion Area 처리를 마치면 request에 연결된 resource를 해제해도 된다고 driver에 알려야 합니다. `CCB_DEQUEUE` command를 `write()`로 보내고, 그 뒤 Completion Area unmapping, DAX device close, memory free 같은 일반적인 program cleanup을 수행합니다.
struct dax_command cmd;
cmd.command = CCB_DEQUEUE;
if (write(fd, &cmd, sizeof(cmd)) != sizeof(cmd)) {
/* bail out */
}
kernel Completion Area와 직접 hypercall
384-415kernel code에서 DAX를 사용할 때 user application과 다른 점은 Completion Area 처리뿐입니다. user application은 driver가 할당한 영역을 mmap하지만 kernel code는 Completion Area용 memory를 직접 할당하고 그 address와 type을 CCB에 기록해야 합니다.
ccb->control |= /* Table 36.1, CCB Header Format */
(3L << 32); /* completion area address type = primary virtual */
ccb->completion = (unsigned long) completion_area; /* Completion area address */
kernel은 DAX submit hypercall을 직접 실행합니다. `sun4v_ccb_submit()`의 flag는 DAX Hypervisor API section `36.3.1`에 정의되어 있습니다. 예제는 query command, privileged argument, primary address type, privileged virtual address를 지정하고 `bytes_accepted`와 `status_data`를 받습니다.
#include <asm/hypervisor.h>
hv_rv = sun4v_ccb_submit((unsigned long)ccb, 64,
HV_CCB_QUERY_CMD |
HV_CCB_ARG0_PRIVILEGED | HV_CCB_ARG0_TYPE_PRIMARY |
HV_CCB_VA_PRIVILEGED,
0, &bytes_accepted, &status_data);
if (hv_rv != HV_EOK) {
/* hv_rv is an error code, status_data contains */
/* potential additional status, see 36.3.1.1 */
}
`hv_rv`가 `HV_EOK`가 아니면 Hypervisor error code이며 `status_data`에 section `36.3.1.1`이 설명하는 추가 status가 들어 있을 수 있습니다.
kernel의 동일한 polling 절차
416-439제출 뒤 Completion Area polling code는 user land와 같습니다. monitored load로 status를 읽고 진행 중을 뜻하는 0이면 `mwait`으로 기다립니다. non-zero status가 1이 아니면 Completion Area의 status와 error code를 조사합니다.
while (1) {
/* Monitored Load */
__asm__ __volatile__("lduba [%1] 0x84, %0\n"
: "=r" (status)
: "r" (completion_area));
if (status) /* 0 indicates command in progress */
break;
/* MWAIT */
__asm__ __volatile__("wr %%g0, 1000, %%asr28\n" ::); /* 1000 ns */
}
if (completion_area[0] != 1) { /* section 36.2.2, 1 = command ran and succeeded */
/* completion_area[0] contains the completion status */
/* completion_area[1] contains an error code, see 36.2.2 */
}
completion status가 성공을 나타내는 즉시 output bitmap을 사용할 수 있습니다.
UltraSPARC VM 사양 발췌 포함
440-445마지막 절은 companion file인 `dax-hv-api.txt`를 literal include하여 UltraSPARC Virtual Machine Specification의 DAX Hypervisor API 발췌문을 이어 붙입니다. 원문의 heading에는 `Excer[t`라는 오자가 있습니다.
.. include:: dax-hv-api.txt
:literal:
요약과 해설
oracle-dax.rst:1-445DAX는 SPARC M7/M8에서 CPU cache와 physical memory에 직접 접근해 data-stream 연산을 수행하는 coprocessor입니다. user application은 `write()`/`pwrite()`로 CCB를 제출하고 read-only로 mmap한 128-byte Completion Area를 monitored load와 `mwait`으로 polling합니다. 완료 interrupt 없이 user level에서 즉시 실행을 재개하도록 설계된 것이 핵심입니다.
DAX에는 I/O TLB나 scatter/gather가 없으므로 모든 buffer가 물리적으로 연속되어야 하며 operation은 virtual page boundary를 넘지 못합니다. user driver는 address type을 virtual로 제한해 process 소유 memory만 접근하게 하고, kernel은 Completion Area를 직접 할당해 `sun4v_ccb_submit()` hypercall을 호출합니다.
예제는 1-bit input vector에서 0을 찾는 Scan CCB를 구성해 반전 bitmap을 만들고, 제출 실패 status 조회, ASI `0x84` monitored load, `%asr28` `mwait`, `CCB_DEQUEUE` resource 회수까지 전체 수명주기를 보여 줍니다.