xref: /linux/Documentation/translations/pt_BR/process/botching-up-ioctls.rst (revision 72fdff1416e280e2baaa3cca69574defb998437e)
1.. SPDX-License-Identifier: GPL-2.0
2
3============================================
4(Como evitar) Deixar as ioctls malfeitas
5============================================
6
7De: https://blog.ffwll.ch/2013/11/botching-up-ioctls.html
8
9Por: Daniel Vetter, Copyright © 2013 Intel Corporation
10
11Uma percepção clara que os hackers de gráficos do kernel tiveram nos últimos
12anos é que tentar criar uma interface unificada para gerenciar as unidades de
13execução e a memória em GPUs completamente diferentes é um esforço inútil.
14Portanto, hoje em dia, cada driver tem seu próprio conjunto de ioctls para
15alocar memória e enviar trabalho para a GPU. O que é bom, já que não há mais a
16insanidade na forma de interfaces falsamente genéricas, mas que na verdade só
17são usadas uma vez. No entanto, a desvantagem clara é que há muito mais
18potencial para estragar as coisas.
19
20Para evitar repetir todos os mesmos erros novamente, escrevi algumas das lições
21aprendidas enquanto fazia um trabalho malfeito para o driver drm/i915. A maioria
22delas aborda apenas tecnicalidades e não os problemas macro (big-picture), como
23deveria ser exatamente a aparência da ioctl de envio de comando. Aprender essas
24lições é provavelmente algo que cada driver de GPU tem que fazer por conta
25própria.
26
27
28Pré-requisitos
29--------------
30
31Primeiro, os pré-requisitos. Sem estes você já falhou, porque precisará
32adicionar uma camada de compatibilidade de 32 bits (compat layer):
33
34 * Use apenas inteiros de tamanho fixo. Para evitar conflitos com typedefs no
35   espaço de usuário (userspace), o kernel possui tipos especiais como __u32 e
36   __s64. Use-os.
37
38 * Alinhe tudo ao tamanho natural e use preenchimento (padding) explícito.
39   Plataformas de 32 bits não alinham necessariamente valores de 64 bits a
40   limites (boundaries) de 64 bits, mas plataformas de 64 bits o fazem. Portanto,
41   sempre precisamos de padding para o tamanho natural para acertar isso.
42
43 * Preencha a struct inteira para um múltiplo de 64 bits se a estrutura contiver
44   tipos de 64 bits -- caso contrário, o tamanho da estrutura diferirá entre
45   32 bits e 64 bits. Ter um tamanho de estrutura diferente prejudica ao passar
46   matrizes (arrays) de estruturas para o kernel, ou se o kernel verificar o
47   tamanho da estrutura, o que o core do drm, por exemplo, faz.
48
49 * Ponteiros são __u64, convertidos de/para um uintptr_t no lado do espaço de
50   usuário e de/para um void __user * no kernel. Tente de verdade não atrasar
51   essa conversão ou, pior ainda, manipular o __u64 bruto pelo seu código, pois
52   isso diminui a verificação que ferramentas como o sparse podem fornecer. A
53   macro u64_to_user_ptr pode ser usada no kernel para evitar avisos sobre
54   inteiros e ponteiros de tamanhos diferentes.
55
56
57Conceitos básicos
58-----------------
59
60Evitadas as alegrias de escrever uma camada de compatibilidade (compat layer),
61podemos dar uma olhada nos deslizes básicos. Negligenciar estes pontos tornará a
62compatibilidade retroativa e futura uma verdadeira dor de cabeça. E, como errar
63na primeira tentativa é garantido, você certamente terá uma segunda iteração ou,
64pelo menos, uma extensão para qualquer interface fornecida.
65
66 * Tenha uma maneira clara para o espaço de usuário descobrir se a sua nova
67   ioctl ou extensão de ioctl é suportada em um determinado kernel. Se você não
68   puder confiar que os kernels antigos rejeitarão as novas flags/modos ou
69   ioctls (já que fazer isso foi deixado de lado no passado), então você
70   precisará de uma flag de recurso (feature flag) do driver ou de um número de
71   revisão em algum lugar.
72
73 * Tenha um plano para estender as ioctls com novas flags ou novos campos no
74   final da estrutura. O core do drm verifica o tamanho passado para cada
75   chamada de ioctl e preenche com zero (zero-extends) quaisquer divergências
76   entre o kernel e o espaço de usuário. Isso ajuda, mas não é uma solução
77   completa, já que um espaço de usuário mais novo em um kernel mais antigo não
78   notará que os campos recém-adicionados no final estão sendo ignorados.
79   Portanto, isso ainda exige novas flags de recurso do driver.
80
81 * Verifique todos os campos e flags não utilizados, além de todo o preenchimento
82   (padding), para garantir que estejam em 0, e rejeite a ioctl se esse não for
83   o caso. Caso contrário, seu excelente plano para extensões futuras irá por
84   água abaixo, pois alguém enviará uma struct de ioctl com lixo de pilha
85   (stack garbage) aleatório nas partes ainda não utilizadas. O que, então,
86   consolida na ABI que esses campos nunca poderão ser usados para nada além de
87   lixo. Esta também é a razão pela qual você deve preencher explicitamente todas
88   as estruturas, mesmo que nunca as use em uma matriz (array) -- o padding que
89   o compilador possa inserir poderia conter lixo.
90
91 * Tenha casos de teste simples para tudo o que foi mencionado acima.
92
93
94Diversão com caminhos de erro (Error Paths)
95-------------------------------------------
96
97Hoje em dia, não temos mais nenhuma desculpa para que os drivers drm sejam pequenos
98exploits de root disfarçados. Isso significa que precisamos tanto de uma
99validação completa de entrada quanto de caminhos sólidos de tratamento de erros
100-- as GPUs eventualmente vão parar de funcionar (die) nos casos mais bizarros
101de qualquer maneira:
102
103 * A ioctl deve verificar se há estouros de matriz (array overflows). Ela também
104   precisa verificar estouros superiores/inferiores (over/underflows) e problemas
105   de limitação (clamping) de valores inteiros em geral. O exemplo usual são os
106   valores de posicionamento de sprite alimentados diretamente no hardware, onde
107   o hardware possui apenas 12 bits ou algo assim. Funciona perfeitamente até que
108   algum servidor de exibição bizarro não se preocupe em fazer o clamping por si
109   mesmo e o cursor dê a volta (wrap around) na tela.
110
111 * Tenha casos de teste simples para cada caso de falha de validação de entrada
112   na sua ioctl. Verifique se o código de erro corresponde às suas expectativas.
113   E, finalmente, certifique-se de testar apenas um único caminho de erro em
114   cada subteste, enviando dados que, de outra forma, seriam perfeitamente
115   válidos. Sem isso, uma verificação anterior já poderia rejeitar a ioctl e
116   ofuscar (shadow) o caminho de código que você realmente deseja testar,
117   ocultando bugs e regressões.
118
119 * Torne todas as suas ioctls reiniciáveis (restartable). Primeiro, o X (X11)
120   realmente ama sinais (signals) e, segundo, isso permitirá que você teste 90%
121   de todos os caminhos de tratamento de erro apenas interrompendo sua suíte de
122   testes principal constantemente com sinais. Graças ao amor do X por sinais,
123   você obterá uma excelente cobertura de base de todos os seus caminhos de erro
124   praticamente de graça para drivers de gráficos. Além disso, seja consistente
125   na forma como você lida com a reinicialização de ioctls -- por exemplo, o drm
126   possui um pequeno helper drmIoctl em sua biblioteca de espaço de usuário. O
127   driver i915 estragou isso com a ioctl set_tiling; agora estamos presos para
128   sempre com algumas semânticas arcanas tanto no kernel quanto no espaço de
129   usuário.
130
131 * Se você não puder tornar um determinado caminho de código reiniciável, torne
132   uma tarefa travada pelo menos finalizável (killable). As GPUs simplesmente
133   morrem, e seus usuários não vão gostar mais de você se você travar a máquina
134   inteira deles (por meio de um processo do X impossível de matar). Se a
135   recuperação de estado ainda for muito complicada, tenha um timeout ou uma
136   rede de segurança de verificação de travamento (hangcheck) como um esforço de
137   última hora (last-ditch) caso o hardware enlouqueça (gone bananas).
138
139 * Tenha casos de teste para os cenários mais complexos (corner cases) no seu
140   código de recuperação de erros -- é fácil demais criar um deadlock entre seu
141   código de hangcheck e os processos que estão aguardando (waiters).
142
143
144Tempo, Espera e a Perda de Prazos
145---------------------------------
146
147As GPUs fazem quase tudo de forma assíncrona, portanto, temos a necessidade de
148cronometrar operações e aguardar pelas que estão pendentes. Esse é um negócio
149realmente complicado; no momento, nenhuma das ioctls suportadas pelo drm/i915
150acerta isso completamente, o que significa que ainda há toneladas de lições para
151aprender aqui.
152
153 * Use CLOCK_MONOTONIC como seu tempo de referência, sempre. É o que o alsa, o
154   drm e o v4l usam por padrão hoje em dia. Mas informe ao espaço de usuário
155   quais carimbos de data/hora (timestamps) são derivados de domínios de relógio
156   diferentes, como o relógio principal do seu sistema (fornecido pelo kernel)
157   ou algum contador de hardware independente em outro lugar. Os relógios vão
158   divergir se você olhar de perto o suficiente, mas se as ferramentas de
159   medição de desempenho tiverem essa informação, elas poderão ao menos compensar.
160   Se o seu espaço de usuário puder obter os valores brutos de alguns relógios
161   (por exemplo, por meio de instruções de amostragem de contador de desempenho
162   no fluxo de comandos), considere expor esses também.
163
164 * Use __s64 para segundos mais __u64 para nanossegundos para especificar o
165   tempo. Não é a especificação de tempo mais conveniente, mas é praticamente o
166   padrão.
167
168 * Verifique se os valores de tempo de entrada estão normalizados e rejeite-os
169   caso contrário. Note que a struct nativa do kernel, ktime, possui um inteiro
170   sinalizado tanto para segundos quanto para nanossegundos, portanto, cuidado
171   aqui.
172
173 * Para timeouts, use tempos absolutos. Se você for um bom sujeito e tiver
174   tornado a sua ioctl reiniciável, os timeouts relativos tendem a ser muito
175   imprecisos (coarse) e podem estender indefinidamente o seu tempo de espera
176   devido ao arredondamento a cada reinicialização. Especialmente se o seu relógio
177   de referência for algo realmente lento, como o contador de quadros da tela
178   (display frame counter). Vestindo o chapéu de advogado de especificações, isso
179   não é um bug, já que os timeouts sempre podem ser estendidos -- mas os usuários
180   com certeza vão odiar você se as belas animações deles começarem a gaguejar
181   (stutter) devido a isso.
182
183 * Considere descartar quaisquer ioctls de espera síncrona com timeouts e apenas
184   entregue um evento assíncrono em um descritor de arquivo passível de poll
185   (pollable file descriptor). Isso se encaixa muito melhor no loop principal de
186   aplicações orientadas a eventos.
187
188 * Tenha casos de teste para cenários complexos (corner-cases), especialmente se
189   os valores de retorno para eventos já concluídos, esperas bem-sucedidas e
190   esperas que estouraram o tempo (timed-out) são todos sãos e adequados às suas
191   necessidades.
192
193
194Evitando o vazamento de recursos (Leaking Resources, Not)
195---------------------------------------------------------
196
197Um driver drm completo essencialmente implementa um pequeno SO, mas especializado
198para as plataformas de GPU fornecidas. Isso significa que um driver precisa
199expor toneladas de handles (identificadores) para diferentes objetos e outros
200recursos para o espaço de usuário. Fazer isso corretamente traz seu próprio
201pequeno conjunto de armadilhas:
202
203 * Sempre vincule o tempo de vida (lifetime) de seus recursos criados
204   dinamicamente ao tempo de vida de um descritor de arquivo (file descriptor -
205   fd). Considere usar um mapeamento 1:1 se o seu recurso precisar ser
206   compartilhado entre processos -- a passagem de fds sobre unix domain sockets
207   também simplifica o gerenciamento do tempo de vida para o espaço de usuário.
208
209 * Sempre tenha suporte a O_CLOEXEC.
210
211 * Certifique-se de que você tem isolamento suficiente entre os diferentes
212   clientes. Por padrão, escolha um namespace privado por fd, o que força
213   qualquer compartilhamento a ser feito de forma explícita. Só adote um
214   namespace mais global por dispositivo se os objetos forem verdadeiramente
215   únicos do dispositivo. Um contraexemplo nas interfaces de modeset do drm é
216   que os objetos de modeset por dispositivo, como conectores, compartilham um
217   namespace com objetos de framebuffer, que na maioria das vezes não são
218   compartilhados de forma alguma. Um namespace separado, privado por padrão,
219   para os framebuffers teria sido mais adequado.
220
221 * Pense sobre os requisitos de unicidade para os handles do espaço de usuário.
222   Por exemplo, para a maioria dos drivers drm, é um bug do espaço de usuário
223   enviar o mesmo objeto duas vezes na mesma ioctl de envio de comando. Mas,
224   se os objetos forem compartilháveis, o espaço de usuário precisa saber se
225   já viu um objeto importado de outro processo ou não. Eu ainda não tentei isso
226   sozinho devido à falta de uma nova classe de objetos, mas considere usar
227   números de inode em seus descritores de arquivo compartilhados como
228   identificadores únicos -- é assim que arquivos reais também são diferenciados.
229   Infelizmente, isso requer um sistema de arquivos virtual completo no kernel.
230
231
232Por último, mas não menos importante
233------------------------------------
234
235Nem todo problema precisa de uma nova ioctl:
236
237 * Pense bem se você realmente quer uma interface privada do driver. Claro que
238   é muito mais rápido aprovar uma interface privada do driver do que se envolver
239   em discussões longas por uma solução mais genérica. E, ocasionalmente, criar
240   uma interface privada para liderar um novo conceito é o que se exige. Mas,
241   no final, assim que a interface genérica surgir, você acabará mantendo duas
242   interfaces. Indefinidamente.
243
244 * Considere outras interfaces além de ioctls. Um atributo sysfs é muito melhor
245   para configurações por dispositivo ou para objetos filhos com tempos de vida
246   razoavelmente estáticos (como conectores de saída no drm com todos os seus
247   atributos de sobreposição de detecção). Ou talvez apenas a sua suíte de
248   testes precise dessa interface e, nesse caso, o debugfs, com seu aviso de
249   isenção de responsabilidade por não ter uma ABI estável, seria melhor.
250
251Finalmente, o objetivo principal é acertar na primeira tentativa, pois se o seu
252driver se provar popular e suas plataformas de hardware forem duradouras, você
253ficará preso a uma determinada ioctl essencialmente para sempre. Você pode
254tentar depreciar ioctls horríveis em iterações mais novas do seu hardware, mas
255geralmente leva anos para conseguir isso. E depois mais anos até que o último
256usuário capaz de reclamar sobre regressões desapareça também.