LED piscando no módulo do kernel do Linux

Olá pessoal. Neste artigo, quero compartilhar a experiência de criar um módulo simples do kernel Linux. Este artigo será útil para aqueles que desejam entender como escrever módulos do kernel, mas não sabem por onde começar.

Há muito que eu queria entender esse tópico, mas até recentemente não sabia como abordá-lo. Queria que o módulo fosse simples o suficiente, mas mais complicado que a mensagem "Olá, mundo!" saída em um arquivo de log. No final, eu decidi tentar piscar um LED. Um objetivo adicional era deduzir o parâmetro responsável pela frequência de piscar no sysfs.

Para o experimento, usei a placa Orange Pi One com o Ubuntu Linux (versão do kernel 3.4.113). Para construir o módulo do kernel, você precisa do compilador gcc, do utilitário make e dos arquivos de cabeçalho do kernel. Para instalar os arquivos de cabeçalho, execute o seguinte comando:

sudo apt-get install linux-headers-$(uname -r) 

A seguir, analisarei em minha opinião as partes mais interessantes do módulo. Para economizar espaço, não fornecerei todo o código aqui, ele está disponível no github junto com o arquivo make.

No módulo, usei os arquivos de cabeçalho:

 #include <linux/kernel.h> #include <linux/module.h> #include <linux/gpio.h> #include <linux/hrtimer.h> #include <linux/moduleparam.h> 

kernel.he module.h sempre devem ser ativados ao escrever o módulo do kernel, gpio.h é realmente responsável por trabalhar com o GPIO, hrtimer.h (timer de alta resolução) é o arquivo de cabeçalho do timer, moduleparam.h é necessário para exibir os parâmetros no sysfs.

Para não exibir suas variáveis ​​e funções no kernel do sistema, todas elas devem ser descritas como estáticas. Apenas no caso, observo que o kernel é escrito em C e estático, ao contrário do C ++, significa que o objeto está disponível apenas dentro do arquivo executável.

O ponto de entrada é:

 static int blink_module_init(void) 

Aqui inicializo as variáveis ​​que usarei no futuro, incluindo:

 gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0); 

O ktime_set inicializa o tipo de dados ktime_t, fornecendo o número desejado de segundos (gpio_blink_interval_s) e nanossegundos (0). No futuro, essa variável será usada pelo timer.

A seguir, uma solicitação para usar o GPIO:

 err = gpio_request(BLINK_PIN_NR, "blink_led"); 

Esta função retorna 0 se for bem-sucedida, portanto, no futuro, verifico se ela retornou. Em seguida, o pino selecionado deve ser definido para a saída do sinal e especificar o valor padrão.

 err = gpio_direction_output(BLINK_PIN_NR, GPIOF_INIT_LOW); 

Se não houver erros, inicialize e inicie o cronômetro

 hrtimer_init(&gpio_blink_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); gpio_blink_timer.function = &gpio_blink_timer_callback; hrtimer_start(&gpio_blink_timer, gpio_timer_interval, HRTIMER_MODE_REL); 

A função de retorno de chamada do temporizador será gpio_blink_timer_callback. Nesta função, altero o valor do pino para o oposto

 gpio_value ^= 0x01; gpio_set_value(BLINK_PIN_NR, gpio_value); 

Pergunto quando o cronômetro deve funcionar na próxima vez.

 hrtimer_forward_now(&gpio_blink_timer, gpio_timer_interval); 

e retorne HRTIMER_RESTART.

Agora, vejamos como mostrar algum tipo de variável no sysfs. Para isso eu uso uma macro

 module_param_cb(gpio_blink_interval_s, &kp_ops, &gpio_blink_interval_s, 0660); 

O primeiro parâmetro dessa macro é o nome do arquivo em sysfs. O segundo é uma estrutura de dados que contém funções de retorno de chamada. O terceiro parâmetro é um ponteiro para a variável real e as permissões do quarto arquivo no sysfs.

As funções do kp_ops são chamadas quando o usuário altera os valores do arquivo sysfs ou lê seu valor. Veja como eu os inicializei:

 static const struct kernel_param_ops kp_ops = { .set = &set_blink_interval, .get = &get_blink_interval }; 

Nesse caso, set é de interesse, pois define um novo valor, gpio_timer_interval.

 gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0); 

No ponto de saída, limpo todos os recursos usados

 static void blink_module_exit(void) { hrtimer_cancel(&gpio_blink_timer); gpio_set_value(BLINK_PIN_NR, 0); gpio_free(BLINK_PIN_NR); printk(KERN_ALERT "Blink module unloaded\n"); } 

Os pontos de entrada e saída devem ser indicados nas macros correspondentes

 module_init(blink_module_init); module_exit(blink_module_exit); 

Parece descrever todos os pontos importantes. Se os leitores tiverem alguma dúvida, terei prazer em respondê-las nos comentários.

Source: https://habr.com/ru/post/pt411721/


All Articles