Controlador ARM/Resumen 1

Controlador de dispositivo Linux

Controlador: el código de software que puede controlar el hardware para implementar funciones específicas es el controlador.


¿Cuál es la diferencia entre el controlador ARM bare metal y el controlador?

       El controlador ARM bare metal es un código de software que no se basa en el sistema operativo y, por lo general, los desarrolladores escriben este código de forma independiente.

Los controladores son códigos de software basados ​​en la arquitectura del kernel (Linux). No solo pueden operar el hardware subyacente, sino que también necesitan interactuar con el kernel de Linux. Por lo general, Linux es el código que ha sido escrito por el desarrollador del kernel y solo por el usuario. Necesita escribir el controlador.


1. módulo del kernel de Linux

1. Tres elementos del módulo del kernel:

        Entrada: Trabajo de aplicación de recursos, ejecutado durante la instalación del controlador.

        Salir: trabajo de liberación de recursos, ejecutado cuando se desinstala el controlador de escritura

        Licencia: El conductor debe cumplir con el acuerdo GPL (código abierto)

  2. Cómo escribir el módulo del kernel: (bloque de código)
//内核的头文件在内核源码目录下通过vi -t xx查找
//ctags -R 创建索引   
//或者在内核目录下 make tags
#include <linux/init.h>
#include <linux/module.h>

//1.入口
static int __init demo_init(void)
{
    //static:修饰的函数只能在当前文件中使用
    //int:这个函数的返回值类型

    //__init:#define __init  __section(".init.text") 
    //vmlinux.lds内核的链接脚本,.init.text这是脚本中的一个段
    //内核源码--->vmlinux.lds--->uImage
    //将驱动的入口函数都放在.init.text段中

    //demo_init:入口函数的名字 led_init uart_init...
    //(void) :入口函数没有参数
    return 0;
}
//2.出口
static void __exit demo_exit(void)
{
    //void类型没有返回值
    //__exit告诉编译器将demo_exit函数放在.exit.text段中
}
module_init(demo_init);
//module_init是内核提供的宏
//告诉内核驱动的入口函数的地址
module_exit(demo_exit);
//告诉内核驱动的出口函数的地址

//3.许可证
MODULE_LICENSE("GPL"); //遵从开源协议
 3. Cómo compilar el módulo del kernel:

        El módulo del kernel no se puede compilar directamente usando gcc al compilar, debería deberse a que el código del controlador tiene un código fuente que depende del kernel.

Por lo tanto, la compilación del controlador depende del kernel y se debe utilizar un Makefile para permitir que el controlador compile el controlador.

1. Compilación interna: compilar en el árbol de fuentes del kernel

   Kconfig .config Archivo MAKE

 2. Compilación externa: compile fuera del árbol de fuentes del kernel (escriba su propio Makefile general)

     make arch=arquitectura modname=nombre del módulo

arch ?=arm
modname ?=demo

ifeq ($(arch),arm)
#KERNELDIR:指向内核目录的一个变量
	KERNELDIR:= /home/ubuntu/linux-5.10.61               #开发板上可安装的arm格式
else
	KERNELDIR := /lib/modules/$(shell uname -r)/build/   #ubuntu可以安装的x86-64格式
endif

PWD :=$(shell pwd)
#当前路径$(shell pwd)在Makefile的时候起一个终端
#这个终端上执行pwd,将这个命令的结果赋值给PWD变量

all:
	make -C $(KERNELDIR) M=$(PWD) modules
#make -C $(KERNELDIR)
#进入到内核顶层目录下,读取这个目录下的Makefile文件,然后执行make
# M=$(PWD) :指定编译模块的路径为当前路径
# make modules 模块化编译
#进入内核顶层目录下读取Makefile文件,然后进行模块化编译
#通过M指定编译的目录在当前目录
clean:
	make -C $(KERNELDIR) M=$(PWD) clean
#清除编译
obj-m:=$(modname).o
#指定编译的当前目录的的模块名是$(modname)===>$(modname).ko
 4. Instalación y desinstalación de módulos del kernel.

sudo insmod xxx.ko  //Instalar módulo del kernel

lsmod  //Ver módulos del kernel

sudo rmmod xxx  //Desinstalar el módulo del controlador

 5. Uso de declaraciones impresas en el módulo del kernel printk

5.1 Formato de uso de printk:

printk(Nivel de impresión "formato de control", lista de parámetros); //Especifica el nivel del mensaje

printk("Formato de control", lista de parámetros); //Nivel predeterminado de mensaje

Nota: El uso de printk y printf en el kernel es el mismo excepto por el nivel de impresión, que se utiliza para filtrar la información de impresión.

5.2Método de información del filtro printk:

Los mensajes tienen niveles de mensaje y los terminales también tienen niveles de terminal. Solo cuando el nivel del mensaje es mayor que el nivel del terminal, el mensaje se mostrará en el terminal.

gato /proc/sys/kernel/printk

#define KERN_EMERG   "0"    /* system is unusable */
#define KERN_ALERT   "1"    /* action must be taken immediately */
#define KERN_CRIT    "2"    /* critical conditions */                                
#define KERN_ERR     "3"    /* error conditions */
#define KERN_WARNING     "4"    /* warning conditions */
#define KERN_NOTICE  "5"    /* normal but significant condition */
#define KERN_INFO    "6"    /* informational */
#define KERN_DEBUG   "7"    /* debug-level messages */
 6. Modificar el nivel predeterminado de mensajes.

 1. Modificar el nivel de impresión predeterminado de ubuntu

        su raíz

        eco 4 3 1 7 > /proc/sys/kernel/printk

2. Modificar el nivel de impresión predeterminado de la placa de desarrollo.

        rootfs/etc/init.d/rcS

        Agregue --->echo 4 3 1 7 > /proc/sys/kernel/printk a la última línea de este script

7. Puede ver la información de impresión mediante comandos (visualización activa)

dmesg  //Ver toda la información impresa desde el kernel hasta el momento actual

sudo dmesg -C o -c  //Borrar información de impresión (-C borra directamente, -c hace eco primero y luego borra)

Nota: Si es blanco, significa que el nivel de mensaje es inferior al nivel del terminal. Si es rojo, significa que el nivel de mensaje es superior al nivel del terminal.

8. Paso de parámetros del módulo kernel

 sudo insmod demo.si a=10 b=20

El fenómeno visto a través de modinfo xxx.ko es: parm: ih: este es el rango de var de retroiluminación [0-255] (int)

#include <linux/init.h>
#include <linux/module.h>

int a=20;
module_param(a,int,0664); // module_param(name, type, perm) 用来接收命令行传递过来的参数
                          //@name:变量名   @type:变量的类型   @perm:权限 (最大的权限是0664)
                    /*当这里的权限不是0的时候,它会在/sys/module/驱动名字目录/parameters/
        目录下产生产生一个以name命名的文件,这个权限就是修饰这个文件权限的*/

//1.入口
static int __init demo_init(void)
{
    printk(KERN_ERR "hello DC21121 everyone!!!\n");
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    printk("a = %d\n",a);
    return 0;
}
//2.出口
static void __exit demo_exit(void)
{
    printk(KERN_ERR "bye DC21121 everyone!!!\n");
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL"); //遵从开源协议

Aviso: 

1. Pasar miembros de tipo char <=== No se pueden pasar tipos de caracteres, solo se pueden pasar números enteros

2. Pasar miembros de tipo corto.

3. Pase el miembro del puntero de cadena (char *) <==== No puede haber espacios en la cadena

sudo insmod 01module_param.ko ch=97 sh=200 ih=900 sp=hola a todos

#include <linux/init.h>
#include <linux/module.h>

char ch='A';
module_param(ch,byte,0664);

short sh=123;
module_param(sh,short,0664);

int ih=2222;
module_param(ih,int,0664);

char *sp = "hell world";
module_param(sp,charp,0664);
//1.入口
static int __init demo_init(void)
{
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    printk("ch = %d\n",ch);
    printk("sh = %d\n",sh);
    printk("ih = %d\n",ih);
    printk("sp = %s\n",sp);
    return 0;
}
//2.出口
static void __exit demo_exit(void)
{
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    printk("ch = %d\n",ch);
    printk("sh = %d\n",sh);
    printk("ih = %d\n",ih);
    printk("sp = %s\n",sp);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL"); //遵从开源协议

Ver las variables que se pueden pasar como parámetros en el módulo

modinfo 01module_param.ko

2. Controlador de dispositivo de caracteres

1.Arquitectura del controlador del dispositivo de caracteres

2. API relacionadas con el controlador de dispositivo de caracteres

Controlador de dispositivo de escritura de caracteres: 

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>

#define CNAME "mycdev"
int major;

int mycdev_open(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
ssize_t mycdev_read(struct file *file, 
 char __user *ubuf, size_t size, loff_t *offs)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
ssize_t mycdev_write(struct file *file, 
 const char __user *ubuf, size_t size, loff_t *off)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
const struct file_operations fops = {
 .open = mycdev_open,
 .read = mycdev_read,
 .write = mycdev_write,
 .release = mycdev_close,
};
static int __init mycdev_init(void)
{
 //1.注册字符设备驱动
 major = register_chrdev(0,CNAME,&fops);
 if(major < 0){
  printk("register char device driver error\n");
  return major;
 }
 printk("register char device driver success... major = %d\n",major);
 return 0;
}
static void __exit mycdev_exit(void)
{
 //2.注销字符设备驱动
 unregister_chrdev(major,CNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

 3. Proceso de prueba del controlador del dispositivo de caracteres

1. Compile el controlador

        Copie el archivo fuente del controlador de Windows a Ubuntu y luego use Makefile para compilarlo

        hacer arch=x86 modname=mycdev

2. Instale el controlador

        sudo insmod mycdev.ko

        dmesg ====>Información impresa en la función de entrada

        cat /proc/devices ====>Número de dispositivo principal y nombre del dispositivo

3. Cree un nodo de dispositivo para el controlador.

        sudo mknod /dev/hola c 240 0

        mknod: comando para crear nodos

        /dev/hello: La ruta y el nombre del nodo del dispositivo (cualquier ruta es aceptable, se acostumbra colocarla en /dev/)

        c/b: dispositivo de caracteres c dispositivo de bloque b

        240: número de dispositivo principal

        0: número de dispositivo secundario (cualquiera entre 0 y 255 es aceptable)

Nota: El nodo del dispositivo es un archivo al que la aplicación accede al controlador.

 4. Transferencia de datos en el espacio del usuario y en el espacio del kernel

 API de función de copia de datos

5. Controlador LED de escritura: basado en el microcontrolador STM32MP157

mapeo de direcciones

La operación del LED de la aplicación se completa operando el registro, la dirección del registro es la dirección física y el controlador se ejecuta en la memoria virtual 3-4G, por lo que si desea operar el registro LED en el kernel, asigne la dirección del registro LED al espacio del kernel.

        El proceso de asignación y desasignación de direcciones se puede completar a través de ioremap/iounmap.

 

myled.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include "myled.h"

#define CNAME "myled"
int major;
char kbuf[128] = {0};
unsigned int *virt_moder;
unsigned int *virt_odr;
unsigned int *virt_rcc;
int myled_open(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
ssize_t myled_read(struct file *file, 
 char __user *ubuf, size_t size, loff_t *offs)
{
 int ret;
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

 //如果用户想写的大小大于内核的内存大小,更正用户写的大小
 if(size > sizeof(kbuf)) size=sizeof(kbuf);
 ret = copy_to_user(ubuf,kbuf,size);
 if(ret){ //成功返回0,失败返回未拷贝的字节的个数
  printk("copy data to user error\n");
  return -EIO; //失败返回错误码
 }
 return size; //成功返回拷贝的字节的个数
}
ssize_t myled_write(struct file *file, 
const char __user *ubuf, size_t size, loff_t *off)
{
 int ret;
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

 //如果用户想写的大小大于内核的内存大小,更正用户写的大小
 if(size > sizeof(kbuf)) size=sizeof(kbuf);
 ret = copy_from_user(kbuf, ubuf, size);
 if(ret){ //成功返回0,失败返回未拷贝的字节的个数
  printk("copy data from user error\n");
  return -EIO; //失败返回错误码
 }
 printk("kernel data = %s\n",kbuf);
 
 if(kbuf[0]=='1'){
  *virt_odr |=(1<<10);   //high
 }else if(kbuf[0]=='0'){
  *virt_odr &=~(1<<10);   //low
 }
 return size; //成功返回拷贝的字节的个数
}
int myled_close(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__); 
 return 0;
}
const struct file_operations fops = {
 .open = myled_open,
 .read = myled_read,
 .write = myled_write,
 .release = myled_close,
};
static int __init myled_init(void)
{
 //1.注册字符设备驱动
 major = register_chrdev(0,CNAME,&fops);
 if(major < 0){
  printk("register char device driver error\n");
  return major;
 }
 printk("register myled driver success... major = %d\n",major);

 //2.映射LED1的地址,并将LED1初始化为熄灭
 virt_moder = ioremap(PHY_LED1_MODER,4);
 if(virt_moder == NULL){
  printk("ioremap moder addr error\n");
  return -ENOMEM;
 }
 virt_odr = ioremap(PHY_LED1_ODR,4);
 if(virt_odr == NULL){
  printk("ioremap odr addr error\n");
  return -ENOMEM;
 }
 virt_rcc = ioremap(PHY_LED1_RCC,4);
 if(virt_rcc == NULL){
  printk("ioremap rcc addr error\n");
  return -ENOMEM;
 }
 *virt_rcc |= (1<<4);  //rcc enable
 *virt_moder &=~(3<<20);
 *virt_moder |=(1<<20);  //output
 *virt_odr &=~(1<<10);   //low
 return 0;
}
static void __exit myled_exit(void)
{
 iounmap(virt_rcc);
 iounmap(virt_odr);
 iounmap(virt_moder);
 //2.注销字符设备驱动
 unregister_chrdev(major,CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

myled.h 

#ifndef __MYLED_H__
#define __MYLED_H__
#define PHY_LED1_MODER 0x50006000
#define PHY_LED1_ODR   0x50006014
#define PHY_LED1_RCC    0x50000a28
#endif

prueba.c 

#include <head.h>  // arm-linux-gnueabihf-gcc test.c -I /usr/include 
int main(int argc, const char *argv[])
{
    int fd;
    char buf[128] = {0};
    if ((fd = open("/dev/myled", O_RDWR)) == -1)
    {
        perror("open error");
        exit(EXIT_FAILURE);
    }
    while (1)
    {
        printf("input 0(off),1(on) > ");
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = '\0';
        write(fd, buf, sizeof(buf));
    }
    close(fd);
    return 0;
}

 3. Controlador de dispositivo de caracteres: mecanismo udev

1. Mecanismo para crear nodos de dispositivos.

  • mknod: comando para crear nodos de dispositivos
  • devfs: es el mecanismo para crear nodos de dispositivos en las primeras etapas de Linux. La lógica para crear nodos de dispositivos está en el espacio del kernel (antes de la versión 2.4)
  • udev: La lógica de creación de nodos de dispositivos está en el espacio del usuario, desde la versión 2.6 del kernel hasta la actualidad.
  • mdev: como versión ligera del mecanismo uudev, se utiliza a menudo en algunos chips integrados.

 2. El principio de creación de nodos de dispositivos mediante el mecanismo udev.

1. El usuario envía el directorio hacia arriba en el controlador del dispositivo, y el kernel solicitará un espacio de tipo de clase estructura, que almacena la información del directorio. El kernel creará un directorio en /sys/class/

2. Enviamos la información del nodo hacia arriba en el controlador del dispositivo y solicitamos un espacio de tipo de dispositivo de estructura en el kernel, que almacena la información del nodo que enviamos. El kernel creará un espacio para almacenar la información del nodo en /sys/class/ directorio/documento

3. Después de completar los dos pasos anteriores, hotplug notificará a udev que cree un archivo de dispositivo en /dev basado en la información en /sys/class/directory/file que almacena la información del nodo.

 3. Cree API relacionadas con nodos de dispositivos

 1.#incluir<linux/device.h>

clase de estructura * class_create(módulo de estructura *propietario, carácter constante *nombre)

Función: enviar información del directorio hacia arriba (solicitar un espacio variable de clase de estructura en el kernel e inicializarlo)

parámetro:

        propietario : un puntero a un espacio de tipo módulo de estructura, complete THIS_MODULE (puntero al módulo actual)

        nombre: nombre del directorio Valor de retorno: devuelve con éxito el puntero del espacio de clases de estructura con una aplicación exitosa

         El error devuelve un puntero de error

        // El espacio de 4K está reservado en el nivel superior del kernel. Cuando falla la llamada a la función class_create, el valor de retorno es un puntero al espacio reservado de 4K bool __must_check IS_ERR(__force const void *ptr)

        Función: se utiliza para determinar si el puntero apunta al espacio 4K reservado en el nivel superior del kernel.

        Valor de retorno: devuelve verdadero si el espacio está reservado, falso si no hay espacio reservado.

         largo __must_check PTR_ERR(__force const void *ptr)

        Función: obtener el valor absoluto de un código de error según el puntero del código de error

2.void class_destroy(struct class *cls);

Función: destruir el espacio de clases de estructura aplicado

Parámetros: cls: la primera dirección del espacio solicitado por cls_create

Valor de retorno: Ninguno

3. dispositivo de estructura *device_create (clase de estructura *clase, dispositivo de estructura *padre, dev_t devt, void *drvdata, const char *fmt, ...)

Función: enviar información del nodo del dispositivo hacia arriba (solicite un espacio de tipo de dispositivo de estructura)

Parámetros: clase: primera dirección del espacio solicitado por class_create

padre: especifique el puntero del nodo principal del espacio del dispositivo de estructura aplicado actualmente, complete NULL

devt: número de dispositivo del controlador de dispositivo

        MKDEV(mayor,menor)//Obtenga el número de dispositivo según el número de dispositivo mayor y el número de dispositivo menor

        MAJOR(dev)//Obtiene el número de dispositivo principal según el número de dispositivo

        MINOR(dev)//Obtenga el número de dispositivo menor según el número de dispositivo

drvdata: datos privados completados en el espacio del dispositivo de estructura aplicado, simplemente complete NULL

fmt: nombre del nodo del dispositivo

Valor de retorno: la primera dirección del espacio del dispositivo de estructura aplicado se devuelve correctamente y el puntero de error se devuelve si falla.

4.void device_destroy(clase de estructura *clase, dev_t devt)

Función: destruir el espacio del dispositivo de estructura aplicado

Parámetros: class:class_create la primera dirección del espacio solicitado

devt: número de dispositivo

Valor de retorno: Ninguno

 Cómo determinar el código de error:

 Nota: uso de múltiples declaraciones de macro

#include <head.h>

int max;
#define MAX(a, b)    \
    do               \
    {                \
        if (a > b)   \
            max = a; \
        else         \
            max = b; \
    } while (0)
//do{}while(0) :可以有多条语句,但是乜有返回值,即使有return也不是宏的返回值

#define MAXX(a, b) ({int tt;if(a>b)tt=a;else tt=b; tt; })
//(())宏:可以有多条语句,最后一句话的结果就是宏的返回值
int main(int argc, const char *argv[])
{
    MAX(100, 200);
    printf("max = %d\n", max);
    printf("max = %d\n",MAXX(1000,200));
    return 0;
}

 Crear automáticamente instancias de nodos de dispositivos

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#include "myled.h"

#define CNAME "myled"
#define LED1_ON   (virt_led1->ODR |=(1<<10)) 
#define LED1_OFF  (virt_led1->ODR &=~(1<<10)) 
#define LED2_ON   (virt_led2->ODR |=(1<<10)) 
#define LED2_OFF  (virt_led2->ODR &=~(1<<10)) 
#define LED3_ON   (virt_led3->ODR |=(1<<8)) 
#define LED3_OFF  (virt_led3->ODR &=~(1<<8)) 

int major;
char kbuf[2] = {0};
gpio_t *virt_led1;
gpio_t *virt_led2;
gpio_t *virt_led3;
unsigned int *virt_rcc;

struct class *cls;
struct device *dev;
int myled_open(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
ssize_t myled_read(struct file *file, 
 char __user *ubuf, size_t size, loff_t *offs)
{
 int ret;
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

 if(size > sizeof(kbuf)) size=sizeof(kbuf);
 ret = copy_to_user(ubuf,kbuf,size);
 if(ret){ 
  printk("copy data to user error\n");
  return -EIO; 
 }
 return size;
}

//kbuf[2] = {which,status};
//0 led1   1  led2   2  led3
//0 on 1 off
ssize_t myled_write(struct file *file, 
 const char __user *ubuf, size_t size, loff_t *off)
{
 int ret;
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

 if(size > sizeof(kbuf)) size=sizeof(kbuf);
 ret = copy_from_user(kbuf, ubuf, size);
 if(ret){ 
  printk("copy data from user error\n");
  return -EIO; 
 }
 switch(kbuf[0]){ //kbuf[0] which
  case LED1:
   //kbuf[1] status
   kbuf[1]==1?LED1_ON:LED1_OFF;
   break;
  case LED2:
   kbuf[1]==1?LED2_ON:LED2_OFF;
   break;
  case LED3:
   kbuf[1]==1?LED3_ON:LED3_OFF;
   break;
  default:
   printk("input arg error,try again\n");
   return -EINVAL;
 }
 return size; 
}
int myled_close(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__); 
 return 0;
}
const struct file_operations fops = {
 .open = myled_open,
 .read = myled_read,
 .write = myled_write,
 .release = myled_close,
};

int all_led_init(void)
{
 virt_led1 = ioremap(PHY_LED1_ADDR,sizeof(gpio_t));
 if(virt_led1 == NULL){
  printk("ioremap led1 addr error\n");
  return -ENOMEM;
 }
 virt_led2 = ioremap(PHY_LED2_ADDR,sizeof(gpio_t));
 if(virt_led2 == NULL){
  printk("ioremap led2 addr error\n");
  return -ENOMEM;
 }
 virt_led3 = virt_led1;

 virt_rcc = ioremap(PHY_RCC_ADDR,4);
 if(virt_rcc == NULL){
  printk("ioremap rcc addr error\n");
  return -ENOMEM;
 }
 *virt_rcc |= (3<<4); // rcc gpioe gpiof enable

 //init led1
 virt_led1->MODER &=~(3<<20);
 virt_led1->MODER |=(1<<20);  //output
 virt_led1->ODR   &=~(1<<10); //led1 off
 //init led2
 virt_led2->MODER &=~(3<<20);
 virt_led2->MODER |=(1<<20);  //output
 virt_led2->ODR   &=~(1<<10); //led2 off
 //init led3
 virt_led3->MODER &=~(3<<16);
 virt_led3->MODER |=(1<<16);  //output
 virt_led3->ODR   &=~(1<<8); //led3 off

 return 0;
}
static int __init myled_init(void)
{
 //1.注册字符设备驱动
 major = register_chrdev(0,CNAME,&fops);
 if(major < 0){
  printk("register char device driver error\n");
  return major;
 }

 printk("register myled driver success... major = %d\n",major);
 //2.led地址映射及初始化
 all_led_init();

 //3.自动创建设备节点
 cls = class_create(THIS_MODULE,"hello");
 if(IS_ERR(cls)){
  printk("class create error\n");
  return PTR_ERR(cls);
 }
 dev = device_create(cls,NULL,MKDEV(major,0),NULL,"myled");
 if(IS_ERR(dev)){
  printk("device create error\n");
  return PTR_ERR(dev);
 }
 return 0;
}
static void __exit myled_exit(void)
{
 device_destroy(cls,MKDEV(major,0));
 class_destroy(cls);

 iounmap(virt_rcc);
 iounmap(virt_led1);
 iounmap(virt_led2);
 unregister_chrdev(major,CNAME);

}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

4. Implementación interna del controlador de dispositivo de caracteres.

 1.Diagrama del marco del controlador del dispositivo de caracteres

El archivo existe en el sistema de archivos y habrá un número de inodo de identificación. Según esta identificación, se encuentra la estructura struct_inode (para guardar la información del archivo actual). En la estructura struct_inode, hay un puntero de dispositivo de carácter de tipo struct cdev *i_cdev, que apunta al objeto de la unidad actual struct cdev estructura (estructura del objeto del controlador del dispositivo de caracteres)/(el número de dispositivo es la identificación del núcleo del controlador, y también el vínculo entre el controlador del dispositivo y el archivo del dispositivo), y esta estructura almacena el puntero de estructura del método de operación struct file_operatives *ops; luego, basándose en el puntero de estructura del método de operación struct file_operatives *ops, se encuentran los métodos de operación mycdev_open(), mycdev_read(), mycdev_write(), mycdev_close(); el método de operación se vuelve a llamar a mycdev_open (capa de usuario)
———————— ————————
2. Proceso de implementación paso a paso (API) del controlador de dispositivo de caracteres

#incluir <linux/cdev.h>

1. Estructura del controlador del dispositivo de caracteres
    struct cdev {         struct module *owner; //THIS_MODULE         const struct file_operaciones *ops; //Estructura del método de operación         struct list_head list; //Constituir una lista vinculada         dev_t dev; //Número de dispositivo         unsigned int count; / /número de dispositivos     };





2. Asigne el objeto del controlador del dispositivo de caracteres
    struct cdev cdev;
 struct cdev *cdev = cdev_alloc();
 
 struct cdev *cdev_alloc(void) Función: Asignar     parámetros de memoria
    para el puntero de estructura cdev :         @Sin     valor de retorno: Devuelve la estructura cdev con éxito Puntero , devuelve NULL en caso de error 3. Inicialización del objeto del controlador del dispositivo de caracteres  void cdev_init(struct cdev *cdev, const struct file_operations *fops)     Función: Completa la inicialización (parte) de los miembros de la estructura cdev     Parámetros:         @cdev: puntero de estructura de cdev         @fops: Puntero de estructura del método de operación     Valor de retorno: Ninguno int Register_chrdev_region(dev_t from, unsigned count, const char *name)     Función: Número de dispositivo especificado estático     Parámetros:          @from: Número de dispositivo especificado (número de dispositivo principal|número de dispositivo menor, por ejemplo: 241 <<20 |0)          @count: el número de dispositivos (3)          @name: el nombre del controlador del dispositivo cat /proc/devices view



        







    
    





    Valor de retorno: devuelve 0 en caso de éxito, código de error en caso de error
         
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
   const char *name)  
función     : aplica
    dinámicamente          para          el número de dispositivo Valor inicial del número          @count: el número de dispositivos (3)          @name: el nombre del controlador de dispositivo cat /proc/devices ver     valor de retorno: devuelve 0 si tiene éxito, devuelve código de error si falla 4. registrar el controlador de dispositivo de carácter  int cdev_add(struct cdev *p, dev_t dev, unsigned count)  Función: Registrar controlador de dispositivo de caracteres     Parámetros:          @p: puntero de estructura cdev          @dev: número de dispositivo          @count: número de dispositivos     Valor de retorno: devuelve 0 en caso de éxito, devuelve código de error en caso de error ----- --------- ----------------------------------------- --------- ---------------------- 1. Destruya el controlador del dispositivo de caracteres  void cdev_del(struct cdev *p) 






                 










 
    Función: Destruir
    parámetros del controlador del dispositivo de caracteres:
        @p: puntero de estructura cdev
    Valor de retorno: Ninguno
2. Liberar el número de dispositivo
 void unregister_chrdev_region (dev_t from, recuento sin firmar) 
    Función: Liberar el número de dispositivo
    Parámetros:
        @from: Número de dispositivo
        @count: Dispositivo número Número
    Valor de retorno: Ninguno
3. Liberar memoria aplicada dinámicamente      Función
     void kfree(void *p) : Liberar memoria aplicada dinámicamente      Parámetros:          @p: La primera dirección de la estructura cdev      Valor de retorno: Ninguno     



 Ejemplo de implementación del controlador de dispositivo de caracteres paso a paso

 marco básico

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>

//定义cdev的结构体指针变量
struct cdev *cdev;
static int __init mycdev_init(void)
{
    //1.分配对象
    //2.对象的初始化
    //3.申请设备号
    //4.字符设备驱动的注册
    //5.自动创建设备节点
    return 0;
}
static void __exit mycdev_exit(void)
{
    //1.销毁设备节点
    //2.销毁字符设备驱动
    //3.销毁设备号
    //4.释放动态申请的cdev内存

}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

 mycdev.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

#define CNAME "mycdev"
//定义cdev的结构体指针变量
struct cdev *cdev;
#if 0
unsigned int major = 0; //动态申请
#else
unsigned int major = 500; //静态指定
#endif
int minor=0;
const int count=3;
struct class *cls;
struct device *dev;

char kbuf[128] = {0};
int mycdev_open(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
ssize_t mycdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
 int ret;
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

 if(size > sizeof(kbuf)) size=sizeof(kbuf);
 ret = copy_to_user(ubuf,kbuf,size);
 if(ret){ 
  printk("copy data to user error\n");
  return -EIO;
    }
 return size; 
}

ssize_t mycdev_write(struct file *file, 
 const char __user *ubuf, size_t size, loff_t *off)
{
 int ret;
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

 if(size > sizeof(kbuf)) size=sizeof(kbuf);
 ret = copy_from_user(kbuf, ubuf, size);
 if(ret){ 
  printk("copy data from user error\n");
  return -EIO; 
 }
 return size; 
}
int mycdev_close(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__); 
 return 0;
}

const struct file_operations fops = {
 .open = mycdev_open,
 .read = mycdev_read,
 .write = mycdev_write,
 .release = mycdev_close,
};

static int __init mycdev_init(void)
{
    int i,ret;
    dev_t devno;
    //1.分配对象
    cdev = cdev_alloc();
    if(cdev == NULL){
        printk("cdev alloc memory error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    //2.对象的初始化
    cdev_init(cdev,&fops);
    //3.申请设备号
    if(major == 0){
        //动态申请
        ret = alloc_chrdev_region(&devno,minor,count,CNAME);
        if(ret){
            printk("dynamic:alloc device number error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    }else if(major > 0){
        //静态指定
        ret = register_chrdev_region(MKDEV(major,minor),count,CNAME);
        if(ret){
            printk("static:alloc device number error\n");
            goto ERR2;
        }
    }
    //4.字符设备驱动的注册
    ret = cdev_add(cdev,MKDEV(major,minor),count);
    if(ret){
        printk("cdev register error\n");
        goto ERR3;
    }
    //5.自动创建设备节点
    cls = class_create(THIS_MODULE,CNAME);
    if(IS_ERR(cls)){
        printk("class create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for(i=0;i<count;i++){
        dev = device_create(cls,NULL,MKDEV(major,i),NULL,"mycdev%d",i);
        if(IS_ERR(dev)){
            printk("device create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }
    return 0; //!!!!!!!这里的return 0千万不要忘记写!!!!!!!!!!!!!!
ERR5:
    for(--i;i>=0;i--){
        device_destroy(cls,MKDEV(major,i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major,minor),count);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    //1.销毁设备节点
    for(i=0;i<count;i++){
        device_destroy(cls,MKDEV(major,i));
    }
    class_destroy(cls);
    //2.销毁字符设备驱动
     cdev_del(cdev);
    //3.销毁设备号
    unregister_chrdev_region(MKDEV(major,minor),count);
    //4.释放动态申请的cdev内存
     kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

5. Uso de la función ioctl en el sistema Linux

1. Función API de ioctl

 #include <sys/ioctl.h>
int ioctl(int fd, solicitud larga sin firmar, ...);
Función: dispositivo de control
Parámetros:
     @fd; Descriptor de archivo
     @request: Código de comando
     @...: Escribible, Escritura opcional, si completa la dirección,
el valor de retorno: se devuelve 0 si tiene éxito, se devuelve -1 si falla Establezca el código de error usuario
———————————————————————— ————— ————————————————
struct file_operatives kernel
long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long arg)
{     //La solicitud de la capa de aplicación es pasado a cmd     // Capa de aplicación...pasado a arg }


2. Código de comando de la función ioctl

 ====== ==================================
 bits significado
 ====== == ================================
 31-30 00 - sin parámetros: utiliza la macro _IO
    10 - lea: _IOR
    01 - escribir: _IOW
    11 - leer/escribir: _IOWR

 29-16 tamaño de los argumentos

 15-8 caracteres ascii supuestamente
    únicos para cada controlador
 7-0 función #
 ====== ============= ======================

Si desea obtener el código de nombre de 32 bits anterior que expresa una determinada función, debe implementarlo a través de la siguiente macro 

 3. Instancia de ioctl

myled.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#include "myled.h"

#define CNAME "myled"
#define LED1_ON (virt_led1->ODR |= (1 << 10))
#define LED1_OFF (virt_led1->ODR &= ~(1 << 10))
#define LED2_ON (virt_led2->ODR |= (1 << 10))
#define LED2_OFF (virt_led2->ODR &= ~(1 << 10))
#define LED3_ON (virt_led3->ODR |= (1 << 8))
#define LED3_OFF (virt_led3->ODR &= ~(1 << 8))

int major;
char kbuf[2] = {0};
gpio_t *virt_led1;
gpio_t *virt_led2;
gpio_t *virt_led3;
unsigned int *virt_rcc;

struct class *cls;
struct device *dev;
int myled_open(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
 return 0;
}
ssize_t myled_read(struct file *file,
       char __user *ubuf, size_t size, loff_t *offs)
{
 int ret;
 printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);

 if (size > sizeof(kbuf))
  size = sizeof(kbuf);
 ret = copy_to_user(ubuf, kbuf, size);
 if (ret)
 {
  printk("copy data to user error\n");
  return -EIO;
 }

 return size;
}

// kbuf[2] = {which,status};
// 0 led1   1  led2   2  led3
// 0 on 1 off
ssize_t myled_write(struct file *file,
     const char __user *ubuf, size_t size, loff_t *off)
{
 int ret;
 printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);

 if (size > sizeof(kbuf))
  size = sizeof(kbuf);
 ret = copy_from_user(kbuf, ubuf, size);
 if (ret)
 {
  printk("copy data from user error\n");
  return -EIO;
 }

 return size;
}
long myled_ioctl(struct file *file,
     unsigned int cmd, unsigned long arg)
{
 //int which, ret;
 printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
 switch(cmd){
  case LED1ON:
   LED1_ON;
   break;
  case LED1OFF:
   LED1_OFF;
   break;
 }
 // switch (cmd)
 // {
 // case LED_ON:
 // 	ret = copy_from_user(&which, (void *)arg, sizeof(int));
 // 	if (ret)
 // 	{
 // 		printk("copy data from user error\n");
 // 		return -EINVAL;
 // 	}
 // 	switch (which)
 // 	{
 // 	case LED1:
 // 		LED1_ON;
 // 		break;
 // 	case LED2:
 // 		LED2_ON;
 // 		break;
 // 	case LED3:
 // 		LED3_ON;
 // 		break;
 // 	}
 // 	break;
 // case LED_OFF:
 // 	ret = copy_from_user(&which, (void *)arg, sizeof(int));
 // 	if (ret)
 // 	{
 // 		printk("copy data from user error\n");
 // 		return -EINVAL;
 // 	}
 // 	switch (which)
 // 	{
 // 	case LED1:
 // 		LED1_OFF;
 // 		break;
 // 	case LED2:
 // 		LED2_OFF;
 // 		break;
 // 	case LED3:
 // 		LED3_OFF;
 // 		break;
 // 	}
 // 	break;
 // 	break;
 // }

 return 0;
}
int myled_close(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
 return 0;
}

const struct file_operations fops = {
 .open = myled_open,
 .read = myled_read,
 .write = myled_write,
 .unlocked_ioctl = myled_ioctl,
 .release = myled_close,
};

int all_led_init(void)
{
 virt_led1 = ioremap(PHY_LED1_ADDR, sizeof(gpio_t));
 if (virt_led1 == NULL)
 {
  printk("ioremap led1 addr error\n");
  return -ENOMEM;
 }
 virt_led2 = ioremap(PHY_LED2_ADDR, sizeof(gpio_t));
 if (virt_led2 == NULL)
 {
  printk("ioremap led2 addr error\n");
  return -ENOMEM;
 }

 virt_led3 = virt_led1;

 virt_rcc = ioremap(PHY_RCC_ADDR, 4);
 if (virt_rcc == NULL)
 {
  printk("ioremap rcc addr error\n");
  return -ENOMEM;
 }

 *virt_rcc |= (3 << 4); // rcc gpioe gpiof enable

 // init led1
 virt_led1->MODER &= ~(3 << 20);
 virt_led1->MODER |= (1 << 20); // output
 virt_led1->ODR &= ~(1 << 10);  // led1 off
 // init led2
 virt_led2->MODER &= ~(3 << 20);
 virt_led2->MODER |= (1 << 20); // output
 virt_led2->ODR &= ~(1 << 10);  // led2 off
 // init led3
 virt_led3->MODER &= ~(3 << 16);
 virt_led3->MODER |= (1 << 16); // output
 virt_led3->ODR &= ~(1 << 8);   // led3 off

 return 0;
}
static int __init myled_init(void)
{
 // 1.注册字符设备驱动
 major = register_chrdev(0, CNAME, &fops);
 if (major < 0)
 {
  printk("register char device driver error\n");
  return major;
 }

 printk("register myled driver success... major = %d\n", major);
 // 2.led地址映射及初始化
 all_led_init();

 // 3.自动创建设备节点
 cls = class_create(THIS_MODULE, "hello");
 if (IS_ERR(cls))
 {
  printk("class create error\n");
  return PTR_ERR(cls);
 }
 dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "myled");
 if (IS_ERR(dev))
 {
  printk("device create error\n");
  return PTR_ERR(dev);
 }
 return 0;
}
static void __exit myled_exit(void)
{
 device_destroy(cls, MKDEV(major, 0));
 class_destroy(cls);

 iounmap(virt_rcc);
 iounmap(virt_led1);
 iounmap(virt_led2);
 unregister_chrdev(major, CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

 myled.h

#ifndef __MYLED_H__
#define __MYLED_H__

typedef struct{
 volatile unsigned int MODER;
 volatile unsigned int OTYPER;
 volatile unsigned int OSPEEDR;
 volatile unsigned int PUPDR;
 volatile unsigned int IDR;
 volatile unsigned int ODR;
 volatile unsigned int BSRR;  
}gpio_t;

#define PHY_RCC_ADDR  0x50000a28
#define PHY_LED1_ADDR 0x50006000
#define PHY_LED2_ADDR 0x50007000 
#define PHY_LED3_ADDR 0x50006000

enum{
    LED1,
    LED2,
    LED3
};

#define LED_ON  _IOW('a',0,int)
#define LED_OFF  _IOW('a',1,int)

#define LED1ON  _IO('a',3)
#define LED1OFF  _IO('a',4)
#endif

prueba.c

#include <head.h>
#include "myled.h"
int main(int argc, const char *argv[])
{
    int fd;
    int which;
    if ((fd = open("/dev/myled", O_RDWR)) == -1)
    {
        perror("open error");
        exit(EXIT_FAILURE);
    }
    while (1)
    {
        // which = LED1;
        // ioctl(fd, LED_ON, &which);
        // sleep(1);
        // ioctl(fd, LED_OFF, &which);
        // sleep(1);
        ioctl(fd, LED1ON);
        sleep(1);
        ioctl(fd, LED1OFF);
        sleep(1);
    }
    close(fd);
    return 0;
}

 

Supongo que te gusta

Origin blog.csdn.net/weixin_57039874/article/details/131343769
Recomendado
Clasificación