요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
================================================
Generic bitfield packing and unpacking functions
================================================
Problem statement
-----------------
When working with hardware, one has to choose between several approaches of
interfacing with it.
One can memory-map a pointer to a carefully crafted struct over the hardware
device's memory region, and access its fields as struct members (potentially
declared as bitfields). But writing code this way would make it less portable,
due to potential endianness mismatches between the CPU and the hardware device.
Additionally, one has to pay close attention when translating register
definitions from the hardware documentation into bit field indices for the
structs. Also, some hardware (typically networking equipment) tends to group
its register fields in ways that violate any reasonable word boundaries
(sometimes even 64 bit ones). This creates the inconvenience of having to
define "high" and "low" portions of register fields within the struct.
A more robust alternative to struct field definitions would be to extract the
required fields by shifting the appropriate number of bits. But this would
still not protect from endianness mismatches, except if all memory accesses
were performed byte-by-byte. Also the code can easily get cluttered, and the
high-level idea might get lost among the many bit shifts required.
Many drivers take the bit-shifting approach and then attempt to reduce the
clutter with tailored macros, but more often than not these macros take
shortcuts that still prevent the code from being truly portable.
The solution
------------
This API deals with 2 basic operations:
- Packing a CPU-usable number into a memory buffer (with hardware
constraints/quirks)
- Unpacking a memory buffer (which has hardware constraints/quirks)
into a CPU-usable number.
The API offers an abstraction over said hardware constraints and quirks,
over CPU endianness and therefore between possible mismatches between
the two.
The basic unit of these API functions is the u64. From the CPU's
perspective, bit 63 always means bit offset 7 of byte 7, albeit only
logically. The question is: where do we lay this bit out in memory?
The following examples cover the memory layout of a packed u64 field.
The byte offsets in the packed buffer are always implicitly 0, 1, ... 7.
What the examples show is where the logical bytes and bits sit.
1. Normally (no quirks), we would do it like this:
::
63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32
7 6 5 4
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
3 2 1 0
That is, the MSByte (7) of the CPU-usable u64 sits at memory offset 0, and the
LSByte (0) of the u64 sits at memory offset 7.
This corresponds to what most folks would regard to as "big endian", where
bit i corresponds to the number 2^i. This is also referred to in the code
comments as "logical" notation.
2. If QUIRK_MSB_ON_THE_RIGHT is set, we do it like this:
::
56 57 58 59 60 61 62 63 48 49 50 51 52 53 54 55 40 41 42 43 44 45 46 47 32 33 34 35 36 37 38 39
7 6 5 4
24 25 26 27 28 29 30 31 16 17 18 19 20 21 22 23 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7
3 2 1 0
That is, QUIRK_MSB_ON_THE_RIGHT does not affect byte positioning, but
inverts bit offsets inside a byte.
3. If QUIRK_LITTLE_ENDIAN is set, we do it like this:
::
39 38 37 36 35 34 33 32 47 46 45 44 43 42 41 40 55 54 53 52 51 50 49 48 63 62 61 60 59 58 57 56
4 5 6 7
7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8 23 22 21 20 19 18 17 16 31 30 29 28 27 26 25 24
0 1 2 3
Therefore, QUIRK_LITTLE_ENDIAN means that inside the memory region, every
byte from each 4-byte word is placed at its mirrored position compared to
the boundary of that word.
4. If QUIRK_MSB_ON_THE_RIGHT and QUIRK_LITTLE_ENDIAN are both set, we do it
like this:
::
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
4 5 6 7
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3
5. If just QUIRK_LSW32_IS_FIRST is set, we do it like this:
::
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
3 2 1 0
63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32
7 6 5 4
In this case the 8 byte memory region is interpreted as follows: first
4 bytes correspond to the least significant 4-byte word, next 4 bytes to
the more significant 4-byte word.
6. If QUIRK_LSW32_IS_FIRST and QUIRK_MSB_ON_THE_RIGHT are set, we do it like
this:
::
24 25 26 27 28 29 30 31 16 17 18 19 20 21 22 23 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7
3 2 1 0
56 57 58 59 60 61 62 63 48 49 50 51 52 53 54 55 40 41 42 43 44 45 46 47 32 33 34 35 36 37 38 39
7 6 5 4
7. If QUIRK_LSW32_IS_FIRST and QUIRK_LITTLE_ENDIAN are set, it looks like
this:
::
7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8 23 22 21 20 19 18 17 16 31 30 29 28 27 26 25 24
0 1 2 3
39 38 37 36 35 34 33 32 47 46 45 44 43 42 41 40 55 54 53 52 51 50 49 48 63 62 61 60 59 58 57 56
4 5 6 7
8. If QUIRK_LSW32_IS_FIRST, QUIRK_LITTLE_ENDIAN and QUIRK_MSB_ON_THE_RIGHT
are set, it looks like this:
::
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
4 5 6 7
We always think of our offsets as if there were no quirk, and we translate
them afterwards, before accessing the memory region.
Note on buffer lengths not multiple of 4
----------------------------------------
To deal with memory layout quirks where groups of 4 bytes are laid out "little
endian" relative to each other, but "big endian" within the group itself, the
concept of groups of 4 bytes is intrinsic to the packing API (not to be
confused with the memory access, which is performed byte by byte, though).
With buffer lengths not multiple of 4, this means one group will be incomplete.
Depending on the quirks, this may lead to discontinuities in the bit fields
accessible through the buffer. The packing API assumes discontinuities were not
the intention of the memory layout, so it avoids them by effectively logically
shortening the most significant group of 4 octets to the number of octets
actually available.
Example with a 31 byte sized buffer given below. Physical buffer offsets are
implicit, and increase from left to right within a group, and from top to
bottom within a column.
No quirks:
::
31 29 28 | Group 7 (most significant)
27 26 25 24 | Group 6
23 22 21 20 | Group 5
19 18 17 16 | Group 4
15 14 13 12 | Group 3
11 10 9 8 | Group 2
7 6 5 4 | Group 1
3 2 1 0 | Group 0 (least significant)
QUIRK_LSW32_IS_FIRST:
::
3 2 1 0 | Group 0 (least significant)
7 6 5 4 | Group 1
11 10 9 8 | Group 2
15 14 13 12 | Group 3
19 18 17 16 | Group 4
23 22 21 20 | Group 5
27 26 25 24 | Group 6
30 29 28 | Group 7 (most significant)
QUIRK_LITTLE_ENDIAN:
::
30 28 29 | Group 7 (most significant)
24 25 26 27 | Group 6
20 21 22 23 | Group 5
16 17 18 19 | Group 4
12 13 14 15 | Group 3
8 9 10 11 | Group 2
4 5 6 7 | Group 1
0 1 2 3 | Group 0 (least significant)
QUIRK_LITTLE_ENDIAN | QUIRK_LSW32_IS_FIRST:
::
0 1 2 3 | Group 0 (least significant)
4 5 6 7 | Group 1
8 9 10 11 | Group 2
12 13 14 15 | Group 3
16 17 18 19 | Group 4
20 21 22 23 | Group 5
24 25 26 27 | Group 6
28 29 30 | Group 7 (most significant)
Intended use
------------
Drivers that opt to use this API first need to identify which of the above 3
quirk combinations (for a total of 8) match what the hardware documentation
describes.
There are 3 supported usage patterns, detailed below.
packing()
^^^^^^^^^
This API function is deprecated.
The packing() function returns an int-encoded error code, which protects the
programmer against incorrect API use. The errors are not expected to occur
during runtime, therefore it is reasonable to wrap packing() into a custom
function which returns void and swallows those errors. Optionally it can
dump stack or print the error description.
.. code-block:: c
void my_packing(void *buf, u64 *val, int startbit, int endbit,
size_t len, enum packing_op op)
{
int err;
/* Adjust quirks accordingly */
err = packing(buf, val, startbit, endbit, len, op, QUIRK_LSW32_IS_FIRST);
if (likely(!err))
return;
if (err == -EINVAL) {
pr_err("Start bit (%d) expected to be larger than end (%d)\n",
startbit, endbit);
} else if (err == -ERANGE) {
if ((startbit - endbit + 1) > 64)
pr_err("Field %d-%d too large for 64 bits!\n",
startbit, endbit);
else
pr_err("Cannot store %llx inside bits %d-%d (would truncate)\n",
*val, startbit, endbit);
}
dump_stack();
}
pack() and unpack()
^^^^^^^^^^^^^^^^^^^
These are const-correct variants of packing(), and eliminate the last "enum
packing_op op" argument.
Calling pack(...) is equivalent, and preferred, to calling packing(..., PACK).
Calling unpack(...) is equivalent, and preferred, to calling packing(..., UNPACK).
pack_fields() and unpack_fields()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The library exposes optimized functions for the scenario where there are many
fields represented in a buffer, and it encourages consumer drivers to avoid
repetitive calls to pack() and unpack() for each field, but instead use
pack_fields() and unpack_fields(), which reduces the code footprint.
These APIs use field definitions in arrays of ``struct packed_field_u8`` or
``struct packed_field_u16``, allowing consumer drivers to minimize the size
of these arrays according to their custom requirements.
The pack_fields() and unpack_fields() API functions are actually macros which
automatically select the appropriate function at compile time, based on the
type of the fields array passed in.
An additional benefit over pack() and unpack() is that sanity checks on the
field definitions are handled at compile time with ``BUILD_BUG_ON`` rather
than only when the offending code is executed. These functions return void and
wrapping them to handle unexpected errors is not necessary.
It is recommended, but not required, that you wrap your packed buffer into a
structured type with a fixed size. This generally makes it easier for the
compiler to enforce that the correct size buffer is used.
Here is an example of how to use the fields APIs:
.. code-block:: c
/* Ordering inside the unpacked structure is flexible and can be different
* from the packed buffer. Here, it is optimized to reduce padding.
*/
struct data {
u64 field3;
u32 field4;
u16 field1;
u8 field2;
};
#define SIZE 13
typedef struct __packed { u8 buf[SIZE]; } packed_buf_t;
static const struct packed_field_u8 fields[] = {
PACKED_FIELD(100, 90, struct data, field1),
PACKED_FIELD(90, 87, struct data, field2),
PACKED_FIELD(86, 30, struct data, field3),
PACKED_FIELD(29, 0, struct data, field4),
};
void unpack_your_data(const packed_buf_t *buf, struct data *unpacked)
{
BUILD_BUG_ON(sizeof(*buf) != SIZE;
unpack_fields(buf, sizeof(*buf), unpacked, fields,
QUIRK_LITTLE_ENDIAN);
}
void pack_your_data(const struct data *unpacked, packed_buf_t *buf)
{
BUILD_BUG_ON(sizeof(*buf) != SIZE;
pack_fields(buf, sizeof(*buf), unpacked, fields,
QUIRK_LITTLE_ENDIAN);
}
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Hardware bitfield 표현의 문제
1-28Generic bitfield packing and unpacking functions
문제 정의 (Problem statement)
Hardware와 작업할 때는 여러 interface 방식 중 하나를 선택해야 합니다. 정교하게 만든 struct pointer를 hardware device의 memory region에 map하고, bitfield로 선언할 수도 있는 member를 통해 field에 접근할 수 있습니다. 하지만 CPU와 hardware device의 endianness가 다를 수 있어 portability가 떨어집니다.
Hardware 문서의 register definition을 struct bitfield index로 옮길 때도 세심한 주의가 필요합니다. 특히 networking 장비 같은 hardware는 합리적인 word boundary, 때로는 64-bit boundary까지 위반하는 방식으로 register field를 묶습니다. 그러면 struct 안에서 register field의 high와 low 부분을 따로 정의해야 하는 불편이 생깁니다.
Struct field 대신 필요한 bit 수만큼 shift하여 field를 추출하는 방식이 더 견고할 수 있습니다. 그러나 모든 memory access를 byte 단위로 수행하지 않는 한 endianness mismatch를 막지는 못합니다. 많은 bit shift 때문에 code가 복잡해지고 high-level 의도가 묻히기도 쉽습니다.
많은 driver가 bit-shift 방식을 사용한 뒤 custom macro로 복잡함을 줄이려 하지만, 이 macro도 대개 shortcut을 택해 code가 완전히 portable해지는 것을 막습니다.
Packing API가 제공하는 추상화
29-50해결책 (The solution)
이 API는 두 가지 기본 operation을 다룹니다.
- CPU가 사용할 수 있는 숫자를 hardware constraint와 quirk를 적용하여 memory buffer에 packing합니다.
- Hardware constraint와 quirk가 적용된 memory buffer를 CPU가 사용할 수 있는 숫자로 unpacking합니다.
API는 hardware constraint와 quirk, CPU endianness, 그리고 둘 사이에서 생길 수 있는 mismatch를 추상화합니다.
이 API function의 기본 단위는 `u64`입니다. CPU 관점에서 bit 63은 논리적으로 항상 byte 7의 bit offset 7을 뜻합니다. 핵심 질문은 이 bit를 memory의 어느 위치에 놓는가입니다.
다음 예제는 packed `u64` field의 memory layout을 설명합니다. Packed buffer의 byte offset은 항상 암묵적으로 0, 1부터 7까지이며, 그림은 logical byte와 bit가 놓이는 위치를 보여 줍니다.
Quirk가 없는 기본 layout
51-661. Quirk가 없을 때의 일반적인 layout입니다.
Packed buffer의 physical byte offset별 logical byte와 byte 내부 bit 진행 방향입니다.
CPU가 사용하는 `u64`의 MSByte인 7은 memory offset 0에 있고 LSByte인 0은 memory offset 7에 있습니다. 이는 bit `i`가 숫자 `2^i`에 대응하는 일반적인 big-endian 형태이며 code comment에서는 logical notation이라고도 부릅니다.
QUIRK_MSB_ON_THE_RIGHT layout
67-792. `QUIRK_MSB_ON_THE_RIGHT`를 설정한 layout입니다.
Packed buffer의 physical byte offset별 logical byte와 byte 내부 bit 진행 방향입니다.
`QUIRK_MSB_ON_THE_RIGHT`는 byte 위치에는 영향을 주지 않고 byte 내부의 bit offset을 반전합니다.
QUIRK_LITTLE_ENDIAN layout
80-923. `QUIRK_LITTLE_ENDIAN`을 설정한 layout입니다.
Packed buffer의 physical byte offset별 logical byte와 byte 내부 bit 진행 방향입니다.
`QUIRK_LITTLE_ENDIAN`은 memory region의 각 4-byte word 안에서 모든 byte를 word boundary에 대해 mirror된 위치에 배치합니다.
MSB-right와 little-endian 조합
93-1034. `QUIRK_MSB_ON_THE_RIGHT`와 `QUIRK_LITTLE_ENDIAN`을 모두 설정한 layout입니다.
Packed buffer의 physical byte offset별 logical byte와 byte 내부 bit 진행 방향입니다.
QUIRK_LSW32_IS_FIRST layout
104-1175. `QUIRK_LSW32_IS_FIRST`만 설정한 layout입니다.
Packed buffer의 physical byte offset별 logical byte와 byte 내부 bit 진행 방향입니다.
8-byte memory region의 첫 4 byte는 least-significant 4-byte word에 대응하고 다음 4 byte는 more-significant 4-byte word에 대응합니다.
LSW-first와 MSB-right 조합
118-1286. `QUIRK_LSW32_IS_FIRST`와 `QUIRK_MSB_ON_THE_RIGHT`를 설정한 layout입니다.
Packed buffer의 physical byte offset별 logical byte와 byte 내부 bit 진행 방향입니다.
LSW-first와 little-endian 조합
129-1397. `QUIRK_LSW32_IS_FIRST`와 `QUIRK_LITTLE_ENDIAN`을 설정한 layout입니다.
Packed buffer의 physical byte offset별 logical byte와 byte 내부 bit 진행 방향입니다.
세 quirk를 모두 적용한 layout
140-1538. `QUIRK_LSW32_IS_FIRST`, `QUIRK_LITTLE_ENDIAN`, `QUIRK_MSB_ON_THE_RIGHT`를 모두 설정한 layout입니다.
Packed buffer의 physical byte offset별 logical byte와 byte 내부 bit 진행 방향입니다.
Offset은 항상 quirk가 없는 것처럼 생각하고, memory region에 접근하기 직전에 quirk에 맞춰 변환합니다.
4의 배수가 아닌 buffer 길이
154-2244의 배수가 아닌 buffer 길이에 관한 참고 사항
4-byte group끼리는 little-endian으로, group 내부는 big-endian으로 배치되는 memory layout quirk를 처리하기 위해 4-byte group 개념은 packing API에 내재합니다. 실제 memory access는 byte 단위라는 점과 혼동하면 안 됩니다.
Buffer 길이가 4의 배수가 아니면 한 group이 불완전합니다. Quirk 조합에 따라 buffer를 통해 접근할 수 있는 bitfield가 불연속적일 수 있습니다. Packing API는 이런 불연속이 의도된 layout이 아니라고 보고, most-significant 4-octet group을 실제 사용 가능한 octet 수만큼 논리적으로 줄여 불연속을 피합니다.
다음은 31-byte buffer 예제입니다. Physical buffer offset은 암묵적으로 group 안에서 왼쪽에서 오른쪽으로, column 안에서 위에서 아래로 증가합니다.
Quirk가 없는 경우입니다.
31-byte buffer에서 physical offset이 왼쪽에서 오른쪽, 위에서 아래로 증가할 때 각 group에 놓이는 logical octet입니다.
`QUIRK_LSW32_IS_FIRST`를 적용한 경우입니다.
31-byte buffer에서 physical offset이 왼쪽에서 오른쪽, 위에서 아래로 증가할 때 각 group에 놓이는 logical octet입니다.
`QUIRK_LITTLE_ENDIAN`을 적용한 경우입니다.
31-byte buffer에서 physical offset이 왼쪽에서 오른쪽, 위에서 아래로 증가할 때 각 group에 놓이는 logical octet입니다.
`QUIRK_LITTLE_ENDIAN | QUIRK_LSW32_IS_FIRST`를 적용한 경우입니다.
31-byte buffer에서 physical offset이 왼쪽에서 오른쪽, 위에서 아래로 증가할 때 각 group에 놓이는 logical octet입니다.
Deprecated packing() 사용 pattern
225-270의도된 사용법 (Intended use)
이 API를 선택한 driver는 먼저 hardware 문서의 layout과 일치하는 세 quirk 조합, 총 여덟 경우 중 하나를 식별해야 합니다. 지원하는 사용 pattern은 세 가지이며 아래에서 설명합니다.
packing()
이 API function은 deprecated 상태입니다.
`packing()`은 잘못된 API 사용으로부터 programmer를 보호하는 int encoded error code를 반환합니다. Runtime에는 error가 발생하지 않을 것으로 기대하므로 `packing()`을 custom void function으로 감싸 error를 무시할 수 있습니다. 선택적으로 stack을 dump하거나 error 설명을 출력할 수 있습니다.
void my_packing(void *buf, u64 *val, int startbit, int endbit,
size_t len, enum packing_op op)
{
int err;
/* Adjust quirks accordingly */
err = packing(buf, val, startbit, endbit, len, op, QUIRK_LSW32_IS_FIRST);
if (likely(!err))
return;
if (err == -EINVAL) {
pr_err("Start bit (%d) expected to be larger than end (%d)\n",
startbit, endbit);
} else if (err == -ERANGE) {
if ((startbit - endbit + 1) > 64)
pr_err("Field %d-%d too large for 64 bits!\n",
startbit, endbit);
else
pr_err("Cannot store %llx inside bits %d-%d (would truncate)\n",
*val, startbit, endbit);
}
dump_stack();
}
Const-correct pack()과 unpack()
271-280pack() and unpack()
이들은 `packing()`의 const-correct variant이며 마지막 `enum packing_op op` argument를 제거합니다.
`pack(...)` 호출은 `packing(..., PACK)`과 같고 더 권장됩니다. `unpack(...)` 호출은 `packing(..., UNPACK)`과 같고 더 권장됩니다.
pack_fields()와 unpack_fields()
281-345pack_fields() and unpack_fields()
Library는 buffer에 많은 field가 있는 상황을 위한 optimized function을 제공합니다. Consumer driver가 field마다 `pack()`과 `unpack()`을 반복 호출하지 말고 code footprint를 줄이는 `pack_fields()`와 `unpack_fields()`를 사용하도록 권장합니다.
이 API는 `struct packed_field_u8` 또는 `struct packed_field_u16` array의 field definition을 사용하므로 consumer driver는 요구 사항에 맞춰 array 크기를 최소화할 수 있습니다.
`pack_fields()`와 `unpack_fields()`는 실제로 macro이며 전달된 fields array의 type을 기준으로 compile time에 적절한 function을 자동 선택합니다.
`pack()`과 `unpack()`보다 나은 또 다른 점은 field definition sanity check가 문제가 되는 code를 실행할 때가 아니라 compile time에 `BUILD_BUG_ON`으로 처리된다는 것입니다. 이 function은 void를 반환하므로 예상치 못한 error를 처리하기 위해 감쌀 필요가 없습니다.
필수는 아니지만 packed buffer를 고정 크기의 structured type으로 감싸는 것이 권장됩니다. 그러면 compiler가 올바른 크기의 buffer 사용을 강제하기 쉽습니다.
다음은 fields API 사용 예제입니다.
/* Ordering inside the unpacked structure is flexible and can be different
* from the packed buffer. Here, it is optimized to reduce padding.
*/
struct data {
u64 field3;
u32 field4;
u16 field1;
u8 field2;
};
#define SIZE 13
typedef struct __packed { u8 buf[SIZE]; } packed_buf_t;
static const struct packed_field_u8 fields[] = {
PACKED_FIELD(100, 90, struct data, field1),
PACKED_FIELD(90, 87, struct data, field2),
PACKED_FIELD(86, 30, struct data, field3),
PACKED_FIELD(29, 0, struct data, field4),
};
void unpack_your_data(const packed_buf_t *buf, struct data *unpacked)
{
BUILD_BUG_ON(sizeof(*buf) != SIZE;
unpack_fields(buf, sizeof(*buf), unpacked, fields,
QUIRK_LITTLE_ENDIAN);
}
void pack_your_data(const struct data *unpacked, packed_buf_t *buf)
{
BUILD_BUG_ON(sizeof(*buf) != SIZE;
pack_fields(buf, sizeof(*buf), unpacked, fields,
QUIRK_LITTLE_ENDIAN);
}
Unpacked `struct data`의 member 순서는 packed buffer와 달라도 되며 예제는 padding을 줄이도록 정렬합니다. `packed_buf_t`는 13-byte fixed-size buffer이고 `PACKED_FIELD` array는 logical bit range와 destination member를 연결합니다.
`unpack_your_data()`와 `pack_your_data()`는 `BUILD_BUG_ON`으로 buffer size를 확인하고 `QUIRK_LITTLE_ENDIAN`을 적용해 모든 field를 한 번에 변환합니다.
요약과 해설
packing.rst:1-345Packing API는 CPU가 이해하는 `u64` bit numbering을 byte 단위 memory access로 변환해 CPU와 hardware endianness mismatch를 피합니다. 세 quirk는 byte 내부 bit 반전, 4-byte word 내부 byte mirror, least-significant 32-bit word 우선 배치를 각각 표현합니다.
Offset은 quirk가 없는 logical numbering으로 정의하고 실제 memory access 직전에 변환합니다. Buffer 길이가 4의 배수가 아니면 most-significant group을 실제 octet 수만큼 논리적으로 줄여 bitfield discontinuity를 방지합니다.
Deprecated `packing()`보다 const-correct `pack()`과 `unpack()`을 사용해야 합니다. Field가 많다면 compile-time type dispatch와 `BUILD_BUG_ON` 검사를 제공하고 code footprint도 줄이는 `pack_fields()`와 `unpack_fields()`가 적합합니다.