← Documents Documentation/translations/it_IT/process/botching-up-ioctls.rst GitHub 원문 ↗

Linux 6.18.37 · Translations

ioctl을 망치지 않는 방법

GPU ioctl ABI의 고정 폭 type과 padding, 확장·검증·재시작, 시간·자원 수명과 대체 interface 원칙을 설명합니다.

Source pathDocumentation/translations/it_IT/process/botching-up-ioctls.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

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

1. 요약·해설

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

요약·해설

botching-up-ioctls.rst:1-249

GPU driver의 userspace ABI를 처음부터 장기간 유지할 수 있게 설계하는 실전 점검표입니다. Fixed-size integer, explicit padding과 pointer 변환부터 feature flag, zero 검증, overflow와 error-path test까지 다룹니다.

Restartable ioctl, absolute timeout과 pollable asynchronous event, file descriptor에 묶인 resource lifetime, private namespace 및 sysfs·debugfs 대안까지 연결해 ioctl 하나가 갖는 호환성과 운영 책임을 설명합니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 .. include:: ../disclaimer-ita.rst
2
3 :Original: Documentation/process/botching-up-ioctls.rst
4
5 ==========================================
6 (Come evitare di) Raffazzonare delle ioctl
7 ==========================================
8
9 Preso da: https://blog.ffwll.ch/2013/11/botching-up-ioctls.html
10
11 Scritto da : Daniel Vetter, Copyright © 2013 Intel Corporation
12
13 Una cosa che gli sviluppatori del sottosistema grafico del kernel Linux hanno
14 imparato negli ultimi anni è l'inutilità di cercare di creare un'interfaccia
15 unificata per gestire la memoria e le unità esecutive di diverse GPU. Dunque,
16 oggigiorno ogni driver ha il suo insieme di ioctl per allocare memoria ed
17 inviare dei programmi alla GPU. Il che è va bene dato che non c'è più un insano
18 sistema che finge di essere generico, ma al suo posto ci sono interfacce
19 dedicate. Ma al tempo stesso è più facile incasinare le cose.
20
21 Per evitare di ripetere gli stessi errori ho preso nota delle lezioni imparate
22 mentre raffazzonavo il driver drm/i915. La maggior parte di queste lezioni si
23 focalizzano sui tecnicismi e non sulla visione d'insieme, come le discussioni
24 riguardo al modo migliore per implementare una ioctl per inviare compiti alla
25 GPU. Probabilmente, ogni sviluppatore di driver per GPU dovrebbe imparare queste
26 lezioni in autonomia.
27
28
29 Prerequisiti
30 ------------
31
32 Prima i prerequisiti. Seguite i seguenti suggerimenti se non volete fallire in
33 partenza e ritrovarvi ad aggiungere un livello di compatibilità a 32-bit.
34
35 * Usate solamente interi a lunghezza fissa. Per evitare i conflitti coi tipi
36 definiti nello spazio utente, il kernel definisce alcuni tipi speciali, come:
37 ``__u32``, ``__s64``. Usateli.
38
39 * Allineate tutto alla lunghezza naturale delle piattaforma in uso e riempite
40 esplicitamente i vuoti. Non necessariamente le piattaforme a 32-bit allineano
41 i valori a 64-bit rispettandone l'allineamento, ma le piattaforme a 64-bit lo
42 fanno. Dunque, per farlo correttamente in entrambe i casi dobbiamo sempre
43 riempire i vuoti.
44
45 * Se una struttura dati contiene valori a 64-bit, allora fate si che la sua
46 dimensione sia allineata a 64-bit, altrimenti la sua dimensione varierà su
47 sistemi a 32-bit e 64-bit. Avere una dimensione differente causa problemi
48 quando si passano vettori di strutture dati al kernel, o quando il kernel
49 effettua verifiche sulla dimensione (per esempio il sistema drm lo fa).
50
51 * I puntatori sono di tipo ``__u64``, con un *cast* da/a ``uintptr_t`` da lato
52 spazio utente e da/a ``void __user *`` nello spazio kernel. Sforzatevi il più
53 possibile per non ritardare la conversione, o peggio maneggiare ``__u64`` nel
54 vostro codice perché questo riduce le verifiche che strumenti come sparse
55 possono effettuare. La macro u64_to_user_ptr() può essere usata nel kernel
56 per evitare avvisi riguardo interi e puntatori di dimensioni differenti.
57
58
59 Le Basi
60 -------
61
62 Con la gioia d'aver evitato un livello di compatibilità, possiamo ora dare uno
63 sguardo alle basi. Trascurare questi punti renderà difficile la gestione della
64 compatibilità all'indietro ed in avanti. E dato che sbagliare al primo colpo è
65 garantito, dovrete rivisitare il codice o estenderlo per ogni interfaccia.
66
67 * Abbiate un modo chiaro per capire dallo spazio utente se una nuova ioctl, o
68 l'estensione di una esistente, sia supportata dal kernel in esecuzione. Se non
69 potete fidarvi del fatto che un vecchio kernel possa rifiutare correttamente
70 un nuovo *flag*, modalità, o ioctl, (probabilmente perché avevate raffazzonato
71 qualcosa nel passato) allora dovrete implementare nel driver un meccanismo per
72 notificare quali funzionalità sono supportate, o in alternativa un numero di
73 versione.
74
75 * Abbiate un piano per estendere le ioctl con nuovi *flag* o campi alla fine di
76 una struttura dati. Il sistema drm verifica la dimensione di ogni ioctl in
77 arrivo, ed estende con zeri ogni incongruenza fra kernel e spazio utente.
78 Questo aiuta, ma non è una soluzione completa dato che uno spazio utente nuovo
79 su un kernel vecchio non noterebbe che i campi nuovi alla fine della struttura
80 vengono ignorati. Dunque, anche questo avrà bisogno di essere notificato dal
81 driver allo spazio utente.
82
83 * Verificate tutti i campi e *flag* inutilizzati ed i riempimenti siano a 0,
84 altrimenti rifiutare la ioctl. Se non lo fate il vostro bel piano per
85 estendere le ioctl andrà a rotoli dato che qualcuno userà delle ioctl con
86 strutture dati con valori casuali dallo stack nei campi inutilizzati. Il che
87 si traduce nell'avere questi campi nell'ABI, e la cui unica utilità sarà
88 quella di contenere spazzatura. Per questo dovrete esplicitamente riempire i
89 vuoti di tutte le vostre strutture dati, anche se non le userete in un
90 vettore. Il riempimento fatto dal compilatore potrebbe contenere valori
91 casuali.
92
93 * Abbiate un semplice codice di test per ognuno dei casi sopracitati.
94
95
96 Divertirsi coi percorsi d'errore
97 --------------------------------
98
99 Oggigiorno non ci sono più scuse rimaste per permettere ai driver drm di essere
100 sfruttati per diventare root. Questo significa che dobbiamo avere una completa
101 validazione degli input e gestire in modo robusto i percorsi - tanto le GPU
102 moriranno comunque nel più strano dei casi particolari:
103
104 * Le ioctl devono verificare l'overflow dei vettori. Inoltre, per i valori
105 interi si devono verificare *overflow*, *underflow*, e *clamping*. Il
106 classico esempio è l'inserimento direttamente nell'hardware di valori di
107 posizionamento di un'immagine *sprite* quando l'hardware supporta giusto 12
108 bit, o qualcosa del genere. Tutto funzionerà finché qualche strano *display
109 server* non decide di preoccuparsi lui stesso del *clamping* e il cursore
110 farà il giro dello schermo.
111
112 * Avere un test semplice per ogni possibile fallimento della vostra ioctl.
113 Verificate che il codice di errore rispetti le aspettative. Ed infine,
114 assicuratevi che verifichiate un solo percorso sbagliato per ogni sotto-test
115 inviando comunque dati corretti. Senza questo, verifiche precedenti
116 potrebbero rigettare la ioctl troppo presto, impedendo l'esecuzione del
117 codice che si voleva effettivamente verificare, rischiando quindi di
118 mascherare bachi e regressioni.
119
120 * Fate si che tutte le vostre ioctl siano rieseguibili. Prima di tutto X adora
121 i segnali; secondo questo vi permetterà di verificare il 90% dei percorsi
122 d'errore interrompendo i vostri test con dei segnali. Grazie all'amore di X
123 per i segnali, otterrete gratuitamente un eccellente copertura di base per
124 tutti i vostri percorsi d'errore. Inoltre, siate consistenti sul modo di
125 gestire la riesecuzione delle ioctl - per esempio, drm ha una piccola
126 funzione di supporto `drmIoctl` nella sua librerie in spazio utente. Il
127 driver i915 l'abbozza con l'ioctl `set_tiling`, ed ora siamo inchiodati per
128 sempre con una semantica arcana sia nel kernel che nello spazio utente.
129
130
131 * Se non potete rendere un pezzo di codice rieseguibile, almeno rendete
132 possibile la sua interruzione. Le GPU moriranno e i vostri utenti non vi
133 apprezzeranno affatto se tenete in ostaggio il loro scatolotto (mediante un
134 processo X insopprimibile). Se anche recuperare lo stato è troppo complicato,
135 allora implementate una scadenza oppure come ultima spiaggia una rete di
136 sicurezza per rilevare situazioni di stallo quando l'hardware da di matto.
137
138 * Preparate dei test riguardo ai casi particolarmente estremi nel codice di
139 recupero del sistema - è troppo facile create uno stallo fra il vostro codice
140 anti-stallo e un processo scrittore.
141
142
143 Tempi, attese e mancate scadenze
144 --------------------------------
145
146 Le GPU fanno quasi tutto in modo asincrono, dunque dobbiamo regolare le
147 operazioni ed attendere quelle in sospeso. Questo è davvero difficile; al
148 momento nessuna delle ioctl supportante dal driver drm/i915 riesce a farlo
149 perfettamente, il che significa che qui ci sono ancora una valanga di lezioni da
150 apprendere.
151
152 * Per fare riferimento al tempo usate sempre ``CLOCK_MONOTONIC``. Oggigiorno
153 questo è quello che viene usato di base da alsa, drm, e v4l. Tuttavia,
154 lasciate allo spazio utente la possibilità di capire quali *timestamp*
155 derivano da domini temporali diversi come il vostro orologio di sistema
156 (fornito dal kernel) oppure un contatore hardware indipendente da qualche
157 parte. Gli orologi divergeranno, ma con questa informazione gli strumenti di
158 analisi delle prestazioni possono compensare il problema. Se il vostro spazio
159 utente può ottenere i valori grezzi degli orologi, allora considerate di
160 esporre anch'essi.
161
162 * Per descrivere il tempo, usate ``__s64`` per i secondi e ``__u64`` per i
163 nanosecondi. Non è il modo migliore per specificare il tempo, ma è
164 praticamente uno standard.
165
166 * Verificate che gli input di valori temporali siano normalizzati, e se non lo
167 sono scartateli. Fate attenzione perché la struttura dati ``struct ktime``
168 del kernel usa interi con segni sia per i secondi che per i nanosecondi.
169
170 * Per le scadenze (*timeout*) usate valori temporali assoluti. Se siete dei
171 bravi ragazzi e avete reso la vostra ioctl rieseguibile, allora i tempi
172 relativi tendono ad essere troppo grossolani e a causa degli arrotondamenti
173 potrebbero estendere in modo indefinito i tempi di attesa ad ogni
174 riesecuzione. Particolarmente vero se il vostro orologio di riferimento è
175 qualcosa di molto lento come il contatore di *frame*. Con la giacca da
176 avvocato delle specifiche diremmo che questo non è un baco perché tutte le
177 scadenze potrebbero essere estese - ma sicuramente gli utenti vi odieranno
178 quando le animazioni singhiozzano.
179
180 * Considerate l'idea di eliminare tutte le ioctl sincrone con scadenze, e di
181 sostituirle con una versione asincrona il cui stato può essere consultato
182 attraverso il descrittore di file mediante ``poll``. Questo approccio si
183 sposa meglio in un applicazione guidata dagli eventi.
184
185 * Sviluppate dei test per i casi estremi, specialmente verificate che i valori
186 di ritorno per gli eventi già completati, le attese terminate con successo, e
187 le attese scadute abbiano senso e servano ai vostri scopi.
188
189
190 Non perdere risorse
191 -------------------
192 Nel suo piccolo il driver drm implementa un sistema operativo specializzato per
193 certe GPU. Questo significa che il driver deve esporre verso lo spazio
194 utente tonnellate di agganci per accedere ad oggetti e altre risorse. Farlo
195 correttamente porterà con se alcune insidie:
196
197 * Collegate sempre la vita di una risorsa creata dinamicamente, a quella del
198 descrittore di file. Considerate una mappatura 1:1 se la vostra risorsa
199 dev'essere condivisa fra processi - passarsi descrittori di file sul socket
200 unix semplifica la gestione anche per lo spazio utente.
201
202 * Dev'esserci sempre Il supporto ``O_CLOEXEC``.
203
204 * Assicuratevi di avere abbastanza isolamento fra utenti diversi. Di base
205 impostate uno spazio dei nomi riservato per ogni descrittore di file, il che
206 forzerà ogni condivisione ad essere esplicita. Usate uno spazio più globale
207 per dispositivo solo se gli oggetti sono effettivamente unici per quel
208 dispositivo. Un controesempio viene dall'interfaccia drm modeset, dove
209 oggetti specifici di dispositivo, come i connettori, condividono uno spazio
210 dei nomi con oggetti per il *framebuffer*, ma questi non sono per niente
211 condivisi. Uno spazio separato, privato di base, per i *framebuffer* sarebbe
212 stato meglio.
213
214 * Pensate all'identificazione univoca degli agganci verso lo spazio utente. Per
215 esempio, per la maggior parte dei driver drm, si considera fallace la doppia
216 sottomissione di un oggetto allo stesso comando ioctl. Ma per evitarlo, se
217 gli oggetti sono condivisibili, lo spazio utente ha bisogno di sapere se il
218 driver ha importato un oggetto da un altro processo. Non l'ho ancora provato,
219 ma considerate l'idea di usare il numero di inode come identificatore per i
220 descrittori di file condivisi - che poi è come si distinguono i veri file.
221 Sfortunatamente, questo richiederebbe lo sviluppo di un vero e proprio
222 filesystem virtuale nel kernel.
223
224
225 Ultimo, ma non meno importante
226 ------------------------------
227
228 Non tutti i problemi si risolvono con una nuova ioctl:
229
230 * Pensateci su due o tre volte prima di implementare un'interfaccia privata per
231 un driver. Ovviamente è molto più veloce seguire questa via piuttosto che
232 buttarsi in lunghe discussioni alla ricerca di una soluzione più generica. Ed
233 a volte un'interfaccia privata è quello che serve per sviluppare un nuovo
234 concetto. Ma alla fine, una volta che c'è un'interfaccia generica a
235 disposizione finirete per mantenere due interfacce. Per sempre.
236
237 * Considerate interfacce alternative alle ioctl. Gli attributi sysfs sono molto
238 meglio per impostazioni che sono specifiche di un dispositivo, o per
239 sotto-oggetti con una vita piuttosto statica (come le uscite dei connettori in
240 drm con tutti gli attributi per la sovrascrittura delle rilevazioni). O magari
241 solo il vostro sistema di test ha bisogno di una certa interfaccia, e allora
242 debugfs (che non ha un'interfaccia stabile) sarà la soluzione migliore.
243
244 Per concludere. Questo gioco consiste nel fare le cose giuste fin da subito,
245 dato che se il vostro driver diventa popolare e la piattaforma hardware longeva
246 finirete per mantenere le vostre ioctl per sempre. Potrete tentare di deprecare
247 alcune orribili ioctl, ma ci vorranno anni per riuscirci effettivamente. E
248 ancora, altri anni prima che sparisca l'ultimo utente capace di lamentarsi per
249 una regressione.
250

3. 한국어 전문 번역

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

GPU별 ioctl에서 얻은 ABI 교훈

1-28

첫 줄은 이탈리아어 번역 문서의 공통 면책 고지를 포함하고, `:Original:` metadata는 같은 v6.18.37의 `Documentation/process/botching-up-ioctls.rst`를 정확히 가리킵니다. Daniel Vetter가 drm/i915에서 얻은 교훈을 바탕으로 GPU별 전용 ioctl이 늘어날수록 ABI 설계를 더 엄격하게 해야 한다고 설명합니다.

Daniel Vetter가 drm/i915 개발 경험을 바탕으로 작성한 2013년 문서다. 서로 완전히 다른 GPU의 execution unit과 memory를 하나의 통합 interface로 관리하려는 시도는 실용적이지 않았다. 그래서 driver마다 memory allocation과 GPU work submission용 ioctl을 제공하게 되었다.

한 번만 쓰이는 가짜 generic interface를 피할 수 있지만 driver별 ABI를 잘못 설계할 가능성도 늘어난다. 이 문서는 command submission의 큰 설계보다 반복해서 실수하기 쉬운 기술 세부 사항을 다룬다.

.. include:: ../disclaimer-ita.rst

:Original: Documentation/process/botching-up-ioctls.rst

==========================================
(Come evitare di) Raffazzonare delle ioctl
==========================================

Preso da: https://blog.ffwll.ch/2013/11/botching-up-ioctls.html

Scritto da : Daniel Vetter, Copyright © 2013 Intel Corporation

Una cosa che gli sviluppatori del sottosistema grafico del kernel Linux hanno
imparato negli ultimi anni è l'inutilità di cercare di creare un'interfaccia
unificata per gestire la memoria e le unità esecutive di diverse GPU. Dunque,
oggigiorno ogni driver ha il suo insieme di ioctl per allocare memoria ed
inviare dei programmi alla GPU. Il che è va bene dato che non c'è più un insano
sistema che finge di essere generico, ma al suo posto ci sono interfacce
dedicate. Ma al tempo stesso è più facile incasinare le cose.

Per evitare di ripetere gli stessi errori ho preso nota delle lezioni imparate
mentre raffazzonavo il driver drm/i915. La maggior parte di queste lezioni si
focalizzano sui tecnicismi e non sulla visione d'insieme, come le discussioni
riguardo al modo migliore per implementare una ioctl per inviare compiti alla
GPU. Probabilmente, ogni sviluppatore di driver per GPU dovrebbe imparare queste
lezioni in autonomia.

32-bit compat를 피하기 위한 layout 전제

29-58
  • Fixed-size integer만 사용한다. Userspace typedef 충돌을 피하도록 __u32, __s64 같은 kernel ABI type을 쓴다.
  • 모든 field를 natural size에 align하고 explicit padding을 넣는다. 32-bit platform은 64-bit value를 64-bit boundary에 align하지 않을 수 있지만 64-bit platform은 그렇게 하므로 layout을 명시해야 한다.
  • 64-bit type이 있는 structure 전체 크기를 64 bit 배수로 padding한다. 그렇지 않으면 32-bit와 64-bit에서 sizeof가 달라져 structure array 전달과 drm core의 size check가 깨진다.
  • Pointer는 __u64로 표현한다. Userspace에서는 uintptr_t와 변환하고 kernel에서는 void __user *와 즉시 변환한다. Raw __u64 pointer를 내부까지 끌고 가지 않아야 sparse 검사를 살릴 수 있다. Kernel에서는 u64_to_user_ptr()로 size warning을 피할 수 있다.
Prerequisiti
------------

Prima i prerequisiti. Seguite i seguenti suggerimenti se non volete fallire in
partenza e ritrovarvi ad aggiungere un livello di compatibilità a 32-bit.

* Usate solamente interi a lunghezza fissa. Per evitare i conflitti coi tipi
  definiti nello spazio utente, il kernel definisce alcuni tipi speciali, come:
  ``__u32``, ``__s64``. Usateli.

* Allineate tutto alla lunghezza naturale delle piattaforma in uso e riempite
  esplicitamente i vuoti. Non necessariamente le piattaforme a 32-bit allineano
  i valori a 64-bit rispettandone l'allineamento, ma le piattaforme a 64-bit lo
  fanno. Dunque, per farlo correttamente in entrambe i casi dobbiamo sempre
  riempire i vuoti.

* Se una struttura dati contiene valori a 64-bit, allora fate si che la sua
  dimensione sia allineata a 64-bit, altrimenti la sua dimensione varierà su
  sistemi a 32-bit e 64-bit. Avere una dimensione differente causa problemi
  quando si passano vettori di strutture dati al kernel, o quando il kernel
  effettua verifiche sulla dimensione (per esempio il sistema drm lo fa).

* I puntatori sono di tipo ``__u64``, con un *cast* da/a ``uintptr_t`` da lato
  spazio utente e da/a ``void __user *`` nello spazio kernel. Sforzatevi il più
  possibile per non ritardare la conversione, o peggio maneggiare ``__u64`` nel
  vostro codice perché questo riduce le verifiche che strumenti come sparse
  possono effettuare. La macro u64_to_user_ptr() può essere usata nel kernel
  per evitare avvisi riguardo interi e puntatori di dimensioni differenti.

Forward와 backward compatibility

59-95
  • Userspace가 새 ioctl 또는 extension 지원 여부를 분명히 알아낼 방법을 제공한다. 옛 kernel이 새 flag와 mode를 확실히 거부하지 않는다면 driver feature flag나 revision number가 필요하다.
  • Structure 끝에 새 flag나 field를 추가할 확장 계획을 마련한다. drm core의 size check와 zero-extension은 도움이 되지만 새 userspace가 옛 kernel에서 추가 field가 무시된 사실을 알지 못하므로 feature flag가 여전히 필요하다.
  • Unused field, flag, padding이 모두 0인지 검사하고 아니면 ioctl을 거부한다. Random stack garbage를 받아들이면 그 값도 ABI 일부가 되어 field를 미래에 사용할 수 없다. Compiler가 만든 implicit padding에도 garbage가 들어갈 수 있으므로 explicit padding이 필요하다.
  • 위의 모든 동작에 간단한 testcase를 둔다.
Le Basi
-------

Con la gioia d'aver evitato un livello di compatibilità, possiamo ora dare uno
sguardo alle basi. Trascurare questi punti renderà difficile la gestione della
compatibilità all'indietro ed in avanti. E dato che sbagliare al primo colpo è
garantito, dovrete rivisitare il codice o estenderlo per ogni interfaccia.

* Abbiate un modo chiaro per capire dallo spazio utente se una nuova ioctl, o
  l'estensione di una esistente, sia supportata dal kernel in esecuzione. Se non
  potete fidarvi del fatto che un vecchio kernel possa rifiutare correttamente
  un nuovo *flag*, modalità, o ioctl, (probabilmente perché avevate raffazzonato
  qualcosa nel passato) allora dovrete implementare nel driver un meccanismo per
  notificare quali funzionalità sono supportate, o in alternativa un numero di
  versione.

* Abbiate un piano per estendere le ioctl con nuovi *flag* o campi alla fine di
  una struttura dati. Il sistema drm verifica la dimensione di ogni ioctl in
  arrivo, ed estende con zeri ogni incongruenza fra kernel e spazio utente.
  Questo aiuta, ma non è una soluzione completa dato che uno spazio utente nuovo
  su un kernel vecchio non noterebbe che i campi nuovi alla fine della struttura
  vengono ignorati. Dunque, anche questo avrà bisogno di essere notificato dal
  driver allo spazio utente.

* Verificate tutti i campi e *flag* inutilizzati ed i riempimenti siano a 0,
  altrimenti rifiutare la ioctl. Se non lo fate il vostro bel piano per
  estendere le ioctl andrà a rotoli dato che qualcuno userà delle ioctl con
  strutture dati con valori casuali dallo stack nei campi inutilizzati. Il che
  si traduce nell'avere questi campi nell'ABI, e la cui unica utilità sarà
  quella di contenere spazzatura. Per questo dovrete esplicitamente riempire i
  vuoti di tutte le vostre strutture dati, anche se non le userete in un
  vettore. Il riempimento fatto dal compilatore potrebbe contenere valori
  casuali.

* Abbiate un semplice codice di test per ognuno dei casi sopracitati.

Input validation과 error path test

96-119
  • Array size overflow와 일반 integer over/underflow, clamping 문제를 모두 검사한다. 예를 들어 12-bit hardware sprite position에 unclamped display-server 값을 직접 쓰면 cursor가 screen을 wrap할 수 있다.
  • 각 input validation failure에 별도 testcase를 만들고 예상 error code를 확인한다.
  • Subtest 하나에서는 오직 한 error path만 검사한다. 나머지 input은 완전히 valid하게 만들어 앞선 check가 먼저 거부하며 목표 path의 bug와 regression을 가리지 않게 한다.
Divertirsi coi percorsi d'errore
--------------------------------

Oggigiorno non ci sono più scuse rimaste per permettere ai driver drm di essere
sfruttati per diventare root. Questo significa che dobbiamo avere una completa
validazione degli input e gestire in modo robusto i percorsi - tanto le GPU
moriranno comunque nel più strano dei casi particolari:

 * Le ioctl devono verificare l'overflow dei vettori. Inoltre, per i valori
   interi si devono verificare *overflow*, *underflow*, e *clamping*. Il
   classico esempio è l'inserimento direttamente nell'hardware di valori di
   posizionamento di un'immagine *sprite* quando l'hardware supporta giusto 12
   bit, o qualcosa del genere. Tutto funzionerà finché qualche strano *display
   server* non decide di preoccuparsi lui stesso del *clamping* e il cursore
   farà il giro dello schermo.

 * Avere un test semplice per ogni possibile fallimento della vostra ioctl.
   Verificate che il codice di errore rispetti le aspettative. Ed infine,
   assicuratevi che verifichiate un solo percorso sbagliato per ogni sotto-test
   inviando comunque dati corretti. Senza questo, verifiche precedenti
   potrebbero rigettare la ioctl troppo presto, impedendo l'esecuzione del
   codice che si voleva effettivamente verificare, rischiando quindi di
   mascherare bachi e regressioni.

Restartable ioctl과 killable hang

120-142

모든 ioctl을 restartable하게 만든다. X server는 signal을 많이 사용하므로 test suite에 계속 signal을 보내는 것만으로 error handling path 대부분을 시험할 수 있다. Restart semantic은 userspace library helper까지 일관되게 유지한다. i915 set_tiling ioctl의 불일치처럼 한번 공개한 이상한 semantic은 kernel과 userspace에서 영구히 유지해야 할 수 있다.

Restartable하게 만들 수 없는 path라도 stuck task는 kill 가능해야 한다. GPU failure 때문에 unkillable X process가 system 전체를 멈추게 해서는 안 된다. State recovery가 어렵다면 timeout이나 hangcheck를 마지막 safety net으로 둔다.

Hangcheck code와 waiter 사이 deadlock처럼 까다로운 recovery corner case에도 testcase를 작성한다.

DRM userspace library의 작은 `drmIoctl` helper처럼 ioctl 재시작 규칙을 공통 경로에 모아 일관되게 적용해야 합니다. i915의 `set_tiling` ioctl은 이 규칙을 어겨 kernel과 userspace가 특이한 semantics를 계속 유지하게 된 사례입니다.

 * Fate si che tutte le vostre ioctl siano rieseguibili. Prima di tutto X adora
   i segnali; secondo questo vi permetterà di verificare il 90% dei percorsi
   d'errore interrompendo i vostri test con dei segnali. Grazie all'amore di X
   per i segnali, otterrete gratuitamente un eccellente copertura di base per
   tutti i vostri percorsi d'errore. Inoltre, siate consistenti sul modo di
   gestire la riesecuzione delle ioctl - per esempio, drm ha una piccola
   funzione di supporto `drmIoctl` nella sua librerie in spazio utente. Il
   driver i915 l'abbozza con l'ioctl `set_tiling`, ed ora siamo inchiodati per
   sempre con una semantica arcana sia nel kernel che nello spazio utente.


 * Se non potete rendere un pezzo di codice rieseguibile, almeno rendete
   possibile la sua interruzione. Le GPU moriranno e i vostri utenti non vi
   apprezzeranno affatto se tenete in ostaggio il loro scatolotto (mediante un
   processo X insopprimibile). Se anche recuperare lo stato è troppo complicato,
   allora implementate una scadenza oppure come ultima spiaggia una rete di
   sicurezza per rilevare situazioni di stallo quando l'hardware da di matto.

 * Preparate dei test riguardo ai casi particolarmente estremi nel codice di
   recupero del sistema - è troppo facile create uno stallo fra il vostro codice
   anti-stallo e un processo scrittore.

Clock domain과 time 표현

143-169

GPU operation은 대부분 asynchronous라 시간 측정과 outstanding work 대기가 필요하지만 어렵다.

  • Reference time은 항상 CLOCK_MONOTONIC을 사용한다. ALSA, DRM, V4L도 이를 기본으로 쓴다.
  • Kernel system clock과 독립 hardware counter처럼 다른 clock domain에서 나온 timestamp는 userspace에 명시한다. Clock mismatch를 없앨 수 없어도 performance tool이 보정할 수 있다.
  • Command stream performance counter sampling처럼 raw clock value에 접근할 수 있다면 노출을 고려한다.
  • Time은 __s64 seconds와 __u64 nanoseconds 조합으로 표현한다.
  • Input time이 normalize되었는지 검사하고 아니면 거부한다. Native struct ktime은 seconds와 nanoseconds 모두 signed integer이므로 주의한다.
Tempi, attese e mancate scadenze
--------------------------------

Le GPU fanno quasi tutto in modo asincrono, dunque dobbiamo regolare le
operazioni ed attendere quelle in sospeso. Questo è davvero difficile; al
momento nessuna delle ioctl supportante dal driver drm/i915 riesce a farlo
perfettamente, il che significa che qui ci sono ancora una valanga di lezioni da
apprendere.

 * Per fare riferimento al tempo usate sempre ``CLOCK_MONOTONIC``. Oggigiorno
   questo è quello che viene usato di base da alsa, drm, e v4l. Tuttavia,
   lasciate allo spazio utente la possibilità di capire quali *timestamp*
   derivano da domini temporali diversi come il vostro orologio di sistema
   (fornito dal kernel) oppure un contatore hardware indipendente da qualche
   parte. Gli orologi divergeranno, ma con questa informazione gli strumenti di
   analisi delle prestazioni possono compensare il problema. Se il vostro spazio
   utente può ottenere i valori grezzi degli orologi, allora considerate di
   esporre anch'essi.

 * Per descrivere il tempo, usate ``__s64`` per i secondi e ``__u64`` per i
   nanosecondi. Non è il modo migliore per specificare il tempo, ma è
   praticamente uno standard.

 * Verificate che gli input di valori temporali siano normalizzati, e se non lo
   sono scartateli. Fate attenzione perché la struttura dati ``struct ktime``
   del kernel usa interi con segni sia per i secondi che per i nanosecondi.

Absolute timeout과 asynchronous event

170-189

Timeout은 absolute time으로 표현한다. Restartable ioctl에서 relative timeout을 쓰면 restart마다 rounding되어 전체 wait가 기한 없이 늘어날 수 있다. Display frame counter처럼 느린 reference clock이면 animation stutter로 나타날 수 있다.

Timeout이 있는 synchronous wait ioctl 대신 poll 가능한 file descriptor로 asynchronous event를 전달하는 방식도 고려한다. Event-driven application main loop에 더 자연스럽다.

이미 완료된 event, 성공한 wait, timeout된 wait의 return value가 모두 일관되고 목적에 맞는지 corner-case testcase로 확인한다.

 * Per le scadenze (*timeout*) usate valori temporali assoluti. Se siete dei
   bravi ragazzi e avete reso la vostra ioctl rieseguibile, allora i tempi
   relativi tendono ad essere troppo grossolani e a causa degli arrotondamenti
   potrebbero estendere in modo indefinito i tempi di attesa ad ogni
   riesecuzione. Particolarmente vero se il vostro orologio di riferimento è
   qualcosa di molto lento come il contatore di *frame*. Con la giacca da
   avvocato delle specifiche diremmo che questo non è un baco perché tutte le
   scadenze potrebbero essere estese - ma sicuramente gli utenti vi odieranno
   quando le animazioni singhiozzano.

 * Considerate l'idea di eliminare tutte le ioctl sincrone con scadenze, e di
   sostituirle con una versione asincrona il cui stato può essere consultato
   attraverso il descrittore di file mediante ``poll``. Questo approccio si
   sposa meglio in un applicazione guidata dagli eventi.

 * Sviluppate dei test per i casi estremi, specialmente verificate che i valori
   di ritorno per gli eventi già completati, le attese terminate con successo, e
   le attese scadute abbiano senso e servano ai vostri scopi.

Resource lifetime과 namespace

190-224

완전한 DRM driver는 특정 GPU를 위한 작은 운영체제처럼 많은 object와 resource handle을 userspace에 노출한다.

  • 동적으로 생성한 resource lifetime을 항상 file descriptor lifetime에 연결한다. Process 사이 공유가 필요하면 1:1 mapping과 Unix-domain socket의 fd passing을 고려해 userspace lifetime 관리도 단순화한다.
  • 항상 O_CLOEXEC를 지원한다.
  • Client 사이를 충분히 격리한다. 기본은 sharing을 명시적으로 수행하게 하는 private per-fd namespace다. Object가 진정 device-unique할 때만 global per-device namespace를 사용한다.
  • DRM modeset의 connector 같은 per-device object와 대부분 공유하지 않는 framebuffer object가 같은 namespace를 쓰는 것은 반례다. Framebuffer는 기본 private인 별도 namespace가 더 적합했다.
  • Userspace handle의 uniqueness 요구를 설계한다. Shareable object를 다른 process에서 import했을 때 이미 본 object인지 구분해야 한다. Shared fd의 inode number를 unique identifier로 쓰는 방안이 있지만 kernel에 full virtual filesystem 구현이 필요하다.
Non perdere risorse
-------------------
Nel suo piccolo il driver drm implementa un sistema operativo specializzato per
certe GPU. Questo significa che il driver deve esporre verso lo spazio
utente tonnellate di agganci per accedere ad oggetti e altre risorse. Farlo
correttamente porterà con se alcune insidie:

 * Collegate sempre la vita di una risorsa creata dinamicamente, a quella del
   descrittore di file. Considerate una mappatura 1:1 se la vostra risorsa
   dev'essere condivisa fra processi - passarsi descrittori di file sul socket
   unix semplifica la gestione anche per lo spazio utente.

 * Dev'esserci sempre Il supporto ``O_CLOEXEC``.

 * Assicuratevi di avere abbastanza isolamento fra utenti diversi. Di base
   impostate uno spazio dei nomi riservato per ogni descrittore di file, il che
   forzerà ogni condivisione ad essere esplicita. Usate uno spazio più globale
   per dispositivo solo se gli oggetti sono effettivamente unici per quel
   dispositivo. Un controesempio viene dall'interfaccia drm modeset, dove
   oggetti specifici di dispositivo, come i connettori, condividono uno spazio
   dei nomi con oggetti per il *framebuffer*, ma questi non sono per niente
   condivisi. Uno spazio separato, privato di base, per i *framebuffer* sarebbe
   stato meglio.

 * Pensate all'identificazione univoca degli agganci verso lo spazio utente. Per
   esempio, per la maggior parte dei driver drm, si considera fallace la doppia
   sottomissione di un oggetto allo stesso comando ioctl. Ma per evitarlo, se
   gli oggetti sono condivisibili, lo spazio utente ha bisogno di sapere se il
   driver ha importato un oggetto da un altro processo. Non l'ho ancora provato,
   ma considerate l'idea di usare il numero di inode come identificatore per i
   descrittori di file condivisi - che poi è come si distinguono i veri file.
   Sfortunatamente, questo richiederebbe lo sviluppo di un vero e proprio
   filesystem virtuale nel kernel.

모든 문제에 새 ioctl이 필요한 것은 아니다

225-243

Driver-private interface는 generic solution 논의보다 빨리 만들 수 있고 새 개념을 먼저 시험할 때 필요할 수도 있다. 하지만 이후 generic interface가 생기면 두 interface를 기한 없이 함께 유지해야 한다.

Per-device setting이나 lifetime이 비교적 고정된 child object에는 sysfs attribute가 더 적합할 수 있다. Test suite만 필요한 interface라면 stable ABI를 약속하지 않는 debugfs가 낫다.

Ultimo, ma non meno importante
------------------------------

Non tutti i problemi si risolvono con una nuova ioctl:

* Pensateci su due o tre volte prima di implementare un'interfaccia privata per
  un driver. Ovviamente è molto più veloce seguire questa via piuttosto che
  buttarsi in lunghe discussioni alla ricerca di una soluzione più generica. Ed
  a volte un'interfaccia privata è quello che serve per sviluppare un nuovo
  concetto. Ma alla fine, una volta che c'è un'interfaccia generica a
  disposizione finirete per mantenere due interfacce. Per sempre.

* Considerate interfacce alternative alle ioctl. Gli attributi sysfs sono molto
  meglio per impostazioni che sono specifiche di un dispositivo, o per
  sotto-oggetti con una vita piuttosto statica (come le uscite dei connettori in
  drm con tutti gli attributi per la sovrascrittura delle rilevazioni). O magari
  solo il vostro sistema di test ha bisogno di una certa interfaccia, e allora
  debugfs (che non ha un'interfaccia stabile) sarà la soluzione migliore.

첫 ABI를 사실상 영구히 유지해야 한다

244-249

Driver가 널리 쓰이고 hardware platform이 오래 유지되면 공개한 ioctl ABI는 사실상 영구히 유지해야 한다. 새 hardware generation에서 나쁜 ioctl을 deprecate해도 수년이 걸리고, 마지막 사용자가 regression을 보고할 수 없게 될 때까지 또 수년이 필요하다. 그래서 첫 설계를 올바르게 만드는 것이 핵심이다.

Per concludere. Questo gioco consiste nel fare le cose giuste fin da subito,
dato che se il vostro driver diventa popolare e la piattaforma hardware longeva
finirete per mantenere le vostre ioctl per sempre. Potrete tentare di deprecare
alcune orribili ioctl, ma ci vorranno anni per riuscirci effettivamente. E
ancora, altri anni prima che sparisca l'ultimo utente capace di lamentarsi per
una regressione.