Actual combat! Teach you how to write a Linux driver and write an LED demo that supports the Internet of Things

table of Contents

1. Development environment

2. Preparations:

1. Create a project directory

2. Create output and target directories

3. Header file directory

4. Create the source code src directory

5. Use git to manage your project

Three. Write LED driver

Three. One preparation

Three. Two init implementation

Three. Four exit realization

Three. Five make implementation

Three. Six open implementation

Three. Seven write implementation

Three. Eight read implementation

Three. Nine ioctl implementation

Fourth, write a test app

V. Internet of Things

6. Add support in make

7. Final presentation


1. Development environment

Development board:

Core board: TQ210CoreB
Backplane: TQ210 V4
CPU: s5pv210
Kernel: Linux_kernel_3.0.8

Cross compilation system environment:

Operating system: ubnutu16.04
Compiler: arm-embedsky-linux-gnueabi tool chain 4.4.6

Required knowledge points:

If you are a beginner student who does not understand the schematic diagram and chip manual, please read the tutorial on the circuit principle and chip documentation in this article: a detailed introduction to how to understand the circuit schematic diagram and chip of the STM32 development board Documents and development manuals, and write a test program: light up an LED light
This article also needs to have a certain understanding of the Linux device manager, otherwise you only know to call these functions when developing, but you don’t know what happens in the kernel mode, so I suggest you learn the relevant knowledge points: Linux Embedded Development_Main Device Number and Detailed explanation of minor device numbers , Linux driver development_Detailed explanation of device file system
This article uses GIT to manage the project, a tutorial on GIT: this one about Git is enough
Basic knowledge of kernel development: Linux kernel development_kernel module
High-level applications for bits: advanced applications for bit manipulation in c language
HTTP aspect:
Http response code meaning
The difference between GET and POST under HTTP protocol
The meaning of each field in the HTTP request header
Open source character processing class library: The CharString class is split from the class library in the web server developed by itself
I have detailed explanations of these knowledge points in other articles. If you have a poor foundation in the microcontroller/embedded system, you can take a look.
This tutorial is more similar to GNU/LINUX style, from the directory system to project management, it will be written in a similar GNU/LINUX style
We have to use git to manage even a small demo. This is to deepen everyone's understanding and understanding of git and to prepare for future work.
Please read the above knowledge first before continuing to study this knowledge.

2. Preparations:

Workers must sharpen their tools if they want to do their best. When we develop a project, we need to build a good project system, which is convenient for our future development and makes our project more maintainable.

1. Create a project directory

mkdir moudul && cd moudul

2. Create output and target directories

arch, output

These two directories will be used for the output of target files and the output directory of intermediate files.

Create a new arm directory under the arch directory. Because our board is an arm structure, we create a new directory to store the target files of the arm. This system is derived from the Linux kernel directory system

mkdir arch && mkdir arch/armmkdir output

3. Header file directory

When creating a header file directory, create a directory TQ210_LED in the include directory at the same time, representing the project type, and then create two subdirectories in this directory: device, app, used to store the driver/app header files

mkdir includemkdir include/TQ210_LEDmkdir include/TQ210_LED/devicemkdir include/TQ210_LED/app

4. Create the source code src directory

The directory structure is consistent with the header file directory

mkdir srcmkdir src/TQ210_LEDmkdir src/TQ210_LED/devicemkdir src/TQ210_LED/app

Well, at this point, our engineering system has been established

[email protected]:~/moudul$ tree.├── arch│   └── arm├── include│   └── TQ210_LED│       ├── app│       └── device├── output└── src    └── TQ210_LED        ├── app        └── device

The directory structure distribution is very clear and reasonable. When we add other modules, we only need to create different types of directories under src and include according to this specification.

For other architectures, you only need to create a corresponding architecture directory under arch.

After the directory system is completed, we are using GIT to manage our projects

5. Use git to manage your project

git init

Three. Write LED driver

Create a new .c file in the src/TQ210_LED/device directory and a new .h file in the include/TQ210_LED/device directory

touch src/TQ210_LED/device/TQ210_LED_device.ctouch include/TQ210_LED/device/TQ210_LED_device.h

Then use vim to open the .c file and we can start writing the driver module.

vim src/TQ210_LED/device/TQ210_LED_device.c

Now we open the schematic diagram of the circuit. This is the necessary first step in the development process, because only by looking at the schematic diagram can the structure of the circuit be known.

Find the LED in the schematic and zoom in

It can be known from the schematic diagram that LED1 and LED2 are all connected to the GPC port.

vdd represents the symbol of the internal working voltage of the device, vdd5v means that at least 5 volts is required for the device to work, but we generally don’t care about this, this is generally done by PCB board designers, and the input source has been set. Devices such as resistors control the flow of current, including the current mode used by the board.

Such circuit symbols at the beginning of R are generally resistance names, 1K=1000 ohms, which means that when the voltage flows, it will absorb a voltage of 1000 ohms.

This is an amplifying transistor used to amplify current. The model is S8050, and the symbol is Q. Q1 represents the No. 1 amplifying transistor.

From here, you can see that LED1 and LED2 are connected to port 0 of GPC, and the bits are 3 and 4.

Here I teach you how to find the corresponding flag in the chip manual through the GPC0_4 logo

Let's open the chip manual, first look at the GPIO frame diagram

It consists of two parts: OFF PART (power-off part) and ALILVE PART (live part)

The two are sleep mode and non-sleep mode, which means that this GPIO framework supports sleep mode and non-sleep mode. If it enters sleep mode, the internal clock of the entire frame will stop working, waiting for other interrupts to wake it up, and it will be in sleep mode at the same time The following can guarantee the value of its GPIO internal register.

It is mounted on the APB bus, and APB is not connected to a clock bus such as RCC. Unlike STM32, which is connected to this bus, the APB bus will not work unless it is turned on first. This is because STM32 is low. Design of power consumption.

Then we find the description of the GPC port

It is recommended that you use CTRL+F to search for GPC when opening the PDF. You can quickly find the corresponding instruction page

According to the experience of microcontroller development, we can know that if you want a GPC port to work, you must set it to OUTPUT mode

At the same time, you can see the introduction of the DAT register

DAT description:

When the port is configured as an input port, the corresponding bit is the pin status. When the port is configured as an output port, the pin status is the same as the corresponding bit. When the port is configured as a function pin, an undefined value will be read.

That is to say, when the corresponding port of CON is output, DAT corresponds to the status value, giving 1 means high level, and giving 0 means low level.

In Samsung s5pv210, each GPIO port is composed of CON and DAT. CON is the control state, and DAT is the input and output state.

And we can see that the port address of GPC0CON is: 0xE0200060

The port address of GPC0DAT is: 0XE0200064 with a difference of four bytes.

The size of an int on a 32-bit machine.

Basically, we all know the hardware and address information, so we will officially write the code and start the development.

Three. One preparation

Create a new .c file in your src/device directory

touch src/TQ210_LED/device/TQ210_device_led.c

At the same time creating the header file:

touch include/TQ210_LED/device/TQ210_device_led.c

Then use your favorite editing tool to start writing code!

First include the basic header files in the .c file:

#include <linux/module.h>#include <linux/ioport.h>#include <linux/io.h>#include <linux/platform_device.h>#include <linux/init.h>#include <linux/serial_core.h>#include <linux/serial.h>#include <asm/irq.h>#include <mach/hardware.h>#include <plat/regs-serial.h>#include <mach/regs-gpio.h>#include <asm/uaccess.h>

Basic Information:

//authorMODULE_AUTHOR("Stephen Zhou");MODULE_LICENSE("GPL");

init and exit:

static int __init led_init(void){ } static void __exit led_exit(void){ } module_init(led_init);module_exit(led_exit);

We are adding some basic information:

This information is used to store the name for the device processor, which has been explained in detail in the previous file system details.

First define the name under /dev, this is for udev:

//led one and two name#define LED_ONE_NAME "TQ210_LED_ONE"#define LED_TWO_NAME "TQ210_LED_TWO"

Define the name in the kernel module table and the name of sysfs, this is for the kernel and sysfs file system

//led name in kernel#define LED_KERNEL_NAME         "TQ210_LED"#define LED_SYSFS_CLASS_NAME    "TQ210_LED_CLASS"

The number of leds, this is for our program to see for ourselves

//led device number max#define LED_NUMBER_MAX 2

Writing several functions for bit operations:

//set or get gpic bit value#define SET_GPIC(GPIC_ADDRESS,VALUE,OPE)                                *GPIC_ADDRESS OPE VALUE#define SET_GPIC_STATE(GPIC_ADDRESS,LED1_VALUE,LED2_VALUE,OPE)          *GPIC_ADDRESS OPE (LED1_VALUE | LED2_VALUE)#define GET_GPIC(GPIC_ADDRESS,VALUE)                                    *GPIC_ADDRESS & VALUE //led gpic port#define LED1_GPIC_BIT(VALUE) (VALUE << 12)#define LED2_GPIC_BIT(VALUE) (VALUE << 16)#define LED1_GPIC_DAT_BIT(VALUE) (VALUE << 3)#define LED2_GPIC_DAT_BIT(VALUE) (VALUE << 4)

Property macro:

//device gpic address#define GPIC0_CON_ADDRESS 0xE0200060#define GPIC0_DAT_ADDRESS 0xE0200064#define GPIC_ADDRESS_FORMAT 16 //state#define LED_STATE_ON  "ON"#define LED_STATE_OFF "OFF"

Okay, let's implement the init function

Three. Two init implementation

Before implementing the init function, we apply for several global variables in the .c file to store different attributes:

Store the led name:

char LED_NAME[][256] = {{LED_ONE_NAME},{LED_TWO_NAME}};

Store con and dat addresses:

volatile unsigned long* GPIC0_address = NULL;volatile unsigned long* GPIC0_dat     = NULL;

Storage kernel fd and class (sysfs) fd:

//drive structstatic int                         led_kernel_fd                                 = 0;                           //kernel struct fdstatic struct class*               led_device_file_class                         = NULL;                        //sysfs fdstatic struct device*              led_device_class_son[LED_NUMBER_MAX]          = {NULL};       

In addition, a structure is required:

struct file_operations

This structure is used to store file function pointers, and functions such as write and open are implemented

For this reason, we first define write and open first, and do nothing. We will implement it later:

//openstatic int led_open(struct inode* inode,struct file* file){ } //writestatic ssize_t led_write(struct file* file,const char __user* buf,size_t count,loff_t* ppos){ } //readstatic ssize_t led_read(struct file* file,char __user* buf,size_t count,loff_t* ppos){ } //ioctlstatic long led_ioctl(struct file* file,unsigned int cmd,unsigned long arg){ }

Note that depending on the kernel version, the write and read function prototypes in the kernel part are different. You can check it according to the kernel version used by your own development board.

We define the fops structure:

//drive structstatic struct file_operations led_drive_fops = {                 .owner          = THIS_MODULE,                .open           = led_open,                .write          = led_write,                .read           = led_read,                .unlocked_ioctl = led_ioctl,};

The THIS_MODULE inside is an address. This will be replaced by the compiler with the address of the current module during precompilation, which means it points to the current module.

The purpose of the init function:

Register the device to the kernel and to the file-like system, and register udev to the dev dir

The first step is to register in the kernel structure

Note that we try to log as much as possible in the kernel module, and use the printk function to facilitate our debugging

Because the kernel mode cannot be debugged with GDB.

led_kernel_fd = register_chrdev(0,LED_KERNEL_NAME,&led_drive_fops);        if(led_kernel_fd < 0){                printk("TQ210_LED[ERROR]: register_chrdev - %d\n",led_kernel_fd);                return -1;}

Step 2 Register to sysfs/sys/class dir and apply for a parent node

led_device_file_class = class_create(THIS_MODULE,LED_SYSFS_CLASS_NAME);

The third step is to register the child device to the class sysfs as a child node

Note that the compiler is c99, we cannot declare variables in the code part, so add a definition of i at the beginning

 int i = 0;      //for c99 
 for(; i < LED_NUMBER_MAX; ++i){                led_device_class_son[i] = device_create (led_device_file_class,NULL,MKDEV(led_kernel_fd,i),NULL,LED_NAME[i]);                if(unlikely(IS_ERR(led_device_class_son[i]))) {                        printk("TQ210_LED[ERROR]: register son device\n");                        return -2;                }        } 

The last step is to map the physical address to the kernel virtual address:

The operating system cannot directly access the physical address due to the protection of the alu address randomization, and needs to be converted to a virtual address first.

 GPIC0_address = (volatile unsigned long*)ioremap(GPIC0_CON_ADDRESS,GPIC_ADDRESS_FORMAT); GPIC0_dat     = (volatile unsigned long*)ioremap(GPIC0_DAT_ADDRESS,GPIC_ADDRESS_FORMAT);

Finally, print it to indicate that we have successfully initialized init.

printk("TQ210_LED[SUCCESS]:init\n");return 0;

After implementing the exit function:

Three. Four exit realization

Release the child nodes first. Note that the release here is in order

Because when we register, we first register to the kernel-sysfs-sysfs child node such a process.

In the detailed description of the device file, I mentioned its search method. When we open a file under /dev, we call the interrupted do_sys_open first, and then call do_filp_open to get the node from the file system. In the VFS, because of the /dev directory The following file exists on the disk, but this file is registered in the file description structure of the VFS, because the VFS manages the disk, and then the open_namei function is called to obtain the pointer to the file in the VFS according to the name.

Finally, through this pointer, you can find which open, write, etc. the file points to. Finally, you will find that sysfs is found through the file pointer in it. udev is only responsible for registering the file to the VFS disk/DEV directory according to the structure of the class directory.

Then the node in sysfs points to the node in the kernel, so when we want to delete a node, if the node in the kernel is deleted first, you will find a segfault when you delete the node in sysfs.

This reason is probably because when the sysfs node is found, linux will judge whether the kernel pointer pointed to is valid to confirm whether it is a normal device.

So how we register, we release it in reverse.

Release the child node first

int i = 0;      //for c99 for(; i < LED_NUMBER_MAX; ++i){                device_unregister(led_device_class_son[i]);} 

Release the parent node

class_destroy(led_device_file_class);

Turn off io mapping

iounmap(GPIC0_address);iounmap(GPIC0_dat);

Delete module information in the kernel

 unregister_chrdev(led_kernel_fd,LED_KERNEL_NAME);

Print a line of log

 printk("TQ210_LED[SUCCESS]:exit\n");

It is highly recommended that you add an identifier before printing, so that you can see your log more clearly when you use grep when outputting the log.

At this point, your prototype driver has been completed. After you have compiled it and installed it with the insmod command, you will see the corresponding node in the /dev directory.

When registering with sysfs, sysfs will automatically notify udev.

Now start to write make and go back to the top directory

Three. Five make implementation

The make writing method of the kernel module has been mentioned in the previous linux kernel module article

If you cross compile here, remember to modify the variable values ​​of KDIR and CROSS_COMPILE.

ifneq ($(KERNELRELEASE),)        obj-m  := ./src/TQ210_LED/device/TQ210_device_led.oelse        KDIR := /home/beis/TQ/opt/EmbedSky/TQ210/Kernel_3.0.8_TQ210_for_Linux_v2.4all:        $(MAKE) -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-embedsky-linux-gnueabi-        mv ./src/TQ210_LED/device/*.mod.c               ./output        mv ./src/TQ210_LED/device/*.ko                  ./arch/arm/TQ210_LEDclean:        $(MAKE) -C $(KDIR) M=$(PWD) clean        rm ./output/*endif

Then you make a click, you can see your ko file in the arch/arm/TQ210_LED directory.

After completing the preliminary, you need to save it in git

git add .git commit -m "one"

Then go back to the driver file to implement the open function

Three. Six open implementation

First, we can use the child node of the current operation taken by MINOR

 int son_id = MINOR(inode->i_rdev);

Then use switch to do different processing on different nodes, so you don't need to write multiple driver files

It is highly recommended to do this for the same drive type.

switch(son_id){                         case 0: //led one                                  break;                         case 1: //led two                         break;                         default:                                printk("TQ210_LED[ERROR]: error son device number\n");                                return -1;                        break;                 }

First use the bit function we wrote to turn on the led into output mode

The first is to clear it first, and then turn it on, so that it is convenient to perform bit operations, so that it will not be affected by other bits.

case 0: //led one        SET_GPIC(GPIC0_address,~LED1_GPIC_BIT(0xf),&=);         //clear        SET_GPIC(GPIC0_address,LED1_GPIC_BIT(0x1),|=);          //outputbreak; case 1: //led two        SET_GPIC(GPIC0_address,~LED2_GPIC_BIT(0xf),&=);         //clear        SET_GPIC(GPIC0_address,LED2_GPIC_BIT(0x1),|=);          //outputbreak;

For beginners, maybe I don’t really understand the alignment, here I will disassemble it for everyone, and talk about this step in detail.

Let’s show you the macro expansion, take LED1 as an example

*GPIC0_address &= ~((0xf<<(12));

As mentioned in the above schematic diagram, CON3[15:12] is in the setting mode, the binary value of 0xf is 1111, and the left shift is 1111000000000000, and then the AND operation is performed on the original position. The characteristics of the AND operation: two bits are 1 at the same time, then 1 , 0 if not the same

Then the negation here is 0000111111111111, which is ANDed with CON3:

Assuming CON3 is 1011000000001000

1011000000001000

——————————

00001111111111110

——————————

0000000000001000

You can see the very clever use and characteristics, without modifying other bits to clear the bits we want to set.

The second one is the same. When you are unclear about these, please pick up the pen and do the calculations yourself, and let yourself be clear is the real understanding.

*GPIC0_address |= ((0x1<<(12));

Shift 12 bits to the left: 0001000000000000, the characteristic of the OR operation: When a bit is 1, it is 1, so there is no need to perform the inversion operation.

0000000000001000

——————————

0001000000000000

——————————

0001000000001000

It is also done without modifying other bits.

Open realizes the complete code:

static int led_open(struct inode* inode,struct file* file){                 /* open device and init */                 //get son device number                int son_id = MINOR(inode->i_rdev);                 switch(son_id){                         case 0: //led one                                SET_GPIC(GPIC0_address,~LED1_GPIC_BIT(0xf),&=);         //clear                                SET_GPIC(GPIC0_address,LED1_GPIC_BIT(0x1),|=);          //output                        break;                         case 1: //led two                                SET_GPIC(GPIC0_address,~LED2_GPIC_BIT(0xf),&=);         //clear                                SET_GPIC(GPIC0_address,LED2_GPIC_BIT(0x1),|=);          //output                        break;                         default:                                printk("TQ210_LED[ERROR]: error son device number\n");                                return -1;                        break;                 }                 printk("TQ210_LED[SUCCESS]:open\n");                return 0;}

Three. Seven write implementation

Get child nodes

 int son_id = MINOR(file->f_dentry->d_inode->i_rdev);

Here we use a function to get the parameters from user mode to kernel mode, remember to log on failure

char val = 0;if(copy_from_user(&val,buf,count)){ printk("TQ210_LED[ERROR]:get user variable\n"); return -1; }

Then realize:

It is judged that 1 is on, and 0 is off. The bit operation has been carefully discussed in open, so I won’t expand it here.

Our operation here needs to be done on the dat register, as mentioned at the beginning of the above.

switch(son_id){                         case 0: //led one                                if(val == 1){ SET_GPIC_STATE(GPIC0_dat,LED1_GPIC_DAT_BIT(1),LED2_GPIC_DAT_BIT((GET_GPIC(GPIC0_dat,LED2_GPIC_DAT_BIT(1)))),|=); }else{ SET_GPIC_STATE(GPIC0_dat,~LED1_GPIC_DAT_BIT(1),(GET_GPIC(GPIC0_dat,0)),&=); }                                printk("TQ210_LED[MSG]:led one\n");                        break;                         case 1: //led two                                if(val == 1){ SET_GPIC_STATE(GPIC0_dat,LED2_GPIC_DAT_BIT(1),LED1_GPIC_DAT_BIT(GET_GPIC(GPIC0_dat,LED1_GPIC_DAT_BIT(1))),|=); }else{ SET_GPIC_STATE(GPIC0_dat,~LED2_GPIC_DAT_BIT(1),(GET_GPIC(GPIC0_dat,0)),&=); }                                printk("TQ210_LED[MSG]:led two\n");                        break;                         default:                                printk("TQ210[ERROR]:can't write device number\n");                        break; }

Finally, print it out:

 printk("TQ210_LED[SUCCESS]:write\n"); return 0;

Complete realization:

static ssize_t led_write(struct file* file,const char __user* buf,size_t count,loff_t* ppos){                 //write device                 //get son device number                int son_id = MINOR(file->f_dentry->d_inode->i_rdev);                 //get user variable to kernel variablei                char val = 0;                if(copy_from_user(&val,buf,count)){ printk("TQ210_LED[ERROR]:get user variable\n"); return -1; }                 switch(son_id){                         case 0: //led one                                if(val == 1){ SET_GPIC_STATE(GPIC0_dat,LED1_GPIC_DAT_BIT(1),LED2_GPIC_DAT_BIT((GET_GPIC(GPIC0_dat,LED2_GPIC_DAT_BIT(1)))),|=); }else{ SET_GPIC_STATE(GPIC0_dat,~LED1_GPIC_DAT_BIT(1),(GET_GPIC(GPIC0_dat,0)),&=); }                                printk("TQ210_LED[MSG]:led one\n");                        break;                         case 1: //led two                                if(val == 1){ SET_GPIC_STATE(GPIC0_dat,LED2_GPIC_DAT_BIT(1),LED1_GPIC_DAT_BIT(GET_GPIC(GPIC0_dat,LED1_GPIC_DAT_BIT(1))),|=); }else{ SET_GPIC_STATE(GPIC0_dat,~LED2_GPIC_DAT_BIT(1),(GET_GPIC(GPIC0_dat,0)),&=); }                                printk("TQ210_LED[MSG]:led two\n");                        break;                         default:                                printk("TQ210[ERROR]:can't write device number\n");                        break;                 }                 printk("TQ210_LED[SUCCESS]:write\n");                return 0; }

Three. Eight read implementation

The implementation of read is also very simple. It uses bit operations to read the value of the bit. The only thing used is the function that passes parameters from the user mode to the kernel mode: copy_to_user

Complete code:

static ssize_t led_read(struct file* file,char __user* buf,size_t count,loff_t* ppos){                 //read led state                 int son_id = MINOR(file->f_dentry->d_inode->i_rdev);                 int BIT = 0;                char on[2]  = LED_STATE_ON;                char off[3] = LED_STATE_OFF;                switch(son_id){                         case 0:                                BIT = GET_GPIC(GPIC0_dat,LED1_GPIC_DAT_BIT(1));                                if(BIT){                                         if(copy_to_user((char*)buf,&on,sizeof(on))) return -EFAULT;                                 }else{                                        if(copy_to_user((char*)buf,&off,sizeof(off))) return -EFAULT;                                }                        break;                         case 1:                                BIT = GET_GPIC(GPIC0_dat,LED2_GPIC_DAT_BIT(1));                                if(BIT){                                         if(copy_to_user((char*)buf,&on,sizeof(on))) return -EFAULT;                                        return 2;                                 }else{                                        if(copy_to_user((char*)buf,&off,sizeof(off))) return -EFAULT;                                        return 3;                                }                        break;                         default:                                printk("TQ210[ERROR]:can't write device number\n");                                return -1;                        break;                 }                 printk("TQ210_LED[SUCCESS]:read\n");                return 0;}

Three. Nine ioctl implementation

For the implementation of ioctl, linux is required. The idea of ​​ioctl is to use parameters to obtain corresponding attributes and implement corresponding functions.

The linux kernel uses command codes to achieve these, and developers use switch case to implement different command codes differently.

A command code in the kernel looks like this:

________________________________________ | 设备类型  | 序列号 |  方向 | 数据尺寸  | |----------|--------|------|--------     | | 8 bit   |  8 bit   | 2 bit |8~14 bit | |----------|--------|------|------------ |

The linux kernel also provides some implementation macro definitions

//nr为序号,datatype为数据类型,如int_IO(type, nr ) //没有参数的命令_IOR(type, nr, datatype) //从驱动中读数据_IOW(type, nr, datatype) //写数据到驱动_IOWR(type,nr, datatype) //双向传送

example:

#define MEM_IOC_MAGIC 'm' //定义类型#define MEM_IOCSET _IOW(MEM_IOC_MAGIC,0,int)#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC, 1, int)

Linux also provides some macro functions to determine whether the parameters are valid

_IOC_NR()    读取基数域值 (bit0~ bit7)_IOC_TYPE    读取魔数域值 (bit8 ~ bit15)_IOC_SIZE    读取数据大小域值 (bit16 ~ bit29)_IOC_DIR     获取读写属性域值 (bit30 ~ bit31)

My definition:

//ioctl#define MEMDEV_IOC_MAGIC  's'#define MEMDEV_IOCPRINT   _IO(MEMDEV_IOC_MAGIC, 1)#define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)#define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)#define MEMDEV_IOC_MAXNR  3

Still take child nodes

int par = 0;int son_id = MINOR(file->f_dentry->d_inode->i_rdev);

Determine whether the type is valid:

 if(_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC)                        return -EINVAL;

Determine whether the parameter is valid

 if(_IOC_NR(cmd) > MEMDEV_IOC_MAXNR)                        return -EINVAL;

Is judging whether the read and write attributes are valid

 if (_IOC_DIR(cmd) & _IOC_READ){ if(access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd))){ return -EFAULT; } }                else if(_IOC_DIR(cmd) & _IOC_WRITE) { if (access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd))){ return -EFAULT; } }

Here, an access_ok is used as a macro to determine whether the read and write parameters are valid.

The next step is to implement it

Just need to judge the attributes of cmd.

 switch(son_id){                         case 0:                                switch(cmd){                                         case MEMDEV_IOCPRINT:   //Print information                                                printk("TQ210_LED1 demo to stephen zhou\n");                                        break;                                         case MEMDEV_IOCGETDATA: //Get parameters                                                return __put_user(par,(int*) arg);                                        break;                                         case MEMDEV_IOCSETDATA: //Set parameters                                                return __get_user(par,(int*) arg);                                        break;                                         default:                                                return -EINVAL;                                        break;                                }                        break;                         case 1:                                switch(cmd){                                         case MEMDEV_IOCPRINT:   //Print information                                                printk("TQ210_LED2 demo to stephen zhou\n");                                        break;                                         case MEMDEV_IOCGETDATA: //Get parameters                                                return __put_user(par,(int*) arg);                                        break;                                         case MEMDEV_IOCSETDATA: //Set parameters                                                return __get_user(par,(int*) arg);                                        break;                                         default:                                                return -EINVAL;                                        break;                                }                        break;}

Finally, don’t forget to print:

 printk("TQ210_LED[SUCCESS]:ioctl\n"); return 0;

Complete code:

static long led_ioctl(struct file* file,unsigned int cmd,unsigned long arg){                 int par = 0;                //get son id                    int son_id = MINOR(file->f_dentry->d_inode->i_rdev);                 //cmd su or err                if(_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC)                        return -EINVAL;                if(_IOC_NR(cmd) > MEMDEV_IOC_MAXNR)                        return -EINVAL;                if (_IOC_DIR(cmd) & _IOC_READ){ if(access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd))){ return -EFAULT; } }                else if(_IOC_DIR(cmd) & _IOC_WRITE) { if (access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd))){ return -EFAULT; } }                 switch(son_id){                         case 0:                                switch(cmd){                                         case MEMDEV_IOCPRINT:   //Print information                                                printk("TQ210_LED1 demo to stephen zhou\n");                                        break;                                         case MEMDEV_IOCGETDATA: //Get parameters                                                return __put_user(par,(int*) arg);                                        break;                                         case MEMDEV_IOCSETDATA: //Set parameters                                                return __get_user(par,(int*) arg);                                        break;                                         default:                                                return -EINVAL;                                        break;                                }                        break;                         case 1:                                switch(cmd){                                         case MEMDEV_IOCPRINT:   //Print information                                                printk("TQ210_LED2 demo to stephen zhou\n");                                        break;                                         case MEMDEV_IOCGETDATA: //Get parameters                                                return __put_user(par,(int*) arg);                                        break;                                         case MEMDEV_IOCSETDATA: //Set parameters                                                return __get_user(par,(int*) arg);                                        break;                                         default:                                                return -EINVAL;                                        break;                                }                        break;                }                 printk("TQ210_LED[SUCCESS]:ioctl\n");                return 0;}

Ok, here open, write, read, ioctl have been implemented, so let's write a simple app to test it

Fourth, write a test app

Here I wrote a flashing app code

Create a new TQ210_app_led.c file in the src/app directory for user-mode programs

Contains basic header files

#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdio.h>#include <unistd.h>#include <string.h>#include <sys/ioctl.h>#include <stdlib.h>#include "../../../include/TQ210_LED/device/TQ210_device_led.h"

Here I wrote a basic printing function

void print(char* led1_state,char* led2_state){        system("clear");        printf("/*****************************************************************************\n");        printf("*                                                                            *\n");        printf("*  TQ210 LED的demo演示                                                       *\n");        printf("*  Copyright (C) 2021 StephenZhou                                            *\n");        printf("*                                                                            *\n");        printf("*----------------------------------------------------------------------------*\n");        printf("*  Device         : State                                                    *\n");        printf("*----------------------------------------------------------------------------*\n");        if(strcmp(led1_state,"ON") == 0){ printf("*  TQ210_LED_ONE  : ON                                                             *\n"); }        else{ printf("*  TQ210_LED_ONE  : OFF                                                        *\n"); }        printf("*----------------------------------------------------------------------------*\n");        if(strcmp(led2_state,"ON") == 0){ printf("*  TQ210_LED_TWO  : ON                                                             *\n"); }        else{ printf("*  TQ210_LED_TWO  : OFF                                                        *\n"); }        printf("*----------------------------------------------------------------------------*\n");        printf("*  Change History :                                                          *\n");        printf("*  <Date>     | <Version> | <Author>       | <Description>                   *\n");        printf("*----------------------------------------------------------------------------*\n");        printf("*  2020/5/25 | 1.0.0.0   | StephenZhou      | LED Demo                       *\n");        printf("*----------------------------------------------------------------------------*\n");        printf("*                                                                            *\n");        printf("*****************************************************************************/\n"); }

The first step is to declare the basic variables, and then just write in the way of open and write. The comments I wrote are very clear

It is to open first, then read to read the status, print the status, and call ioctl to print the basic information, and use write to write the status.

int main(int argc,char **argv){         /* Two LEDs flash each other and print the status */        int cmd = 0,arg = 0,val = 0;        char led1_state[4] = {0},led2_state[4] = {0};        //1. open udev device        int led_fd1 = open(LED_DEV_ONE_NAME,O_RDWR);        int led_fd2 = open(LED_DEV_TWO_NAME,O_RDWR);        if(led_fd1 == -1 || led_fd2 == -1){                printf("error:can't open led device\n");                return -1;        }         //2. print msg to kernel ioctl        cmd = MEMDEV_IOCPRINT;        if(ioctl(led_fd1,cmd,&arg) == -1) { printf("error:can't ioctl for led1\n"); return -1; }        cmd = MEMDEV_IOCPRINT;        if(ioctl(led_fd2,cmd,&arg) == -1) { printf("error:can't ioctl for led2\n"); return -1; }         //3. wink        while(1){                 //led one wink led two close                val = 1;                if(write(led_fd1,&val,sizeof(val)) == -1){ printf("error:can't write for led1\n"); return -1; }                val = 0;                if(write(led_fd2,&val,sizeof(val)) == -1){ printf("error:can't write for led2\n"); return -1; }                 //clear string                memset(led1_state,0,sizeof(led1_state));                memset(led2_state,0,sizeof(led2_state));                 //read led state                if(read(led_fd1,led1_state,sizeof(led1_state)) == -1){ printf("error:cant't read for led1\n"); return -1; }                if(read(led_fd2,led2_state,sizeof(led2_state)) == -1){ printf("error:cant't read for led2\n"); return -1; }                print(led1_state,led2_state);                 sleep(3);                 //led two wink led one close                val = 1;                if(write(led_fd2,&val,sizeof(val)) == -1){ printf("error:can't write for led1\n"); return -1; }                val = 0;                if(write(led_fd1,&val,sizeof(val)) == -1){ printf("error:can't write for led2\n"); return -1; }                 //clear string                memset(led1_state,0,sizeof(led1_state));                memset(led2_state,0,sizeof(led2_state));                 //read led state                if(read(led_fd1,led1_state,sizeof(led1_state)) == -1){ printf("error:cant't read for led1\n"); return -1; }                if(read(led_fd2,led2_state,sizeof(led2_state)) == -1){ printf("error:cant't read for led2\n"); return -1; }                print(led1_state,led2_state);                 sleep(3);         }         return 0;}                                                                                                                                                                                                                                                  

We are adding the app file to make, using a cross compiler

The following is my modified make

I used some mv commands to put the generated temporary files and target files in a fixed directory.

ifneq ($(KERNELRELEASE),)        obj-m  := ./src/TQ210_LED/device/TQ210_device_led.oelse        KDIR := /home/beis/TQ/opt/EmbedSky/TQ210/Kernel_3.0.8_TQ210_for_Linux_v2.4all:        $(MAKE) -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-embedsky-linux-gnueabi-        arm-embedsky-linux-gnueabi-gcc ./src/TQ210_LED/app/TQ210_app_led.c -o TQ210_app_led        mv ./src/TQ210_LED/device/*.o                   ./output        mv *.symvers                                    ./output        mv *.order                                      ./output        mv ./src/TQ210_LED/device/*.mod.c               ./output        mv ./src/TQ210_LED/device/*.ko                  ./arch/arm/TQ210_LED        mv TQ210_app_led                                ./arch/arm/TQ210_LEDclean:        $(MAKE) -C $(KDIR) M=$(PWD) clean        rm ./output/*        rm ./arch/arm/TQ210_LED/*        rm ./arch/arm/network/*endif

At this time, you can see the app and .ko files in the arch/arm/TQ210_LED directory by clicking on make.

[email protected]:~/moudul/arch/arm/TQ210_LED$ tree.├── TQ210_app_led└── TQ210_device_led.ko 0 directories, 2 files

Finally, don't forget to git

git add .git commit -m "two"

At this step, basically a demo has been written, so next we will let it be the Internet of Things

Write a server to control it

The following code is implemented by the way I have written web ui before.

V. Internet of Things

Create a new network directory in the src directory, create a new server.c file in this directory, the same is true for the include directory

If you have no experience in http development and website development, you can skip this part. Here I am writing a server by myself to let everyone understand the principles of the Internet of Things more clearly. You can go to see my explanation of the http protocol and my open source Http parsing code

mkdir src/networktouch src/network/server.cmkdir include/networktouch include/network/server.h

We are writing a html to display the front end, which is the submission of post

The code is very simple, stored in the src/network directory

First define the protocol and header file in the .h file

#include <stdio.h>#include <strings.h>#include <unistd.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <string.h>#include "../../include/TQ210_LED/device/TQ210_device_led.h" //server config#define PORT                    8081#define BACKLOG                 10 //http#define STATE_OK                "HTTP/1.0 200 OK\r\n"#define SERVER_TYPE             "Server: DWBServer\r\nContent-Type: text/html;charset=utf-8\r\n\r\n" //file#define INDEX_NAME              "./index.html" //http ops#define STATE_GET_INDEX         1#define STATE_BUTTON_FOO        2#define STATE_200               3 //led#define LED_1                   1#define LED_2                   2#define LED_UP                  3#define LED_DOWN                4

The idea is to submit the post to the server, the server gets the post data and then judges the data information to perform the corresponding function

The form of form is used here to achieve this function

<html><body><form action = "" method = "post">                 <button name="foo" value="LED1_Light">LED1_点亮</button></form><form action = "" method = "post">       <button name="foo" value="LED1_Ext">LED1_熄灯</button></form><form action = "" method = "post">                 <button name="foo" value="LED2_Light">LED2_点亮</button></form><form action = "" method = "post">       <button name="foo" value="LED2_Ext">LED2_熄灯</button></form></body></html>

server.c part

First include the header file:

#include "../../include/network/server.h"

Writing a head code that parses the http protocol:

This part is used to get the message header, so that the server can judge what the request parameter is

int GetHead(char* buff,char* t){         if(buff == NULL || t == NULL){                return -1;        }        int str_len = strlen(buff);        int i = 0;        for(; i<str_len ;++i){                 if(buff[i] == '\r'){                        break;                }                 t[i] = buff[i];         }         return 0; }

Write a function to get the end of the message, because the text will be at the end of the message body if the post is submitted

int GetEnd(char* buff,char* t){        if(buff == NULL || t == NULL){                return -1;        }         int i = strlen(buff);        int count = 0;        int d = 0;        int y = 0;        for(;i>0;--i){                if(buff[i] == '\n'){                        break;                }                ++count;        }         d = strlen(buff) - count+1;        for(;buff[d] != '\0';++d){                t[y++] = buff[d];         }         return 0;}

Then I am writing a function to get the HTTP status. This part mainly uses the parsed text header to determine what operation the http client performed, and then returns it to us so that we can do the corresponding operation

int GET_HTTP_STATE(char* buff){                if(buff == NULL){                  return 0;        }         char Head[256] = {0};        GetHead(buff,Head);        if(Head == NULL){ return 0; }                if(strcmp(Head,"GET / HTTP/1.1") == 0){                                return STATE_GET_INDEX;            }            char ff[256] = {0};        if(strcmp(Head,"POST / HTTP/1.1") == 0){                                          return STATE_BUTTON_FOO;         }         return STATE_200; }

Then there is the function of the led operation. Needless to say, this is very simple.

int Led_Ops(int LED_INDEX,int STATE){         int fd  = 0;        int val = 0;         if(LED_INDEX == LED_1){                 fd = open(LED_DEV_ONE_NAME,O_RDWR);                if(fd == -1) { return -1; }        }                if(LED_INDEX == LED_2){                fd = open(LED_DEV_TWO_NAME,O_RDWR);                if(fd == -1) { return -1; }        }         if(STATE == LED_UP){                val = 1;                write(fd,&val,sizeof(val));        }         if(STATE == LED_DOWN){                val = 0;                write(fd,&val,sizeof(val));        }         close(fd); }

The last is the event function of exec, which performs the corresponding operation according to the returned status, and finally gives feedback to the http client, 200 tells it that we have completed the work, and secondly, after the post is submitted, we have to return to the page, because the http client will display The data returned by the server.

int Http_Exec(int state,int fd,char* buff){         char rt[2*1024] = {0};        if(state == STATE_GET_INDEX){                strcat(rt,STATE_OK);                strcat(rt,SERVER_TYPE);                strcat(rt,index_body);                send(fd,rt,strlen(rt),0);        }         if(state == STATE_BUTTON_FOO){                char fun[256] = {0};                GetEnd(buff,fun);                strcat(rt,STATE_OK);                strcat(rt,SERVER_TYPE);                strcat(rt,index_body);                send(fd,rt,strlen(rt),0);                if(strcmp(fun,"foo=LED1_Light") == 0){                        Led_Ops(LED_1,LED_UP);                }                if(strcmp(fun,"foo=LED1_Ext") == 0){                        Led_Ops(LED_1,LED_DOWN);                }                if(strcmp(fun,"foo=LED2_Light") == 0){                        Led_Ops(LED_2,LED_UP);                }                if(strcmp(fun,"foo=LED2_Ext") == 0){                        Led_Ops(LED_2,LED_DOWN);                }         }         if(state == STATE_200){                strcat(rt,STATE_OK);                strcat(rt,SERVER_TYPE);                send(fd,rt,strlen(rt),0);        }         return 0;}

Then there is the main function

First initialize tcp, what I wrote here is not multi-threaded, it is single-threaded, which means that only one http client can respond at a time.

 int listenfd, connectfd;        struct sockaddr_in server, client;        socklen_t addrlen;         if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){                perror("[SERVER_ERROR] socket\n");                return -1;        }         int opt = 1;        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));        bzero(&server, sizeof(server));        server.sin_family = AF_INET;        server.sin_port = htons(PORT);        server.sin_addr.s_addr = htonl(INADDR_ANY);         if(bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1){                perror("[SERVER_ERROR] bind\n");                return -1;        }         addrlen = sizeof(client);         FILE* fp = fopen(INDEX_NAME,"r");        if(fp == NULL){                perror("[SERVER_ERROR] index.html file\n");                return -1;        } 

Read index.html and return it to the client

fread(index_body,sizeof(index_body),1,fp);

Then the while loop monitors and executes the event

 while(1){                 if(listen(listenfd, BACKLOG) == -1){                        perror("[SERVER_ERROR] listen\n");                        return -1;                }                 if((connectfd = accept(listenfd, (struct sockaddr *)&client, &addrlen)) == -1){                        perror("[SERVER_ERROR] accept\n");                        return -1;                }                 char buff[1024] = {0};                recv(connectfd,buff,1024,0);                //printf("%s\n",buff);                Http_Exec(GET_HTTP_STATE(buff),connectfd,buff);                close(connectfd);        }

The final close tcpfd

 close(listenfd); return 0;

6. Add support in make

arm-embedsky-linux-gnueabi-gcc ./src/network/server.c -o servermv server                                       ./arch/arm/networkcp ./src/network/index.html                     ./arch/arm/network

Well, at this step, it's completely finished.

The complete make:

ifneq ($(KERNELRELEASE),)        obj-m  := ./src/TQ210_LED/device/TQ210_device_led.oelse        KDIR := /home/beis/TQ/opt/EmbedSky/TQ210/Kernel_3.0.8_TQ210_for_Linux_v2.4all:        $(MAKE) -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-embedsky-linux-gnueabi-        arm-embedsky-linux-gnueabi-gcc ./src/TQ210_LED/app/TQ210_app_led.c -o TQ210_app_led        arm-embedsky-linux-gnueabi-gcc ./src/network/server.c -o server        mv ./src/TQ210_LED/device/*.o                   ./output        mv *.symvers                                    ./output        mv *.order                                      ./output        mv ./src/TQ210_LED/device/*.mod.c               ./output        mv ./src/TQ210_LED/device/*.ko                  ./arch/arm/TQ210_LED        mv TQ210_app_led                                ./arch/arm/TQ210_LED        mv server                                       ./arch/arm/network        cp ./src/network/index.html                     ./arch/arm/networkclean:        $(MAKE) -C $(KDIR) M=$(PWD) clean        rm ./output/*        rm ./arch/arm/TQ210_LED/*        rm ./arch/arm/network/*endif

git save

git add .git commit -m "three"

7. Final presentation

Use your method to transfer all the files in the two directories under arch/arm to the development board

I use dropbear

Then ssh login to execute.

We log in by ssh, so many commands are in the /sbin directory

Load the module:

/sbin/insmod TQ210_device_led.ko

Then check the log

You can see the successful loading.

Then we are running the app demo demo:

Take a look at the server demo:

Because it’s a GIF, it’s fuzzy, so you can test it yourself

Here is the project address of github: https://github.com/beiszhihao/Internet-of-things-project

I will open source all IoT-based projects in this warehouse in the future

Welcome everyone star