1. ARM basic knowledge
Basic: C language with a certain hardware foundation
Features---"Back and Back Contact
arm target:
Understand simple assembly code
Can read circuit diagrams, chip manuals
Learn how to use software to control hardware ideas
Solutions to the problem
Talk about the understanding of embedded?
Centered on computing applications, a dedicated computer system with tailorable software and hardware. There are certain requirements for power consumption, volume, performance, cost, etc.
Features: strong specificity, dedicated computer; large difference in operating environment; less resources than general-purpose PCs (applicability, just enough); low power consumption, small size, high integration, low cost; long life cycle
Learning arm focuses on two aspects: the principle of program operation and the principle of hardware control.
1. Basic computer theory
In computer systems, high and low levels are used to represent logic 1 and 0
The storage, transmission, and operation (processing of data) of data in the computer are all carried out in binary form
Through data transmission, what is actually transmitted through the bus is an electrical signal, high and low levels (0, 1). Memory only has high and low levels. The operation is performed in the circuit, and the operation is completed in the integrated circuit.
2. The composition of the computer
Composed of input devices, output devices, memory, calculators, and controllers
1. Input device: converts other signals into signals (electrical signals) that the computer can recognize.
2. Output device: Convert electrical signals (0, 1) into signals that people or other devices can understand.
3. Memory: A component that stores programs and data, and is also the basis for computers to realize "stored program control".
Program: an ordered collection of instructions //assembly instructions
ROM: flash (EMMC), disk space, no data loss when power off
RAM: memory, lost data when power off
+ -->Summation command
32 is the operating system, addressing is 32 bits. Addressing space bits 2^32,4G.
4. Calculator: The part of the CPU for information processing and calculation, often performs arithmetic and logic operations. Its core is the arithmetic logic unit ALUCPU, which uses various digital circuits to form a variety of calculation circuits, such as: addition, subtraction, etc.
Such as the addition operator:
5. Controller: the command center of the entire computer
The key thing I want to learn is: the principle of program operation
think:
1.运算器不同,处理指令不同,及对应指令集不同。不同的处理器上如何运行同一个c语言程序?
3. 指令的解析
一条指令(机器码)的执行通常分为三个阶段:
1)取指:控制器将PC寄存器中的值发送给内存,内存将对应地址中的指令(机器码)传送回CPU的指令寄存器IR中
2)译码:指令译码器对IR中的指令进行识别,即将指令(机器码)翻译成具体的运算操作(+/-/*...)
3)执行:运算器执行对应的指令并将结果写入寄存器
执行完一条指令后CPU内对应的硬件会将PC的值自动增加使PC指向内存中的下一条指令
- 指令的执行是按照流水线
- 取指--》取指器 根据PC值取指令
- 译码--》译码器
- 执行--》执行器
以上三个器件,都是单周期的器件,三个器件的工作是独立,
指令1 指令2 指令3 指令4 指令5
1 取指
2 译码 取指
3 执行 译码 取指
4 执行 译码 取指
5 执行 译码 取指
6 执行 译码
7 执行
- PC永远指向当前正在取指指令的地址,一旦取到指令,pc后移4byte,保存下一条指令地址。
4. 编译原理
CPU能够识别的唯一的语言是机器码,一个CPU能够识别哪些机器码是由处理器的硬件(运算器种类)决定的不同的机器码代表不同的运算,同样不同的CPU的机器码是不通用的即不可以移植,汇编是用一个标识符来代表一个机器码,所以不同的CPU汇编也不一样,即汇编语言不可以移植。
C语言编译的时候我们可以使用不同的编译器将C编译成不同的汇编和机器码,所以C可以不依赖CPU架构
总结面试题:
1.指令解析的过程
2.为什么不同处理器,要用不同的编译器编译程序代码?编译原理
5. 认识ARM
- ARM含义?
1. ARM代表一个公司
2. ARM表示一种技术
3. ARM可以表示一些处理器的统称
- 架构:
arm-v4,arm-v5,arm-v6,arm-v7(32Bits),arm-v8(64Bits)
架构指支持的汇编指令集
- 内核:
cortex-a9,a53,a73,a77
ARM公司授权芯片的公司,芯片产家在内核的基础上,增加了一些外设,发布一款芯片,这些芯片可以统称为SOC
- SOC:
System Of Chip: System On Chip), S5P6818, Snapdragon 855 (Qualcomm), Kirin 990 (Hisilicon)
s5p6818:
- ARM History
1. In 1978, CPU Corporation
Cambridge processing Unit (Cambridge Warehouse)
2. 1979 Acorn (assembles computers)
3. 1985, 32-bit, 8MHz,
Reduced instruction set RISC used
The name of the chip ARM ----" Acorn RISC machine (Acorn RISC machine)
4. In 1990,
iPhone £1.5m VLSI: £250,000
12 engineers + technical patents: £1.5 million
ARM Company - "Advanced RISC Machine (Advanced Reduced Instruction Set Computer)
ARM does not produce chips, but licenses technology and provides solutions.
For example: Xiaomi mobile phone (bought Qualcomm chip + UI + camera optimization, etc.)
5. In 2016, Japan’s Softbank acquired
- Instruction Set
- Reduced instruction set (RISC) --> microprocessor (reduced instruction set computer)
Select some relatively simple and frequently used instructions in the complex instruction set
The width of the instruction is fixed, and most of them are single-cycle instructions.
Example: If there is an addition operator, there is no multiplication operator 3*3 ---》3+3+3
- Complex instruction set (CISC) --> computer CPU (complex instruction set computer)
Pay attention to the functionality of the instruction, the cycle of the instruction, and the width of the instruction is not fixed
eg Simplification: After compiling, use disassembly to view code instructions.
Compile the program with a cross-compilation tool to generate an executable program for arm
arm-linux-gnueabinf-gcc 1.c
file a.out --> View executable file properties
Use the disassembly command to convert the elf file to a disassembly file.dis
arm-linux-gnueabinf-objdump -D a.out > a.dis
View the instructions of the ubuntu complex instruction set:
gcc 1.c ---> Compile and generate a.out executable file
file a.out view file properties
objdump -D a.out > a.dis ----> disassembly
- ARM company product distribution
- Cortex-A:
Qualcomm, MediaTek, HiSilicon, Samsung, Freescale, for cutting-edge virtual memory-based operating systems and user applications.
- Cortex-R:
Real-time processors provide high-performance solutions for embedded systems that require reliability, fault tolerance, and real-time responsiveness. Automotive electronics, camera cameras.
- Cortex-M: MCU
For cost- and power-sensitive MCU and terminal applications, generally do not run the operating system, but can run real-time operating systems: FreeRTOS, uCosII, LiteOS (Huawei), STMicroelectronics (ST) STM32 series
- ARM architecture: ARM-v8(A)--->Cortex-A53(8-core)-->S5P6818 Main frequency: 1.4GHZ
- ARM data type conventions
ARM-v7 architecture: 32bit processor
- char: 8 bits
- halfword: 16 bits
- word: 32 bits
- doubleword:64位(cortex-a)
ARM-v8 architecture: 64bit processor, backward compatible with 32bit (we learn 32bit)
- char: 8 bits
- halfword: 16 bits
- word: 32 bits
- doubleword:64位(cortex-a)
- quadword: 128-bit (ARM-v8)
- What do 32-bit and 64-bit processors mean?
- 32-bit: One instruction can perform operations on 32-bit data
- 64-bit: One instruction can perform operations on 64-bit data
- Most ARM cores provide:
ARM-v7 architecture:
- ARM instruction set (32-bit)
One instruction occupies 32 bits of memory space
- Thumb instruction set (16-bit)
One instruction occupies 16 bits of memory space
ARM-V8: backward compatible with ARM-v7 architecture
- ARM instruction set: A64, A32
- Thumb instruction set: T32, T16
- Regardless of whether it is A64 or A32, each instruction occupies 32 bits of space
- Regardless of whether it is T32 or T16, each instruction occupies 16 bits of space
ARM-v7:
ARM instruction set: A32
Thumb指令集:T16
ARM指令集功能更全,性能更高
thumb指令集比ARM指令集指令密度要大
- ARM处理器的工作模式
ARM内核的命名规格历史
ARM7 ARM9 ARM10 ARM11 ,ARM11之后,命名规格改变
Cortex-A9 A53 A75
ARM7-11 有7种基本工作模式:
- User : 非特权模式,大部分任务执行在这种模式
- FIQ (Fast Interrupt Request) : 当一个高优先级(fast) 中断产生时将会进入这种模式
- IRQ (Interrupt Request): 当一个低优先级(normal) 中断产生时将会进入这种模式
FIQ和IRQ打断当前正在做的事去做其他的事情,做了再回来继续做自己的事情。鼠标键盘等都是这样实现的。(Linux内核会有中断,驱动写中断驱动代码)
中断的概念:
- Supervisor(SVC) : 当复位或软中断指令执行时将会进入这种模式
(任务的切换会切入这个模式,权限最高的模式,刚启动的时候在这个模式下,权限高,可以做一些核心的操作。进行系统调用的时候会切换这个模式。)
- Abort : 当指令存取异常时将会进入这种模式
- Undef : 当执行未定义指令时会进入这种模式
- System : 使用和User模式相同寄存器集的特权模式
保证不同任务每次调用同一个函数都是从头开始。
Cortex-A特有模式:
- Monitor : 是为了安全而扩展出的用于执行安全监控代码的模式;
也是一种特权模式
特定的模式拥有特定的权限,执行特定的代码,完成特定的功能
- CPU(内核)组成:
- 运算器
加法运算 --》加法器 --》加法指令
- 控制器
- 存储器---》REG Register:此寄存器由ARM公司集成到CPU的内部来存放机器码
- A32:每个寄存器可以存储一个32位数据
- A64:每个寄存器可以存储一个64位数据
总结:
- ARM7,9,11 有37个32-Bits长的寄存器
- 1 个用作PC( program counter)
- 1 for CPSR (current program status register)
- 5 for SPSR (saved program status registers)
- 30 general purpose registers
- Cortex-A has 3 more registers and 40 32-Bits long registers
- Monitor mode r13_mon , r14_mon , spsr_mon
- R0-r15, CPSR, SPSR These registers are provided by ARM, and each register is 32 bits.
These registers have no address, only a unique number, through which the corresponding address space can be accessed. R0- R15, cpsr, spsr are the corresponding numbers, how many binary digits does each number correspond to? 32
- R13: Stack pointer register
the stack pointer, sp
Store the address of the top of the stack
- R14: link register
the link register, lr
- When the function is called, save the return address
(Save the address of the next instruction corresponding to the instruction of the call letter, and give the lr value to pc when returning, and continue to execute the next code)
- R15: Program count register
the program counter, pc
- Store the address of the current fetch instruction
The cpu takes instructions (values) one by one from the memory
- CPSR: Current Program Status Register
- Store the current program running status
current program status register, cpsr
These NZCV bits are called condition bits, and the next eight bits are called control bits (indicated by C)
- SPSR: Register to save program state
saved program status register
- for saving cpsr
- Clock: responsible for sending out the clock signal for the CPU to start timing.
Two, arm assembly instruction learning
1. Basic concepts
- What codes can generate assembly instructions in c language
1 "The statement with ';' can be compiled to generate instructions
2 "Preprocessing with '#' sign, how to compile the auxiliary compiler, and what content to compile
- Compile the overall taxonomy
1》指令: 编译完生成一条机器码存储在内存单元当中,CPU执行时能完成对应的操作(类似于C中的语句)
2》伪操作 (相当于c中的’#‘的内容)告诉编译器怎么编译):不会生成机器码也不会占用内存,其作用是告诉编译器怎样编译(类似于C中的预处理指令)
3》伪指令 (如:cpu中没有乘法器,对应没有乘法指令,3*3 ---》用加法器实现3+3+3,替换实现):不是指令,编译器在编译时将其替换成等效的指令
汇编中注释代码用'@'注释一行 ,注释一段代码 /**/
- 指令分类
1.数据处理指令: 对数据进行逻辑、算数运算
2.跳转指令: 实现程序的跳转,实质是修改PC
3.Load/Store指令: 对内存的读写操作//如 a++ 读a的值,将运算结果从cpu写道内存
4.状态寄存器传送指: 对CPSR进行读写操作//其他都不能动CPSR
5.异常中断产生指令: 触发软中断,常用于内核的系统调用 //SWI:软中断
6.协处理器指令: 操作协处理器的指令
//如3*3 ---》用加法器实现3+3+3,比较慢。我们可以外接一个协处理器(乘法器)(每个协处理器的功能比较单一),协处理器指令就是操作这个协处理器的,用的比较多的cp15协处理器。
- 汇编指令代码框架
.text @声明一段代码
.global _start @将_start 声明为一个全局的符号,其他.s文件也可以引用
@如调用函数 func : .global func
_start: @汇编的入口
@汇编代码段
.end @汇编的结束
2. 汇编指令
- 指令的语法格式:
- <opcode><code>{s} Rd,Rn,oprand2
opcode:指令的名字
code:条件码(if else),可以省略不写,默认指令是无条件执行
s:状态标志
加s,指令的执行结果影响cpsr的NZCV位,
不加s,不影响
Rd:目标寄存器
Rn:第一个操作寄存器
oprand2:第二个操作数,可以是普通寄存器,可以是立即数
注:指令的名字,条件码,s连到一起写,指令名和目标寄存器之间使用空格,寄存器和数据之间使用逗号隔开,指令中的字符不区分大小写
2.1 数据处理指令
1》数据搬移指令 mov
格式:
<opcode><code>{s} Rd,oprand2如果是立即数,前边必须加#
PC寄存器详细讲解:
指令的执行三步:取地,译码,执行(PC永远指向当前正在取指指令的地址)
2》立即数:立即数是保存在指令中的数,取指令的同时将值取过去,和普通变量的区别是,变量保存在内存中的数据,需要单独取值运算。
立即数的本质:立即数是包含在指令当中的数据(即属于指令的一部分)
立即数的优点:读取指令的同时也将立即数读取到了内存中,速度快
立即数的缺点:数量有限
如:MOV ,#0x12345678 @报错,不合法
注:使用mov 给寄存器里面存放值的时候,#号后面需是有效数(1:立即数,2:取反之后是立即数),如果不是立即数需要用ldr指令进行存放。
如果不是立即数,用伪指令ldr 赋值
3》算数运算指令
算数运算指令 add adc sub sbc mul
数据运算指令格式s
<操作码><目标寄存器><第一操作寄存器><第二操作数>
ADD R3,R1,R2 ;R2可以是立即数 只有乘法这不能为立即数
操作码 指定当前指令是哪种运算
目标寄存器 存放运算结果
第一操作寄存器 存放参与运算的一个数据(只能是寄存器)
第二操作数 存放参与运算的另一个数据(可以是寄存器/立即数)
add 普通的加法指令
adc 带进位的加法指令
假设2个64位的数相加
第一个64位的数,R0存放低32位,R1存放高32位,
第二个64位的数,R2存放低32位,R3存放高32位
结果R4存放低32位,R5存放高32位
注意:mul r2, r0, #0x4 @ 错误
乘法指令的第二个操作数只能是一个寄存器
mul r2, r1,r0
2.2 跳转指令
2.2 跳转指令
1》修改PC,不建议使用,因为需要查询指令的地址
2》 b bl :指令跳转
格式:b/bl Label
Label: 指令
相当C语言的函数调用
B指令(不带返回的跳转)
不保存返回地址的跳转(返回地址不保存到lr中)
BL指令(带返回的跳转指令),将LR的值修改成跳转指令下一条指令的地址,再将PC的值修改成跳转标识符下指令的地址
补充了解:
RM指令条件码表:可跟的判断条件成立跳转(NZCV在用于判断两者之间关系使用比较多)
如:c代码如下:
练习:
实现以下逻辑
unsigned int r1 = 9;
unsigned int r2 = 15;
while(1)
{
if(r1 == r2)
goto stop;
if(r1 > r2)
r1 = r1 - r2;
if(r1 < r2)
r2 = r2 - r1;
}
stop:
while(1);
汇编指令练习答案如下:
mov r1,#9
mov r2,#15
loop:
cmp r1,r2 @cmp 比较指令
beq stop
subhi r1,r1,r2
subcc r2,r2,r1
b loop
stop:
b stop
2.3 Load/Store指令
2.3 Load/Store指令
对内存的读写操作//如 a++ 读a的值,将运算结果从cpu写到内存
可用地址查找:(我们不用查找,脚本文件中配置了内存空间的分配)
查看内存中内容:
1>单寄存器操作指令 ldr/str
格式:ldr/str Rm, [Rn]
Rm: 存储是数据
Rn:存储的数据,地址
将CPU中r1寄存器中的数据存储到内存中r0地址的空间中
将r0指向的地址空间中的内容,读到r2寄存器中
ldr r2, [r0]
将r1中的值存储到r0+4指向的地址空间中,R0中的值不变
str r1, [r0, #4];
将r2中的值存储到r0指向的地址空间中,r0 = r0 + 4
str r2, [r0], #4
将R3中的值存储到R0+4指向的地址空间中,并且r0 = r0 + 4
str r3, [r0, #4]!
2>多寄存器操作指令 stm ldm
将r1到r4中的值存储到r0指向地址空间中,连续16个字节的地址空间
stm r0, {r1-r4}
将r0指向的地址空间中,连续的16个字节的数据,读到r5-r8寄存器中
ldm r0, {r5-r8}
如果寄存器列表中的寄存器编号既有连续又有不连续,连续的使用“-”隔开,不连续的使用“,”
stm r0, {r1-r3,r4}
2. 不管寄存器列表中的寄存器编号顺序如何变化,都是小地址对应小编号的寄存器高地址对应大编号的寄存器
stm r0, {r4,r3,r2,r1}
ldm r0, {r8,r7,r6,r5}
3>栈的操作指令 stmfd ldmfd
栈的种类
空栈(Empty)
栈指针指向的地址是空的,在栈中存储数据时,可以直接存储,存储完成之后需要将栈指针再次指向空的位置。
-
满栈(Full)
栈指针指向的地址有数据,在栈中存储数据时,需要先将栈指针,指向一个空的位置,然后在存储数据。
-
增栈(Ascending)
栈指针向高地址方向移动
-
减栈(Descending)
栈指针向低地址方向移动
操作栈的方式有四种
满增栈 满减栈 空增栈 空减栈
FA:Full Ascending 满增(FA)
FD:Full Descending 满减(FD)
EA:Empty Ascending 空增(EA)
ED:空减
ARM默认采用的是满减栈
stmfd/ldmfd
sp!, {寄存器列表}
stmfd sp!, {r1-r5}(写) (压栈)
更新栈指针指向的地址空间
ldmfd sp!, {r6-r10}(读) (出栈)
特殊:
stmfd sp!, {r1-r5,lr}(写) (压栈)
ldmfd sp!, {r6-r10,pc}(读) (出栈) //r1-r5出栈给r6-r10, 将lr的值出栈给pc
.text
.globl _start
_start:
/*
@1.数据处理指令
@1》数据搬移指令 mov
mov r0,#0x1
mov r1,#2
@mov r2,#0x00103000 不是立即数
mov r3,#0x00108000 @是立即数据
@mov r3,#0x12345678
@error: invalid constant (12345678) after fixup
@#0x1是立即数,携带在指令的数据,取指令的时候
@可以同时将数据取过来使用,读取数据快
@判断那些是立即数:将一个数据转化为二进制,所有的1组合起来可以
@形成一个0-255之间的数据,且将这个数据循环右移偶数位可以得到
@这个数据本身得数叫立即数。
@2》指令
ldr r4,=0x12345678
mov r5,#0xfffffff
@mvn r5,#0xf0000000 @按位取反指令
@3》算数运算
mov r0,#1
ldr r1,=0xffffffff
adds r2,r1,#0x2
adds r3,r1,r0
@举例:两个64位数据得加减运算
@0x00000030 fffffffe
@0x00000041 00000005
ldr r0,=0x00000030
ldr r1,=0xfffffffe
ldr r2,=0x00000041
ldr r3,=0x00000005
mov r8,#6
mov r9,#5
mul r10,r8,r9
adds r5,r1,r3
adcs r4,r0,r2
adc r11,r8,r9
subs r7,r3,r1
sbc r6,r2,r0
@2.跳转指令 本质修改pc的值
@mov pc,#0x00000005
@b(不携带返回的跳转) bl(携带返回的跳转)-会自动更新lr的值
ldr r0,=0x00000030
ldr r1,=0xfffffffe
bl loop
ldr r2,=0x00000041
ldr r3,=0x00000005
b stop
loop:
mov r8,#6
mov r9,#5
mov pc,lr
@3.Load/Store指令
@1》单寄存器操作指令 ldr/str
ldr r0,=0x40000100
ldr r1,=0x12345678
@ str r1,[r0] @将r1的值写道r0保存的内存地址中
@ ldr r2,[r0] @将r0保存的内存地址中的值读到r2中
@str r1,[r0,#8] @将r1的值写道r0+8内存地址中
@str r1,[r0],#8 @将r1的值写道r0保存的内存地址中,r0=r0+8
str r1,[r0,#4]!@将r1的值写道r0+4内存地址中,且r0=r0+4
@2》多寄存器操作指令 ldm/stm
ldr r0,=0x40000100
ldr r1,=0x11234567
ldr r2,=0x22234567
ldr r3,=0x33234567
bl add
ldr r4,=0x44234567
ldr r7,=0x77234567
ldr r8,=0x88234567
@ stm r0!,{r1-r4,r7,r8}
stm r0,{r1-r4,r7,r8}
ldr r1,=0xffffffff
ldm r0,{r3,r1,r4,r6,r2,r5}
*/
@3》栈的操作指令 stmfd ldmfd sp
ldr sp,=0x40000200
ldr r1,=0x11234567
ldr r2,=0x22234567
ldr r3,=0x33234567
ldr r4,=0x44234567
ldr r7,=0x77234567
ldr r8,=0x88234567
stmfd sp!,{r1-r4,r7-r8}
ldr r1,=0x1
ldr r2,=0x2
ldr r3,=0x3
ldr r4,=0x4
ldr r7,=0x7
ldr r8,=0x8
ldmfd sp!,{r1-r4,r7-r8}
stop:
b stop @while(1)
.end
2.4 状态寄存器指令
对CPSR进行读写操作//其他都不能动CPSR (SWI 指令是linux内核有,所以arm为了匹配才有的指令)(CPSR保存cpu的状态、模式、中断中断开关、运算状态,非常重要,不能任意更改,只有一类指令能操作这个寄存器)
1》读cpsr 指令mrs
2》写cpsr 指令 msr :一般情况不能修改cpsr,只能用msr命令修改,user模式下不能切换到其他模式。
Note: To modify the control field of CPSR ( bit [ 7 :0 ]) , you must specify which area to modify when modifying CPSR
In USER mode, the value of CPSR cannot be modified to prevent applications from modifying the CPU state and protect the operating system
CPSR_C modifies the lower eight ctrl (control) field of CPSR , and generally only modifies the C field
2.5 Abnormal interrupt generation instruction (in case of exception handling)
Trigger soft interrupt, often used in kernel system calls //SWI: soft interrupt
Verification of the processing code that actually triggers the soft interrupt:
3. Exception source and handling process
- Pattern review:
ARM7-11 有7种基本工作模式:(不同的模式下干不同的事,效率高)
User : 非特权模式,大部分任务执行在这种模式
FIQ : 当一个高优先级(fast) 中断产生时将会进入这种模式
IRQ : 当一个低优先级(normal) 中断产生时将会进入这种模式
Supervisor : 当复位或软中断指令执行时将会进入这种模式
Abort : 当存取异常时将会进入这种模式
Undef : 当执行未定义指令时会进入这种模式
System : 使用和User模式相同寄存器集的特权模式
Cortex-A特有模式:
Monitor : 是为了安全而扩展出的用于执行安全监控代码的模式;也是一种特权模式
3.1 Abnormal
Exception is the most important knowledge point to understand the operation of CPU. Almost every processor supports specific exception handling. Interrupt is one of the exceptions. Sometimes we measure the real-time performance of an operating system by looking at the shortest response interrupt time of the os and the number of response interrupts per unit time.
Note: When the processor encounters an exception, it will suspend the current program and turn to handle the exception (execute the exception handler), and return to the code interrupted by the exception to continue execution after the processing is completed
3.2 Exception sources
There are 7 events that lead to exceptions: FIQ, IR Q (most contacted), Reset (reset), soft interrupt, DataAbort, PrefetchAbort, Undef.
1》reset复位异常 (svc)
当CPU刚上电时或按下reset重启键之后进入该异常,该异常在管理模式下处理。
2》irq/fiq一般/快速中断请求 (irq、fiq)
CPU和外部设备是分别独立的硬件执行单元,CPU对全部设备进行管理和资源调度处理,CPU要想知道外部设备的运行状态,要么CPU定时的去查看外部设备特定寄存器,要么让外部设备在出现需要CPU干涉处理时“打断”CPU,让它来处理外部设备的请求,毫无疑问第二种方式更合理,可以让CPU“专心”去工作,这里的“打断”操作就叫做中断请求,根据请求的紧急情况,中断请求分一般中断和快速中断,快速中断具有最高中断优先级和最小的中断延迟,通常用于处理高速数据传输及通道的中数据恢复处理,如DMA等,绝大部分外设使用一般中断请求。
3》预取指令中止异常 PrefetchAbort(abort)
该异常发生在CPU流水线取指阶段,如果目标指令地址是非法地址进入该异常,该异常在中止异常模式下处理。
4》未定义指令异常(Undef)
该异常发生在流水线技术里的译码阶段,如果当前指令不能被识别为有效指令,产生未定义指令异常,该异常在未定义异常模式下处理。
5》软件中断指令(swi)异常 (svc)
该异常是应用程序自己调用时产生的,用于用户程序申请访问硬件资源时,例如:printf()打印函数,要将用户数据打印到显示器上,用户程序要想实现打印必须申请使用显示器,而用户程序又没有外设硬件的使用权,只能通过使用软件中断指令切换到内核态,通过操作系统内核代码来访问外设硬件,内核态是工作在特权模式下,操作系统在特权模式下完成将用户数据打印到显示器上。这样做的目的无非是为了保护操作系统的安全和硬件资源的合理使用,该异常在管理模式下处理。
6》数据中止访问异常 DataAbort (Abort)
该异常发生在要访问数据地址不存在或者为非法地址时,该异常在中止异常模式下处理。
3.3 Exception handling (important)
Steps (completed automatically):
1. Copy CPSR to SPSR_
Save the cpsr of the running mode to the spsr in the corresponding abnormal mode.
2. Modify CPSR: (Modify cpsr to switch to the corresponding abnormal mode)
a. Enter the ARM state (mandatory)
b. Enter the corresponding abnormal mode (switch mode)
c. Prohibit the corresponding interruption (another exception will not interrupt the current exception handling)
3. Save the return address to LR_
Before jumping to exception handling (modified pc), save the address of the next instruction of pc in lr, and then modify pc to jump to the corresponding exception handling position.
4. Set the PC to the corresponding exception vector address (jump to the corresponding position in the exception vector table)
Modify the PC to switch to the exception handling position
步骤详解:
1》保存执行状态
当前程序的执行状态是保存在CPSR里面的,异常发生时,要保存当前的CPSR里的执行状态到异常模式里的SPSR里,将来异常返回时,恢复回CPSR,恢复执行状态。
2》模式切换
硬件自动根据当前的异常类型,将异常码写入CPSR里的M[4:0]模式位,这样CPU就进入了对应异常模式下。不管是在ARM状态下还是在THUMB状态下发生异常,都会强制切换到ARM状态下进行异常的处理,这是由硬件自动完成的,将CPSR[5] 设置为 0。同时,CPU会关闭中断IRQ(设置CPSR 寄存器I位),防止中断进入,如果当前是快速中断FIQ异常,关闭快速中断(设置CPSR寄存器F位)。
3》保存返回地址
当前程序被异常打断,切换到异常处理程序里,异常处理完之后,返回当前被打断模式继续执行,因此必须要保存当前执行指令的下一条指令的地址到LR_excep(异常模式下LR,并不存在LR_excep寄存器,为方便读者理解加上_excep,以下道理相同),由于异常模式不同以及ARM内核采用流水线技术,异常处理程序里要根据异常模式计算返回地址。
4》跳入异常向量表
该操作是CPU硬件自动完成的,当异常发生时,CPU强制将PC的值修改为一个固定内存地址,这个固定地址叫做异常向量。
3.4 Exception vector table
The entire exception handling process is automatically completed, and there will be corresponding hardware inside ARM to automatically complete it, that is, it is fixed when designing ARM, so each exception handling will have a fixed address. The address of the exception vector table cannot be changed, and some new processors can be changed through the coprocessor later.
If linu wants to modify the start address of the exception vector table, it needs to use the coprocessor to complete it. Generally, the address of the exception vector table will be set at 0xFFFF0000 (if IRQ is generated, the exception vector address will be 0xFFFF0018)
Summarize:
The exception vector table is a space in memory
Four bytes of storage are allocated for each exception source in the exception vector table
When an exception occurs, the value of PC will automatically become the address of the exception source in the exception vector table
We write a jump instruction in the corresponding position of the exception vector table to make it jump to the exception handler entry
The base address of the exception vector table for the Cortex-A series processor can be set by the coprocessor CP15
详解:(帮助理解)
1》跳入异常向量表操作是异常发生时,硬件自动完成的,剩下的异常处理任务完全交给了程序员。由上表可知,异常向量是一个固定的内存地址,我们可以通过向该地址处写一条跳转指令,让它跳向我们自己定义的异常处理程序的入口,就可以完成异常处理了。
2》正是由于异常向量表的存在,才让硬件异常处理和程序员自定义处理程序有机联系起来。异常向量表里0x00000000地址处是reset复位异常,之所以它为0地址,是因为CPU在上电时自动从0地址处加载指令,由此可见将复位异常安装在此地址处也是前后接合起来设计的,其后面分别是其余7种异常向量,每种异常向量都占有四个字节,正好是一条指令的大小,最后一个异常是快速中断异常,将其安装在此也有它的意义,在0x0000001C地址处可以直接存放快速中断的处理程序,不用设置跳转指令,这样可以节省一个时钟周期,加快快速中断处理时间。
3》存储器映射地址0x00000000是为向量表保留的。在有些处理器中,向量表可以选择定位在高地址0xFFFF0000处【可以通过协处理器指令配置】,当今操作系统为了控制内存访问权限,通常会开启虚拟内存,开启了虚拟内存之后,内存的开始空间通常为内核进程空间,和页表空间,异常向量表不能再安装在0地址处了。
3.5 Install and set exception vector table and save field instructions
2》 安装异常向量表
我们可以通过简单的使用下面的指令来安装异常向量表:
b reset ; 跳入reset处理程序
b undef_handler ;跳入未定义处理程序
b swi_handler ;跳入软中断处理程序
b pref_handler ;跳入预取指令处理程序
b data_handler ;跳入数据访问中止处理程序
b res ; 跳入未使用程序
b irq_handler ;跳入中断处理程序
b fiq_handler ;跳入快速中断处理程序
通常安装完异常向量表,跳到我们自己定义的处理程序入口,这时我们还没有保存被打断程序的现场,因此在异常处理程序的入口里先要保存打断程序现场。
2》 保存执行现场
异常处理程序最开始,要保存被打断程序的执行现场,程序的执行现场无非就是保存当前操作寄存器里的数据,可以通过下面的栈操作指令实现保存现场:
stmfd sp!, {r0-r1,lr}
需要注意的是,在跳转到异常处理程序入口时,已经切换到对应异常模式下了,因此这里的SP是异常模式下的SP了,所以被打断程序现场(寄存器数据)是保存在异常模式下的栈里,上述指令将R0~R1全部都保存到了异常模式栈,最后将修改完的被打断程序返回地址入栈保存,之所以保存该返回地址就是将来可以通过类似:MOV PC, LR的指令,返回用户程序继续执行。
异常发生后,要针对异常类型进行处理,因此,每种异常都有自己的异常处理程序,中断异常处理过程通过系统中断处理来进行分析。
3.6 Return of exception handling (completed by the user himself)
Write the exception handling code yourself.
1) Restore CPSR from SPSR_ to restore the processor to the state before the exception
2) Restore the PC from LR_, so that the program returns to the position interrupted by the exception to continue execution
Note: CPSR always saves the current program running status, and SPSR only backs up CPSR when it is abnormal
异常处理完成之后,返回被打断程序继续执行,具体操作如下:
1-》恢复被打断程序运行时寄存器数据(从栈中恢复)
2-》恢复程序运行时状态CPSR(恢复spsr_<mode>到cpsr)
3-》通过进入异常时保存的返回地址,返回到被打断程序继续执行(恢复lr<mode>到pc)
3.7 Correspondence between abnormal source and abnormal mode
Exception source: FIQ IRQ Reset/soft interrupt DataAbort/PrefetchAbort Undef
Abnormal mode: FIQ IRQ SVC Abort Undef
3.8 Abnormal Response Priority
Reset、Data Abort、FIQ、IRQ、Prefetch Abort、SWI、Undefined instruction
high - low
4. Coprocessor instruction + pseudo-instruction + pseudo-operation (understand)
- coprocessor instructions
Instructions to operate the coprocessor (not used) (to assist the cpu to process data)
1.数据运算
2.内存访问
3.与主处理器通信
MRC 将协处理器中寄存器的内容读取到ARM处理器的寄存器中
MCR 将ARM理器中寄存器的内容读取到协处理器的寄存器中
- directive
Essence: It is not an instruction itself, but the cpu is replaced with an equivalent operation. (may be programmed as)
举例1:延时一个指令周期(耗时一条指令的时间) (cpu没有这个指令)
NOP ;执行NOP和MOV R0,R0一个效果,执行NOP,cpu替换成MOV R0,R0。
MOV R0,R0
LDR的两种形式
;->指令
LDR R1,[R2]
;->伪指令
LDR R1,=0x12345678 ;R1 = 0x12345678
;可以将任何一个32bit的数据放入寄存器
- fake operation
The instructions are specified by the arm company, and the pseudo-operations are specified by the compiler. Different compilers have different pseudo-operation instructions.
(We learn linux, use linux compiler)
5. Compile Supplementary Content
位运算指令 and orr eor bic
格式:
<opcode><code>{s} Rd, Rn, oprand2
mov r0, #0xFF
[7:4]bits 清零
and r1, r0, #0xFFFFFF0F
and r1, r0, #(~(0xF << 4))
[11:8]bits 置1
orr r2, r0, #(0xF << 8)
[9:6]bits 取反
eor r3, r0, #(0xF << 6)
bic 位清除指令,把哪位清0,就给哪位写1
[4:0]bits 清零
bic r4, r0, #0x1F
比较指令 cmp 比较两个数大小
格式:
cmp Rn, oprand2
没有目标寄存器,指令的结果影响cpsr的nzcv位
并且不需要加s
本质:做减法运算
例子:比较r0和r1的大小,
如果r0 > r1,r0 = r0 - r1
如果r0 < r1,r1 = r1 - r0
mov r0, #5
mov r1, #9
cmp r0, r1
subhi r0, r0, r1
subcc r1, r1, r0
6. CPU control hardware principle
Of all the instructions we have learned, among the six major instructions, only memory access instructions can access content other than the CPU. How does the cpu control the hardware? *****load/store command--"operate 4G memory
Any chip has an address mapping table. Tell you how the address space is mapped so that we can find the corresponding hardware address.
Our SOC model is S5P6818, and the corresponding chip user manual is: (recommended: quick PDF reader)
One of the chapters is: You can see the address mapping relationship in a memory map or a table in the Memory Controller.
Hardware control principle: *****
The CPU cannot directly control the hardware, and the hardware is controlled by its corresponding controller (register)
Each controller (register) is mapped to a section of space within the CPU address range
The CPU achieves indirect control of the hardware by reading and writing the controller (register)
Hardware board introduction:
There is a map.lds in the provided project template, which guides how the program is arranged. To run a bare-metal program, you first need to read this file.
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x43c00000; //起始地址,异常向量表的起始地址
. = ALIGN(4);
.text : //代码段
{
./Start/start.o(.text)
//运行的第一段代码是start.s编译生成的.o
*(.text)
}
. = ALIGN(4);
.rodata : //只读
{ *(.rodata) }
. = ALIGN(4);
.data : //.data段
{ *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss ://.bss
{ *(.bss) }
__bss_end__ = .;
}
7. start start code
start.S文件:
.text @ .:伪操作
.arm
.global _start
_start: @第一段代码
@ 第一条指令,内存地址0x43c00000
@ 异常向量表
b reset @直接通过跳转指令跳转到reset
b .
b .
b .
b .
b .
b irq @ IRQ异常对应的位置
b .
/* The actual reset code */
reset:
/* 将异常向量表的基地址重定向到0x43c00000,即这段代码执行后遇到IRQ后PC的值会变成0x43c00000 + 0x18 */
ldr r0,=0x43c00000 @伪指令,将0x43c00000 放到r0中
mcr p15,0,r0,c12,c0,0 @ Vector Base Address Register
@p15是一个协处理器,管内存的,c12,c0都是p15里边的寄存器。c12管异常向量表的位置的。
mrc p15, 0, r0, c1, c0, 0 @mrc是协处理器指令
bic r0, #(1<<13)
mcr p15, 0, r0, c1, c0, 0
/* 设置CPU为SVC模式 32位数据处理 ARM状态 */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr, r0
/* Enable NEON/VFP unit */ 设置协处理器,浮点运算的协处理器
mrc p15, #0, r1, c1, c0, #2
orr r1, r1, #(0xf << 20)
mcr p15, #0, r1, c1, c0, #2
mov r1, #0
mcr p15, #0, r1, c7, c5, #4
mov r0, #0x40000000
fmxr fpexc, r0
/* Cache init */协处理器设置高速缓存,比内存读取更快
mrc p15, 0, r0, c0, c0, 0
and r1, r0, #0x00f00000
and r2, r0, #0x0000000f
orr r2, r2, r1, lsr #20-4
cmp r2, #0x30
mrceq p15, 0, r0, c1, c0, 1
orreq r0, r0, #0x6
mcreq p15, 0, r0, c1, c0, 1
/* Invalidate L1 I/D */
mov r0, #0
mcr p15, 0, r0, c8, c7, 0
mcr p15, 0, r0, c7, c5, 0
/* 关闭MMU,所以该代码运行在实际的物理地址上 */mmc负责虚拟地址和虚拟地址转换的,关闭,操作的是实际的物理地址。
/*mmu:内存管理单元,基于操作系统才有*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000
bic r0, r0, #0x00000007
orr r0, r0, #0x00001000
orr r0, r0, #0x00000002
orr r0, r0, #0x00000800
mcr p15, 0, r0, c1, c0, 0
/* 初始化各个模式下的栈 */每个模式下都有自己栈指针,即每个模式都有自己的栈地址。每个模式下的栈初始化都需要切换到这模式下。
/*********svc mode stack************/
mrs r0, cpsr
bic r0, r0, #0xdf
orr r1, r0, #0xd3
msr cpsr, r1
ldr sp, _stack_svc_end
/**********undef mode stack**********/
orr r1, r0, #0xdb
msr cpsr, r1
ldr sp, _stack_und_end
/*********abort mode stack*********/
orr r1, r0, #0xd7
msr cpsr, r1
ldr sp, _stack_abt_end
/*********irq mode stack************/
orr r1, r0, #0xd2
msr cpsr, r1
ldr sp, _stack_irq_end
/*********fiq mode stack************/
orr r1, r0, #0xd1
msr cpsr, r1
ldr sp, _stack_fiq_end
/*********user mode stack************/
orr r1, r0, #0x10
msr cpsr, r1
ldr sp, _stack_usr_end
思考:为什么最后初始化user模式下的栈?因为user模式下不能再切换到其他模式了。
/*Close Watch Dog Timer*/
ldr r0, =0xC0012004
ldr r1, [r0]
orr r1, r1, #0x04000000
str r1, [r0]
ldr r0, =0xC0019000
ldr r1, [r0]
and r1, r1, #0xFFFFFFDF
str r1, [r0]
/* USER模式下的栈放在最后初始化 */
/*
1.因为USER模式不能切换成其他模式
2.main执行时CPU处于USER模式
*/
/* Call _main */
b main @跳转到main函数运行代码。
/* IRQ的异常处理程序 */
irq:
@ 遇到IRQ异常后CPU自动保存的返回地址是遇到异常时指令下下条指令的地址,所以我们需要人为修正
sub lr,lr,#4
@ 因为IRQ模式下使用的寄存器与USER模式相同(R0-R12),所以在处理异常前要先将之前的寄存器压栈保护
stmfd sp!,{
r0-r12,lr}
@ 异常处理,跳转到C处理(也可以用汇编在这写。)
bl do_irq
@ 异常返回
@ 1.将R0-R12寄存器的值出栈恢复
@ 2.将SPSR的值给CPSR,回到了USER模式
@ 3.将LR的值给PC,实现程序的返回
ldmfd sp!,{
r0-r12,pc}^
_stack_svc_end: .long stack_svc + 512
_stack_und_end: .long stack_und + 512
_stack_abt_end: .long stack_abt + 512
_stack_irq_end: .long stack_irq + 512
_stack_fiq_end: .long stack_fiq + 512
_stack_usr_end: .long stack_usr + 512
.data
stack_svc: .space 512
stack_und: .space 512
stack_abt: .space 512
stack_irq: .space 512
stack_fiq: .space 512
stack_usr: .space 512
8. LED experiment
step:
1. Through the schematic diagram of the bottom board, find the corresponding circuit and analyze how the light turns on. Give a high level light.
2. Find the corresponding pins through the principle of the core board.
3. Determine the pin function through the manual -- "GPIO, output a high level light on
There are 160 GPIOs in total, divided into 5 categories: ABCDE, 32 in each category, numbered 0-31
Control Register-->Settings
1) Select the function of GPIOA28 -- "The corresponding register is GPIOAALTFN1
地址:0xC001A024 第24位和25位置为0就先择GPIO功能
2)设置GPIOA28为输出功能--》GPIOAOUTENB
地址:0xC001A004 第28位置为1是输出模式
3)设置GPIOA28为输出高电平--》GPIOAOUT
地址:0xC001A000 第28位置为1红灯亮
本地开发和交叉开发
本地开发:PC端编写代码,编译代码,运行代码
交叉开发:PC端编写代码,编译代码,Target(目标板)运行代码
PC Target
X86架构 arm架构
- 使用交叉编译工具链将源码编译成支持ARM架构的可执行程序
- 再将可执行程序拷贝到目标板上运行。
PC:gcc
交叉编译工具链:arm-none-linux-gnueabi-gcc
注:arm-none-linux-gnueabi-:交叉编译工具链的名字,名字就是一个代号,
在工作中用的不一定是这个,不同的公司做的交叉编译工具链的名字不同
安装交叉编译工具链
(1)获取交叉编译工具链
一般交叉编译工具链和uboot和linux内核源码,都具有配套的关系。
- 自己去gnu官网获取交叉编译工具链的源码,自己进行编译生成对应的交叉编译工具链。不推荐:编译过程很繁琐
- 直接从芯片厂家获取交叉编译工具链
- 直接跟开发板的生成厂家获取交叉编译工具链
- 直接找主管获取交叉编译工具链(单位)
**********************************************
(2) Install the cross-compilation toolchain:
installation steps:
1. Create an arm-gcc directory under the home directory of u Ubuntu
$ cd ~
$ mkdir arm-gcc
$ cd arm-gcc
Copy the gcc-4.9.4.tar.xz in " 1. Tool software \2. Assembly environment construction \3. Compilation tools" in the student materials to the arm-gcc directory and decompress it
$ tar -xvf gcc-4.9.4.tar.xz
2. Add the cross-compilation toolchain to the global environment variable to make it globally available
Open the configuration file /etc/bash.bashrc
$ sudo you /etc/bash.bashrc
Add the following to its last line
export PATH=$PATH:/home/hq/arm-gcc/gcc-4.9.4/bin
Check the environment variables in the system: printenv/env
Restart the configuration file to make the configuration take effect
$ source /etc/bash.bashrc
3. Test whether the cross-compilation toolchain is successfully installed
arm-none-linux-gnueabi-gcc -v
Print the following content, indicating success
gcc version 4.9.4 (Sourcery G++ Lite 2010.09-50)
LED experiment
- Analyzing Circuit Diagrams
- The idea of analyzing the circuit diagram: from the peripheral (floor)---"SOC (core board) analysis
Analyze LEDs
- Find the position of the led light on the circuit board
- There will be white words next to the LED light, this white word is silk screen , the word next to the LED light is the number of the LED light
- Open the schematic diagram of the bottom board, and search for the LED number (RGB) on the schematic diagram
Analyze the circuit diagram of led
Common anode three-color diode: three diodes , the anodes are connected together
- RGB_R/RGB_G/RGB_B represent the network label
The same name of the network label means that it has the same electrical connection properties, reflected on the circuit board, and they are connected together by wires
- According to the network label to the core board schematic diagram, find which pin of the soc drives the LED light
- 2. Read the chip manual (black box test, white box test )
- Working registers: R0-R15, cpsr, spsr, provided by ARM, no address
- Control register: It is a space of memory with an address provided by the chip manufacturer. Registers are used in the GPIO chapter, so read the GPIO chapter when reading the chip manual, there must be the use and function realization of related registers.
- We only need to write or read a value to the control register to allow our processor to complete certain functions. This is the idea of our software programming to control hardware.
1》GPIOxOUT: control pin output high and low level
RED_LED--->GPIOA28
GPIOAOUT ---> 0xC001A000
GPIOA28 output high level:
GPIOAOUT[28] <-- write-- 1
GPIOA28 output low level:
GPIOAOUT[28] <-- write -- 0
2 "GPIOxOUTENB: Control the input and output mode of the pin
GPIOAOUTENB ---> 0xC001A004
Set the GPIOA28 pin to output mode:
GPIOAOUTENB[28] <-- write -- 1
3》GPIOxALTFN: Control pin function selection
GPIOAALTFN1 ---> 0xC001A024
Set GPIOA28 pin as GPIO function:
GPIOAALTFN1[25:24] <--写-- 0b00
00 = ALT Function0
01 = ALT Function1
10 = ALT Function2
11 = ALT Function3
GPIO pin function selection: every two bits control a GPIO pin,
The corresponding functions can be found in the chip manual
2.3 chapter to view.
3. Write the code
1. Set GPIOA28 as GPIO function
2. Set GPIOA28 as output function
while(1)
{
Set GPIOA28 output high level
time delay
Set GPIOA28 output low level
time delay
}
#define GPIOAALTFN1 ((unsigned int *)0xc001a024)
#define GPIOAOUTENB ((unsigned int *)0xc001a004)
#define GPIOAOUT ((unsigned int *)0xc001a000)
#define GPIOEALTFN0 ((unsigned int *)0xc001e020)
#define GPIOEOUTENB ((unsigned int *)0xc001e004)
#define GPIOEOUT ((unsigned int *)0xc001e000)
#define GPIOBALTFN0 ((unsigned int *)0xc001b020)
#define GPIOBOUTENB ((unsigned int *)0xc001b004)
#define GPIOBOUT ((unsigned int *)0xc001b000)
void delay_ms(unsigned int ms)
{
unsigned int i,j;
for(i = 0; i < ms; i++)
for(j = 0; j < 1800; j++);
}
int main()
{
*GPIOAALTFN1 &= (~(3<<24));
*GPIOAOUTENB |= (1<<28);
*GPIOEALTFN0 &= (~(3<<26));
*GPIOEOUTENB |= (1<<13);
*GPIOBALTFN0 |= (1<<25);
*GPIOBALTFN0 &= (~(1<<24));
*GPIOBOUTENB |= (1<<12);
while(1)
{
*GPIOAOUT |= (1<<28);
delay_ms(2000);
*GPIOAOUT &=(~(1<<28));
delay_ms(2000);
*GPIOEOUT |= (1<<13);
delay_ms(2000);
*GPIOEOUT &=(~(1<<13));
delay_ms(2000);
*GPIOBOUT |= (1<<12);
delay_ms(2000);
*GPIOBOUT &=(~(1<<12));
delay_ms(2000);
}
return 0;
}
4. Download, debug and modify bugs
1》Copy the .bin file to windows
2"Hardware connection between the development board and the computer
Plug the USB end of the serial cable into the USB port of the computer
Insert the serial end of the serial line into the UART0 port of the development board
Plug in the development board
3》Configure Windows HyperTerminal
If the serial port line is used for the first time, the serial port driver needs to be installed
The serial port driver file is in the data
Configure HyperTerminal:
You can view the instructions for configuring HyperTerminal
There are in the data
In the device manager, check the port number used by the serial cable
Configure port properties:
Baud rate: 115200
Data bits: 8
stop bits: 1
Check digit: none
Flow control: no
4》Power on the development board, the HyperTerminal will print information
Press any key before the countdown reaches 0 to enter the FS6818# interface
Execute the command loadb 0x43c00000 --"Download the binary file to memory 0x43c00000
Send--"Send File--"Select the .bin file to download, select the Kermit protocol --" Confirm the download
Execute the command: go 0x43c00000 --" go to 0x43c00000 to run the code
If you need to download the code again, repeat step 4
Modify the countdown time:
setenv bootdelay 60
saveenv
3. System transplantation
1. Basic concepts
1.1 What is system transplantation?
It is to port the operating system to the corresponding hardware platform
Porting the linux system to the development board
(Computer-Windows system; mobile phone-Android system, the application program runs,
Only when the system is running can we do driver development)
1.2 Why is the learning system transplanted?
Software and hardware can be cut
The company's new hardware platform---》
Transplant linux system to hardware platform
1.3 What is the purpose of learning system transplantation?
1》Work needs
2》Build a system environment for the following driver development
3"The development of embedded (soft + hard) application layer is inseparable from the operating system
1.4 How to learn about system transplantation?
Just learn the process of transplantation
1.5 Migration process
1. Environment construction
2. uboot porting (B IOS )
The main functions of uboot are as follows
1) Initialize some hardware to prepare for the follow-up
2) Boot and load the kernel
3) Pass parameters to the kernel
4) Execute user commands
BIOS--"boot system boot (SD card boot, hard disk boot, U disk boot)
Some hardware is initialized
3. Linux kernel porting
The kernel in windows is similar
windows=kernel+library+graphical interface+file system
4. Migration of root file system
(Windows C drive, d, etc., Linux is an inverted tree)
2. Local development and cross-development
Local development: write code on PC, compile code, run code
Cross-development: write codes on the PC side, compile the codes, and run the codes on the Target (target board)
(Question: Hasn’t the system been ported to the target board? Why use cross-development, can’t you write and compile code directly on the target board?)
PC Target
X86 architecture arm architecture
Use the cross-compilation tool chain to compile the source code into an executable program that supports the ARM architecture, and then copy the executable program to the target board to run.
PC:gcc
Cross-compilation toolchain: arm-none-linux-gnueabi-gcc
arm-none-linux-gnueabi-: the name of the cross-compilation toolchain, the name is a code name
(This is not necessarily the one used at work, the cross-compilation toolchains made by different companies have different names)
3. Install the cross-compilation toolchain
1. Get the cross-compilation toolchain
Generally, the cross-compilation tool chain and uboot and linux kernel source codes have a supporting relationship.
1"Go to the gnu official website to obtain the source code of the cross-compilation toolchain, and compile it by yourself to generate the corresponding cross-compilation toolchain. Not recommended: the compilation process is cumbersome
2" Obtain the cross-compilation toolchain directly from the chip manufacturer
3" Directly obtain the cross-compilation toolchain from the manufacturer of the development board
4" Directly find the supervisor to obtain the cross-compilation toolchain (unit)
**********************************************
2. Install the cross-compilation toolchain:
Compile the code into an executable program for the ARM architecture
installation steps:
1. In the ubuntu home directory (~), create a toolchain
mkdir toolchain
2. Copy gcc-4.6.4.tar.xz to the toolchain directory
cp dir/ gcc-4.6.4.tar.xz ~/toolchain
3. Unzip the cross-compilation toolchain
tar -vxf gcc-4.6.4.tar.xz
4. Configure environment variables
Open sudo vi /etc/bash.bashrc
Add the following on the last line:
export PATH=$PATH:/home/linux/toolchain/gcc-4.6.4/bin/
Modify to your own path
5. Make environment variables take effect immediately
source /etc/bash.bashrc
6. Test whether the cross-compilation toolchain is successfully installed
arm-none-linux-gnueabi-gcc -v
Print the following content, indicating success
gcc version 4.6.4 (Sourcery G++ Lite 2010.09-50)
4. How to make hardware connection between PC and Target
1. Serial port line : print various debugging information to the serial port tool
2. Network cable : used to download images of uboot , kernel and root file system (executable program)
Mount the root file system over the network
Need to install the corresponding server in ubuntu
Download files through network cable---"tftp service
Mount the root file system (board (path) ) through the network cable --- "nfs service
The client uboot and kernel source code of tftp service and nfs service have been installed by default
(server and client)
5. Install tftp service
Tftp is a simple text file transfer protocol based on the udp protocol
1. Check whether the tftp service is installed on ubuntu
dpkg -s tftpd-hpa
Printing the following indicates that the tftp service is installed:
Architecture: i386
Source: tftp-hpa
Version: 5.2-7ubuntu3.1
2. Install tftp service
(Prerequisite: ubuntu must be able to connect to the external network)
sudo apt-get update update-source
sudo apt-get install -f update dependencies
sudo apt-get install tftpd-hpa tftp-hpa
3. Configure tftp service
1. Create a tftpboot folder in your home directory
mkdir tftpboot
Purpose: The tftpboot directory stores the files you want to download to
Executables on the board
2. Modify the permissions of tftpboot
sudo chmod 777 tftpboot
3. Configure the environment variables of the tftp service
Open sudo vi /etc/default/tftpd-hpa
Modify the following:
1 # /etc/default/tftpd-hpa
2
3 TFTP_USERNAME="tftp"
tftp user name, no need to modify
4 TFTP_DIRECTORY="/home/hq/tftpboot"
The storage path of files downloaded by tftp service needs to be modified
Change to your own corresponding tftpboot path
5 TFTP_ADDRESS="0.0.0.0:69"
The default port number of 69 used by tftp service
6 TFTP_OPTIONS="-c -s -l"
The parameters of the tftp service, this needs to be modified
4. Restart the tftp service
1. sudo service tftpd-hpa re start to start the TFTP service
2. sudo service tftpd-hpa restart Restart the TFTP service
5. Locally test whether the tftp service is successfully installed
(The process of pinging other machines: the local network card driver, to the kernel, the kernel processes this driver, sends the ping packet to the network card of another system, processes this packet, puts the content of the packet into the protocol station (inside the kernel), recognizes the ping packet, and then passes the network card driver to the network card, and returns a packet (all will go to the real physical hardware))
$ tftp 127.0.0.1
tftp> get 1.txt # Download from the tftpboot directory
# 1.txt file to the current directory (this example is,
Touch 1.TXT under tftpboot, and then run it under the home directory)
tftp 127.0.0.1;然后get 1.txt,就把tftpboot下面的1.TXT下载到家目录下面了)
tftp> put 2.txt # 把当前目录中的2.txt文件
# 上传到tftpboot文件夹中
tftp> q <回车> 退出
6. 可能出现的问题
下载或上传是,一直卡,
原因:
- tftp服务安装成功,需要重启tftp服务
- tftp服务安装不成功
- 关闭windows和ubuntu的防火墙(防火墙会阻碍数据传输)
6. nfs服务的安装
Network File System
1. 检查nfs服务是否安装
dpkg -s nfs-kernel-server
2. 安装nfs服务(前提:可以上网)
sudo apt-get install nfs-kernel-server
3. 配置nfs服务
1》在家目录下创建nfs文件夹
mkdir nfs
2》设置文件夹的权限最大
sudo chmod 777 nfs
3》拷贝根文件系统到nfs目录下
根文件系统一会发给你们(rootfs-A53-ok.tar.xz)
cp /mnt/hgfs/share/rootfs-A53-ok.tar.xz ~/nfs
4》对根文件系统的压缩包进行解压缩
cd ~/nfs
tar -vxf rootfs-A53-ok.tar.xz
5》配置nfs服务的环境变量
sudo vi /etc/exports
在文件的最后一行添加以下内容:
/home/hq/nfs/rootfs/ *(rw,sync,no_subtree_check,no_root_squash)
解析:
/home/ hq /nfs/rootfs/: the path of your own root file system
*: All users, note: no space can appear between * and the following left bracket "(".
rw: read and write permissions
sync: synchronize files
no_subtree_check: Do not check file permissions for subdirectories
no_root_squash: If the client is the root user, then he has root permissions to the entire file
Note: Do not add # in front of this paragraph, # is the comment symbol in this file
4. Restart the nfs service
1. sudo service nfs-kernel-server start Start nfs service
2. sudo service nfs-kernel-server restart restart nfs service
5. Locally test whether the nfs service is successfully installed
1》Back to the home directory
cd ~
2》sudo mount -t nfs local IP address: /home/ hq /nfs/rootfs/ /mnt
(Local IP address: ubuntu's IP)
sudo mount -t nfs 172.20.10.100:/home/hq/nfs/rootfs /mnt
sudo mount -t nfs 10.60.138.66:/home/hq/nfs/rootfs/ /mnt
nfs: use the nfs service, set the local IP address: /home/ hq /nfs/rootfs/
The file is mounted to the /mnt directory
3》Check whether the /mnt directory is mounted successfully
cd /mnt
ls (At this time, the things under mnt are the same as those under rootfs)
4》Uninstall the mounted file
sudo umount /mnt
Note: Uninstallation commands cannot be executed in the /mnt directory
7. Deploy the operating system to the development board
7.1 Deploy uboot
1 "New development board, everything is blank, make a SD card boot disk
1> Copy the sd card boot disk production tool to the toolchain directory of ubuntu
cp /mnt/hgfs/share/ sdtool . tar.xz ~/toolchain -raf
tar -xvf sdtool.tar.xz
2> Go to the sdtool directory
cd ~/toolchain/sdtool
3> Insert the sd card into the computer and let your ubuntu recognize the sd card
Note: You must use a card reader, not the SD card slot that comes with the computer
Insert the SD card into the card reader, insert the card reader into the computer,
Format the SD card under Windows .
Virtual Machine--"Removable Devices--"realtek USB3.0-CRW---"Connection
What are the files in the sdtool folder?
s5p6818-sdmmc.sh : script for burning uboot to sd card
ubootpak.bin: executable file of uboot
/dev/sdb: device file of sd card
4> Burn ubootpak.bin to SD card
execute command in ubuntu
sudo ./s5p6818-sdmmc.sh /dev/sdb ubootpak.bin
If the information is printed, the deployment is successful:
688+1 records in
689+0 records out
352768 bytes (353 kB) copied, 0.00914641 s, 38.6 MB/s
^_^ The image is fused successfully
Summary: report SD card read-only error,
Turn the switch in the SD card to the lock position,
The lock is close to the contact position of the sd card
5> Test whether the sd card is made successfully
Insert the sd card into the development board,
Set the DIP switch on the development board to start for the sd card,
The DIP switch is used to set the startup mode of uboot on the development board
OM1 OM2 OM3 Device
ON ON X nand flash
OFF ON X USB
ON OFF ON EMMC The flash of this development board is EMMC
OFF OFF OFF SD/TF
6> Power on the development board again and start successfully.
7.2 System deployment in the development stage
uboot mirror------》Flash(EMMC) SD
linux kernel image --"
Download directly to the development board memory via network (TFTP) and start the kernel
Root file system image --"
Directly mount the root file system through the network (NFS), (development)
Benefits: High efficiency, the number of reads and writes of Flash is limited .
7.3 Product Release System Deployment
uboot image---------->Flash
linux kernel image------>Flash
Root file system image ----->Flash
First download the image to the memory using tftp,
Then move from memory to flash,
Then move from flash to memory,
start from memory again
7.4 System deployment in the development stage
1. Put the kernel image uImage in the tftpboot directory
cp /mnt/hgfs/share/uImage ~/tftpboot
2. 使用tftpboot命令将uImage下载到0x480000000
tftp 0x48000000 uImage
3. 设置uboot的自动参数bootargs,
bootargs:启动linux内核是,uboot会将
bootargs后边的参数传递给内核,内核(wind系统)根据这些参数,去设置IP等东西内容为:
bootargs=root=/dev/nfs nfsroot=192.168.1.99:/home/hq/nfs/rootfs rw console=ttySAC0,115200 init=/linuxrc ip=192.168.1.222
root=/dev/nfs :根文件系统的类型
nfsroot=192.168.0.99:/home/hq/nfs/rootfs 根文件系统的服务器的IP和路径
rw :文件系统的可读可写的权限
console=ttySAC0,115200:
使用串口0实现内核和PC的数据的交互,波特率是115200
init=/linuxrc:启动内核之后,运行的1号进程
ip=192.168.1.250 :开发板的IP地址
本次bootargs环境变量设置为:
setenv bootargs root=/dev/nfs nfsroot=192.168.1.99:/home/hq/nfs/rootfs rw console=ttySAC0,115200 init=/linuxrc ip=192.168.1.222
saveenv
4. Use the bootm command to start the kernel
bootm kernel address root file system address device tree address
bootm 0x48000000
5. Set uboot to self-start mode, set the bootcmd environment variable
Format:
bootcmd=uboot command 1\;uboot command 2\;uboot command 3\;....
The above three commands will be executed in sequence until the end.
setenv bootcmd uboot command 1\;uboot command 2\;uboot command 3\;.....
eg:
setenv bootcmd tftp 0x48000000 uImage\;bootm 0x48000000
saveenv
6. Power on the development board again.
(press any key)
7.5 Product Release System Deployment
1. Put uboot in EMMC
How to put uboot into flash(EMMC)
( uboot is placed in SD, but when the mobile phone leaves the factory, the system cannot be installed in the SD card, right? The system is put in flash when the mobile phone leaves the factory)
Premise: SD card must burn a uboot in advance
1> Start uboot through the SD card and enter the FS6818# interface
2> Copy ubootpak.bin to the tftpboot directory
cp /mnt/hgfs/share/ubootpak.bin /home/hq/tftpboot
3> Burn ubootpak.bin into memory using tftp command
tftp 0x41000000 ubootpak.bin (run on the board, pay attention to the link network cable)
(Download is downloaded to memory, it does not exist when power off)
( The address 0x41000000 , 0x4 2 000000 is also ok)
4> update_mmc (move the data in the memory to emmc, emmc disk space)
update_mmc 2 2ndboot 0x41000000 0x200 0x78000
update_mmc
- type : 2ndboot | boot | raw | part
:flash device number EMMC: 2
: type 2ndboot
: The starting address of uboot in memory , in bytes
: The starting address of flash : in blocks
: How many pieces of space to download to flash (byte length)
Bytes transferred = 352296 (56028 hex)
The size of a block is 512 bytes
The size of length > 352296 / 512 = 688.07
fastboot=flash=mmc,
2: ubootpak:2nd:0x200,0x78000;
flash=mmc,2:2ndboot:2nd:0x200,0x4000;
Excuting an order:
update_mmc 2 2ndboot 0x41000000 0x200 0x78000
(run on the board)
Print the following to indicate success
head boot dev = 2
update mmc.2 type 2ndboot = 0x200(0x1) ~ 0x78000(0x3c0): Done
(Tell the CPU the address of your UBOOT, startup parameters, type, etc., so that when the device is powered on, it will boot uboot to start, and when the kernel is transplanted, uboot will boot the kernel to start)
5> Test whether to update ubootpak.bin to EMMC
Set the DIP switch to switch to EMMC startup
The development board is powered on again.
8. uboot command (1):
mmc command
mmc info - display info of the current MMC device
Show details of the current MMC settings
mmc read addr blk# cnt
addr: corresponds to the address of the memory
blk#: block number of mmc device
cnt: the number of mmc device blocks
Meaning: use mmc with blk# as the starting block, the data of cnt block size,
Read to the addr address of the memory
(mmc is the hard disk, in blocks, M em is the memory)
mmc write addr blk# cnt
addr: corresponds to the address of the memory
blk#: block number of mmc device
cnt: the number of mmc device blocks
Meaning: set the starting address of the memory to the content at addr
Write to mmc with blk# as the starting block, cnt block size data,
mmc erase blk# cnt
blk#: block number of mmc device
cnt: the number of mmc device blocks
Meaning: Erase the data whose starting block number of mmc is blk#, cnt block size.
The erasing time is affected by the number of cnt blocks,
The larger the cnt, the longer the trial.
( It’s okay not to erase, because when writing, the previous ones are overwritten )
1. Put the kernel image uImage and ramdisk.img in the tftpboot directory
cp /mnt/hgfs/share/uImage ~/tftpboot
cp /mnt/hgfs/share/ramdisk.img ~/tftpboot
2. Use the tftpboot command to download uImage to 0x410000000
tftp 0x41000000 uImage
(there are 0x2 681 blocks)
3. Move the kernel image in memory to mmc
mmc write 0x41000000 0x800 0x4000
4. Use the tftpboot command to download ramdisk.img to 0x41000000
tftp 0x41000000 ramdisk.img
(has 0x 1369 blocks)
ramdisk.img: image of the root file system
5. Move the root file system image in memory to mmc
mmc write 0x41000000 0x20800 0x20800
6. Set the bootcmd command to start the system from flash
setenv bootcmd mmc read 0x48000000 0x800 0x4000\;mmc read 0x49000000 0x20800 0x20800\;bootm 0x48000000 0x49000000
saveenv
7. 设置自启动的参数
setenv bootargs root=/dev/ram rw initrd=0x49000040,0x1000000 rootfstype=ext4 init=/linuxrc console=ttySAC0,115200
saveenv
(setenv bootargs root=/dev/ram(从ram里面挂载) rw(文件系统可读可写) initrd=0x49000040(根文件系统的起始地址),0x1000000(大小) rootfstype=ext4(文件系统) init=/linuxrc(启动之后是一号进程) console=ttySAC0(串口调试),115200(波特率))
注意:
0x49000040和0x1000000之间不允许出现空格,
使用英文逗号隔开
root=/dev/ram:从ram(内存)中挂载根文件系统
initrd=0x49000040 0x1000000 :
根文件系统的入口地址,省略前边64字节头
根文件系统的大小0x1000000
rootfstype=ext4:根文件系统的类型
8. 重启开发板,测试是否部署成功(可以关掉虚拟机)
boot:执行boot命令,自动的执行bootcmd环境
变量后边的命令。就不需要重启
9. uboot中的命令(2)
1.help命令
help 查看当前uboot支持的所有的命令命令就是一个字符串,uboot收到字符串之后,会解析字符串,完成对应的功能
FS6818# help
0 - do nothing, unsuccessfully
1 - do nothing, successfully
? - alias for 'help'
base - print or set address offset
bdinfo - print Board Info structure
boot - boot default, i.e., run 'bootcmd'
bootd - boot default, i.e., run 'bootcmd'
bootm - boot application image from memory
bootp - boot image via network using BOOTP/TFTP protocol
cmd - cmd [command] options...
cmp - memory compare
cp - memory copy
crc32 - checksum calculation
dhcp - boot image via network using DHCP/TFTP protocol
drawbmp - darw bmpfile on address 'addr' to framebuffer
env - environment handling commands
exit - exit script
ext4load- load binary file from a Ext4 filesystem
ext4ls - list files in a directory (default /)
ext4write- create a file in the root directory
fastboot- fastboot- use USB Fastboot protocol
fatinfo - print information about filesystem
fatload - load binary file from a dos filesystem
fatls - list files in a directory (default /)
fatwrite- write file into a dos filesystem
fdisk - mmc list or create ms-dos partition tables (MAX TABLE 7)
go - start application at address 'addr'
goimage - start Image at address 'addr'
help - print command description/usage
i2c - I2C sub-system
i2cmod - set I2C mode
iminfo - print header information for application image
loadb - load binary file over serial line (kermit mode)
loadbmp - load bmpfile with command or 'bootlog' environment
loads - load S-Record file over serial line
loadx - load binary file over serial line (xmodem mode)
loady - load binary file over serial line (ymodem mode)
loop - infinite loop on address range
md - memory display
mdio - MDIO utility commands
mii - MII utility commands
mm - memory modify (auto-incrementing address)
mmc - MMC sub system
mmcinfo - display MMC info
mtest - simple RAM read/write test
mw - memory write (fill)
nm - memory modify (constant address)
ping - send ICMP ECHO_REQUEST to network host
pmic - PMIC
printenv- print environment variables
reset - Perform RESET of the CPU
run - run commands in an environment variable
saveenv - save environment variables to persistent storage
saves - save S-Record file over serial line
sdfuse - sdfuse - read images from FAT partition of SD card and write them to booting device.
setenv - set environment variables
showvar - print local hushshell variables
source - run script from memory
test - minimal test like /bin/sh
tftpboot- boot image via network using TFTP protocol
udown - Download USB
update_mmc- update mmc data
version - print monitor, compiler and linker version
2. help command
View the help manual for the command
3. loadb (help loadb: check the function and usage of loadb)
loadb The address downloaded to memory
Download the binary file to an address in memory, using the kermit protocol (file transport protocol)
flash memery
Not lost when power off Lost when power off
slow speed fast
cheap price expensive
Flash access is accessed through memory in bytes
block to access,
512 bytes per block size
4. printenv print uboot environment variables
FS6818# printenv
baudrate=115200
bootargs=root=/dev/nfs nfsroot=192.168.0.222:/home/hqyj/nfs/rootfs,v4,tcp rw console=/dev/ttySAC0,115200 init=/linuxrc ip=192.168.0.250
bootcmd=loadb 43c00000;go 43c00000
bootdelay=3
bootfile=uImage
ethact=dwmac.c0060000
ethaddr=00:e2:1c:ba:e8:60
ethprime=RTL8211
fastboot=flash=mmc,2:ubootpak:2nd:0x200,0x78000;flash=mmc,2:2ndboot:2nd:0x200,0x4000;flash=mmc,2:bootloader:boot:0x8000,0x70000;flash=mmc,2:boot:ext4:0x00100000,0x04000000;flash=mmc,2:system:ext4:0x04100000,0x2F200000;flash=mmc,2:cache:ext4:0x33300000,0x1AC00000;flash=mmc,2:misc:emmc:0x4E000000,0x00800000;flash=mmc,2:recovery:emmc:0x4E900000,0x01600000;flash=mmc,2:userdata:ext4:0x50000000,0x0;
gatewayip=192.168.0.1
ipaddr=192.168.0.250
loadb=0x43c00000;go 0x43c00000
netmask=255.255.255.0
serverip=192.168.0.222
stderr=serial
stdin=serial
stdout=serial
Environment size: 872/32764 bytes
Note: When uboot matches the command, it is a partial match
pri/print/printenv commonly used
5. bootm (it is used to start the kernel) (the go command can only be followed by one address)
The running address of the bootm kernel The running address of the root file system The running address of the device tree
Boot the linux kernel to start
6. ping command
ping ip address
Whether the development board and the computer can be pinged
7. setenv set environment variables
1》Add environment variables to uboot
setenv environment variable name value corresponding to the environment variable
eg: setenv xiaoming nozuonodie
Note: the equal sign will be added automatically
2 "Delete environment variables
setenv The name of the environment variable to be deleted
eg:setenv xiaoming
3 "Modify environment variables
setenv old environment variable name new environment variable value
eg:setenv xiaoming hahaha
After setting the environment variable, the environment variable exists in memory,
Data will be lost after power on again
8. saveenv save environment variables to flash (MMC)
10. The role of environment variables in uboot
baudrate=115200 baud rate
bootdelay=3 countdown time after uboot starts
gatewayip=192.168.0.1 gateway
ipaddr=192.168.0.100 The ip address of the development board (FS6818)
netmask=255.255.255.0 subnet mask
serverip=192.168.0.99 The ip address of the server (PC: Ubuntu)
11. Test the use of ping command and tftp command
0》How to connect the development board and PC
1》Set the IP address of ubuntu
1> Modify the ip address of ubuntu
(1) Set the ubuntu system to use a wired network card
(2) Set the ubuntu system to a fixed IP address
(3) Check whether the IP address is set successfully
ifconfig
2》Set the IP address of the development board
setenv serverip 192.168.0.99
setenv ipaddr 192.168.0.100
setenv gatewayip 192.168.0.1
setenv netmask 255.255.255.0
saveenv
3"Test the development board can pass through Ubuntu
Execute the following command on HyperTerminal
FS6818# ping 192.168.0.106
If the ping fails, print the following information:
dwmac.c0060000 Waiting for PHY auto negotiation to complete......... TIMEOUT !
Waiting for PHY realtime link...... TIMEOUT !
done
dwmac.c0060000: No link.
ping failed; host 192.168.0.106 is not alive
If the ping is successful, print the following information:
Speed: 100, full duplex
Using dwmac.c0060000 device
host 192.168.0.106 is alive
Summarize:
Reason for ping failure
1》The firewall of windows may not be closed
2》Check the network cable between the development board and the computer
3》Restart tftp service
4》Check whether the environment configuration of the tftp service is correct
5 "Whether 100M full duplex is set
Summary: Reasons for tftp failure
1"Contains the reasons for the above ping failure
2》Check whether the environment variable setting of uboot is correct
serverip ipaddr gatewayip netmask
3》Check the network settings of ubuntu
Remember, the ip address of the development board and ubuntu are in the same network segment
12. u-boot porting
【1】bootloader concept
boot: guide
loader: load
Bootloader: used to boot and load the kernel, and start the kernel, and then pass parameters to the kernel
Bootloader belongs to the collective name of the kernel boot program.
For example: u-boot Bios vivi redboot etc.
The most widely used bootloader in embedded development is u-boot.
uboot is an open source software
[2] Features of u-boot
1. u-boot is an open source software
2. u-boot supports platforms with multiple architectures
arm powerPC mips x86
3. The source code of u-boot is short and concise
4. u-boot is a bare metal code
5. u-boot bootloads the kernel, starts the kernel, and passes parameters to the kernel
6. u-boot can complete the initialization of some hardware: uart, memory, emmc, network card
7. u-boot is a short-lived ghost, after starting the kernel,
After passing parameters to the kernel ( telling the kernel where to mount the root file system ), the life cycle of u-boot ends.
[3] Acquisition of u-boot source code
1. Obtain from uboot official website
ftp://ftp.denx.de/pub/u-boot/
For the s5p6818 chip, it cannot be obtained from the official,
Samsung did not open source the code assigned to s5p6818 into uboot
2. Chip manufacturer obtains
3. Development board manufacturers --- "Currently the 6818 development boards on the market, the development board manufacturers only provide u-boot.bin
4. Superior supervisor --- "Recommendation
This porting course uses: u-boot-2014.07-netok.tar.bz2
[4] Selection of uboot version
1. Do not choose a version that is too new
Unstable, less data
2. Do not choose too old version
May not support your own hardware platform
3. Do not select the test version u-boot,
Select a stable version of u-boot
The suffix rcx indicates the test version
4. Select the u-boot that supports your own hardware platform version
This porting course uses: u-boot-2014.07-netok.tar.bz2
This version of uboot supports s5p6818
【5】Preparation before transplantation
Get basic hardware information
cpu (core): cortex-a53
arch:armV8
vendor:samsung
SOC:S5P6818
board (public board): S5P6818
Public board: A set of reference circuit boards designed by the chip manufacturer based on the chip
NEXELL: A chip manufacturer in South Korea,
Samsung authorized the S5P6818 chip to NEXELL
【6】Began to prepare for transplanting uboot
1. Create a bootloader in the ubuntu home directory
mkdir bootloader
2. Copy u-boot-2014.07-netok.tar.bz2 to bootloader
cp /shared folder path/u-boot-2014.07-netok.tar.bz2 ~/bootloader
cp /shared folder path/u-boot-2014.07.tar.bz2 ~/bootloader
3. Unzip the uboot source code
tar -vxf u-boot-2014.07-netok.tar.bz2
mv u-boot-2014.07 u-boot-2014.07-6818
tar -vxf u-boot-2014.07.tar.bz2 ( downloaded from the official website )
【7】Introduction to the u-boot directory
Platform-dependent code: linked to hardware
a rch (architecture)
The download on the official website supports multiple platforms:
board (board, supported board)
The above two are related to hardware.
Platform-independent code: can be shared with hardware-independent code
fs
drivers
include
tools
....
In the uboot source code provided by Huaqing, the platform-related source code,
Delete all unused ones .
[8] Porting uboot
1. Read the README
2. Configure the cross-compilation toolchain
Open the Makefile in the top directory of the u-boot source code
vi Makefile (*****)
"readability" (modularity; see the name;)
The following content:
198 ifeq ($(HOSTARCH),$(ARCH))
199 CROSS_COMPILE ?=
200 endif
change into
198 ifeq (arm,arm) (the two inside are equal, the reason for writing ARM is that we use arm)
199 CROSS_COMPILE ?= arm-none-linux-gnueabi- (do not write a space after it)
200 endif
3. Delete the intermediate files of the u-boot source code
make distclean/clean
4. Configure u-boot source code to support fs6818 development board
make _config
make fs6818_config
If you print the information, it means success:
hqyj@hqyj:u-boot-2014.07-fs6818$ make fs6818_config
Configuring for fs6818 board...
If the following information is printed, it means that u-boot does not support this development board:
hqyj@hqyj:u-boot-2014.07-fs6818$ make maxiaoli_config
make: *** No rule to make target `maxiaoli_config'. Stop.
make: *** [maxiaoli_config] Error 1
5. Compile u-boot source code and generate ubootpak.bin
make / make all (compilation time is relatively long)
The above problems may occur during Make, and the above problems can be solved by the following methods :
6. Download the generated ubootpak.bin file to the development board, and test whether it can be used.
How to put uboot into flash(EMMC)
Premise: SD card must burn a uboot in advance
1> Start uboot via SD card,
Enter into FS6818# interface
2> Copy ubootpak.bin to the tftpboot directory
cd bootloader/u-boot-2014.07-fs6818
cp ubootpak.bin ~/tftpboot
3> Burn ubootpak.bin into memory using tftp command
tftp 0x41000000 ubootpak.bin
4> update_mmc
update_mmc
- type : 2ndboot | boot | raw | part
:flash device number EMMC: 2
: type 2ndboot
: The starting address of uboot in memory, in bytes
: The start address of flash: in blocks
: How much space to download to flash
Bytes transferred = 352296 (56028 hex)
The size of a block is 512 bytes
The size of length > 352296 / 512
fastboot=flash=mmc,
2:ubootpak:2nd:0x200,0x78000; flash=mmc,2:2ndboot:2nd:0x200,0x4000;
Excuting an order:
update_mmc 2 2ndboot 0x41000000 0x200 0x78000
Print the following to indicate success
head boot dev = 2
update mmc.2 type 2ndboot = 0x200(0x1) ~ 0x78000(0x3c0): Done
5> Test whether to update ubootpak.bin to EMMC
Set the DIP switch to switch to EMMC startup
The development board is powered on again.
13. u_boot analysis
One: makefile file analysis
According to README, u_boot needs to be configured first and then make
The configuration command is make fs6818_config
The following commands can be found from the top-level makefile:
1. Open the Makefile in the top directory of the u-boot source code
vi Makefile
2. Search for the fs6818_config target
Search for _config by multipart matching,
Get the following information:
467 %_config:: outputmakefile
468 @$(MKCONFIG) -A $(@:_config=)
Parse:
%: pattern wildcard
The first @: the following command is not echoed to the ubuntu terminal
@:_config=: Kill _config in fs6818_config,
keep fs6818
method 1:
Remove the first @ sign and re-execute make fs6818_config
/home/linux/bootloader/u-boot-2014.07-fs6818/mkconfig -A fs6818
When the above is echoed to the terminal, it can be seen that fs6818 is in front of config;
Then analyze the mkconfig file,
The following is the part about ARM in the makefile
vi Makefile
Put the following content:
198 ifeq ($(HOSTARCH),$(ARCH))
199 CROSS_COMPILE ?=
200 endif
change into
198 ifeq (arm,arm) (the two inside are equal, the reason for writing ARM is that we use arm)
199 CROSS_COMPILE ?= arm-none-linux-gnueabi- (do not write a space after it)
200 endif
It depends on how these files are organized into u_boot .
Look at the link script u-boot.lds (refer to the actual file comment here, u-boot.lds: link script (how to guide the arrangement of the program) )
It can be seen from the u-boot.lds file,
The first file to run is start.o , and then start to analyze the arch/arm/cpu/slsiap/s5p6818/start.S file, and the makefile analysis is completed.
Conclusions: 1. The first file is: arch/arm/cpu/slsiap/s5p6818/start.S
Two: start.s analysis
Function description: The first stage: set to SVC mode, turn off watchdog, mask interrupt, initialize SDRAM, set stack and clock, code from flash to SDRAM, clear BSS segment, call start_armboot (C function)
1> Build exception vector table
Set to SVC mode
Set to SVC mode, turn off watchdog
Put the mouse on lowlevel_init , and perform ctrl +] to jump
Clear BSS segment
Put the mouse on board_init_ r, press ctrl +] to jump
Jump to the arch/arm/lib/board.c file
void board_init_f(head bootflag){}
The initialization of the structure gd (global data) of the board, the gb structure is used to store the structure of global information.
The second stage: call board.c\start_armboot to start
First figure out the goal of u_boot: read the kernel from flash and start
Analysis code board.c:
If you want to read the kernel, you must support flash. The code is in line 561 and line 588 , which call
flash_init() and nand_init() initialize nor and nand.
Next is the environment variable function env_relocate () in line 617. Entering the print command in u_boot will display a lot of environment variables, which are used to set the baud rate, etc. The environment variables come from:
1. The default one. 2. It is saved on the flash. When starting up, check whether it is on the flash. If not, use the default one.
Continue to analyze, don't worry about it in the middle, it is some network card and debugger settings, until line 927 run _ main_loop (), the infinite loop in the function calls the main_loop () function, go to the file main.c to see this function,
in the main_loop() function. If there is input on the U-Boot console before the bootdelay countdown reaches 0, it will enter the cycle of command parsing and execution; if there is no input on the console, U-Boot will start the kernel.
autoboot_command() determines whether the keyboard is pressed, that is, whether the countdown is interrupted, and if the keyboard is pressed, execute the corresponding
branch. run_command_list, this function will execute a series of commands specified by the parameter s, which is the command of the environment variable bootcmd,
The default startup command is stored in bootcmd, so the linux kernel starts! This is after the countdown in uboot ends
The principle of automatically starting the linux kernel. If a key on the keyboard is pressed before the countdown ends, run_command_list
The function will not be executed, which is equivalent to autoboot_command being an empty function.
Back to the main_loop function, if the button is pressed before the countdown ends, the cli_loop function will be executed, which is the command processing function, which is responsible for receiving and processing the input commands.
run_command_list (s, -1,0 ) executes the bootcmd command to start the kernel ; so reading and starting the kernel depends on the s command, that is , bootcmd ,
It can be seen from the environment variables that bootcmd (/uboot/include/configs/fs6818.h)
#define CONFIG_BOOTCOMMAND "ext4load mmc 2:1 0x48000000 uImage;ext4load mmc 2:1 0x49000000 root.img.gz;bootm 0x48000000"
There are two types of kernel formats: zImage and uImage.
Not all U-Boots support zImage, whether it supports it depends on whether the macro CONFIG_ZIMAGE_BOOT is defined in its configuration file (fs6818.h does not define CONFIG_ZIMAGE_BOOT). So some uboots support zImage startup, while others do not. But all uboots definitely support uImage startup
The kernel header information in ulmage format is defined in /uboot/include/Image.h.
ih_load is the loading address, that is, the address of the kernel in DDR (running address); ih_ep is the kernel entry address.
copy code
typedef struct image_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
uint32_t ih_time; /* Image Creation Timestamp */
uint32_t ih_size; /* Image Data Size */
uint32_t ih_load; /* Data Load Address */
uint32_t ih_ep; /* Entry Point Address */
uint32_t ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
} image_header_t;
Summarize:
1) Move the kernel to DDR;
2) Check the kernel format and CRC;
3) Prepare to pass parameters;
4) Jump to execute the kernel
Three: run_command analysis
As you can imagine, the command is a structure, {name, fun()}, start to analyze run_command, and implement this function at line 1280 of main.c.
Skip directly to line 1325. Line 1355 is the parsing command. For example, if the input command is md.w 0, it will be parsed as argv[0]= " md.w " ,
argv[1] = " 0 " . argv[0] puts the command, and argv[1] puts the parameter. Analyze 1361 lines of code as follows:
if ((cmdtp = find_cmd(argv[0])) == NULL) {
printf ("Unknown command '%s' - try 'help'\n", argv[0]);
rc = -1; /* give up after bad command */
continue;}
How to find the command? cmdtp is a structure, see the specific code (located at line 39 of include\command.h).
The find_cmd(argv[0]) function is in line 346 of common\command.c. First look at line 360 __u_boot_cmd_start and __u_boot_cmd_end. These two things cannot be found after searching the code. They are in the link script, and in the C language, the link script can also pass in values. The *(.u_boot_cmd) section is analyzed below. Line 93 in include \command.h :
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
I don’t know which function to call after entering bootm . Search for bootm in the project and find it in common \cmd_bootm.c
U_BOOT_CMD(
bootm, CFG_MAXARGS, 1, do_bootm,
"bootm - boot application image from memory\n",
"[addr [arg ...]]\n - boot application image stored in memory\n"
"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
"\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
"\tWhen booting a Linux kernel which requires a flat device-tree\n"
"\ta third argument is required which is the address of the of the\n"
"\tdevice-tree blob. To boot that kernel without an initrd image,\n"
"\tuse a '-' for the second argument. If you do not pass a third\n"
"\ta bd_info struct will be passed instead\n"
#endif);
Go search for the macro U_BOOT_CMD and find line 97 in include\command.h :
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
Expand the above macro to get:
cmd_tbl_t __u_boot_cmd_bootm
__attribute__ ((unused,section (".u_boot_cmd")))= {#name, maxargs, rep, cmd, usage, help}
It can be seen from here that a structure such as __u_boot_cmd_bootm is defined, and the type is cmd_tbl_t.
(The code is typedef struct cmd_tbl_s cmd_tbl_t; line 93 in include\command.h :)
This structure has an attribute: __attribute__, which forces the segment attribute section to be set to .u_boot_cmd (in u-boot.lds)
The content inside is: {#name, maxargs, rep, cmd, usage , help }
替换得:{bootm, CFG_MAXARGS, 1, do_bootm, "bootm - boot application image from memory\n", "bootm - boot application image from memory\n","[addr [arg ...]]\n - boot application image stored in memory\n" "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n" "\t'arg' can be the address of an initrd image\n"}
Experimental content: add a hello command.
Create a new file named cmd_hello.c in the common directory . The code inside is modified according to cmd_bootm.c . The code is as follows:
#include
#include
#include
int do_hello(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
int i;
printf( " hello world!\n, the number of parameters is %d " , argc);
for(i=0;i<=argc;i++)
printf(“参数是:%s ”,argv[i]);
return 0;
}
另外里面还需要定义一个宏:
U_BOOT_CMD(
hello, CFG_MAXARGS, 1, do_hello,
"hello short help.....",
"hello long help.............................................."\n
);
最后把此文件放到common目录下,修改此目录下的makefile第54行:
加上文件cmd_hello.c,然后重新make一下即可。
四:内核启动分析
一:
根据bootm 命令执行do_bootm_states函数,658行非常重要,通过bootm_os_get_boot_func查找系统启动函数,参数images->os.os为系统类型,函数返回值是查找到的系统启动函数do_bootm_linux。处理615行BOOTM_STATE_OS_PREP状态,调用do_bootm_linux->boot_prep_linux处理环境变量bootargs,bootargs保存着传递给linux kernel的参数。
638行调用boot_selected_os,启动linux内核,第4个参数为linux镜像头,boot_os数组如下:
static boot_os_fn *boot_os[] = {
[IH_OS_U_BOOT] = do_bootm_standalone,
#ifdef CONFIG_BOOTM_LINUX
[IH_OS_LINUX] = do_bootm_linux,
#endif
#ifdef CONFIG_BOOTM_NETBSD
[IH_OS_NETBSD] = do_bootm_netbsd,
#endif
#ifdef CONFIG_LYNXKDI
[IH_OS_LYNXOS] = do_bootm_lynxkdi,
#endif
#ifdef CONFIG_BOOTM_RTEMS
[IH_OS_RTEMS] = do_bootm_rtems,
#endif
#if defined(CONFIG_BOOTM_OSE)
[IH_OS_OSE] = do_bootm_ose,
#endif
#if defined(CONFIG_BOOTM_PLAN9)
[IH_OS_PLAN9] = do_bootm_plan9,
#endif
#if defined(CONFIG_BOOTM_VXWORKS) && \
(defined(CONFIG_PPC) || defined(CONFIG_ARM))
[IH_OS_VXWORKS] = do_bootm_vxworks,
#endif
#if defined(CONFIG_CMD_ELF)
[IH_OS_QNX] = do_bootm_qnself,
#endif
#ifdef CONFIG_INTEGRITY
[IH_OS_INTEGRITY] = do_bootm_integrity,
#endif
};
The red function do_bootm_linux is the corresponding system startup function.
Line 295, the function kernel_entry, look at the name "kernel_entry", indicating that this function enters the Linux kernel, and also
That's the ultimate big boos! ! This function has three parameters: zero, arch, params, the first parameter zero is also 0;
The second parameter is the machine ID; the third parameter ATAGS or the first address of the device tree (DTB), ATAGS is a traditional method, use
Instead of passing some command line information, if you use the device tree, you must pass the device tree (DTB).
Line 299, get the kernel_entry function, the function kernel_entry is not defined by uboot, but in Linux
Kernel definition, the first line of code of the Linux kernel image file is the function kernel_entry, and images->ep saves the Linux
The starting address of the kernel image, which stores the first line of code of the Linux kernel!
boot_jump_linux, line 315~318 is to set the value of register r2?
Why set the value of r2? The Linux kernel is an assembly code at the beginning, so the function kernel_entry is an assembly function
number. To pass parameters to the assembly function, use r0, r1 and r2 (when the number of parameters does not exceed 3), so the r2 register is
The third parameter of the function kernel_entry.
If you use the device tree, r2 should be the starting address of the device tree, and the device tree address is stored in images
in the ftd_addr member variable.
If the device tree is not used, r2 should be the starting address of the parameters passed by uboot to Linux, that is,
is the value of the environment variable bootargs, call the kernel_entry function to enter the Linux kernel, this line will never return, and the mission of uboot will be
finished.
14. Transplantation of linux kernel
[1] Features of the linux kernel
1 "Linux kernel source code open source
2 "Linux supports multiple architecture platforms arm x86 (both mobile phones and Ubuntu use the Linux kernel)
3"The linux kernel code is modularized ()
4"The linux kernel code adopts the idea of layering
5"Linux kernel source code has good portability and tailoring features
6"Linux kernel source code is realized by assembly and c language
[2] Selection of linux kernel source code
1" can be obtained from the official linux kernel
https://mirrors.edge.kernel.org/pub/linux/kernel/
2》Obtained from the chip manufacturer
3》Obtained from the development board manufacturer
4 "Obtained from the technical director --" recommended
[3] The naming rules of the linux kernel
linux-major version number. minor version number. revision number.tar.xz
Main version number: The main version number will only be updated when the kernel source code is relatively new
Minor version number:
The minor version number is an even number: indicates a stable version
The minor version number is an odd number: indicates a test version
Revision number: as long as there is a code update in the kernel source code,
Upgrade revision number
【4】How to choose the version of the linux kernel
1》Not too new
2》Not too old
3"Select a stable version
Samsung does not have the source code of S5P6818, and open source it into the Linux kernel, so you cannot download the Linux kernel source code from the official website, and use the kernel source code provided by Samsung.
The kernel source code used in this course is:
kernel-3.4.39-ok.tar.bz2
[5] Configuration and compilation of linux kernel source code
1. Create a kernel folder in the ubuntu home directory
Copy the kernel source compressed package to the kernel folder
mkdir kernel
cd kernel
cp /mnt/hgfs/share/kernel-3.4.39-ok.tar.bz2 ./
Decompress the kernel source code
tar -vxf kernel-3.4.39-ok.tar.bz2
2. Enter the kernel source code directory and analyze the directory structure of the kernel source code
cd kernel-3.4.39
ls
Platform-related: related to hardware, code cannot be shared
arch
Platform independent: code can be shared
fs
include
driver
net
tools
usr
ipc
....
3. Configure the cross-compilation toolchain
Open the Makefile in the top-level directory of the kernel source code,
Search for CROSS_COMPILE
195 ARCH ?=
196 CROSS_COMPILE ?=
change into:
195 ARCH ?= arm
196 CROSS_COMPILE ?= arm-none-linux-gnueabi-
4. Read the README
1" After getting the kernel source code, you should clear the
Intermediate files in the kernel source
make clean
make distclean
make mrproper --"clear cleaner
2" Configure the kernel source code to support the current hardware platform
183 "make ${PLATFORM}_defconfig"
184 Create a ./.config file by using the default
symbol values from
186 arch/$ARCH/configs/${PLATFORM}_defconfig.
187 Use "make help" to get a list of all available
188 platforms of your architecture.
(创建一个.config文件,使用默认的符号值,从arch/arm/configs/目录下的这个文件${PLATFORM}_defconfig.生成一个.config文件。)
方法1:
make help
得到以下信息:
fs6818_defconfig - Build for fs6818
方法2:m
进入arch/arm/configs/目录
发现以下文件fs6818_defconfig
所以PLATFORM=fs6818. :q!
让当前的内核支持自己的硬件平台,
应该执行make fs6818_defconfig
执行结果,打印以下信息:
hqyj@hqyj:kernel-3.4.39$ make fs6818_defconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
SHIPPED scripts/kconfig/zconf.tab.c
SHIPPED scripts/kconfig/zconf.lex.c
SHIPPED scripts/kconfig/zconf.hash.c
HOSTCC scripts/kconfig/zconf.tab.o
HOSTLD scripts/kconfig/conf
#
# configuration written to .config
#
After executing the command, write the configuration information of the board to the .config file in the top directory of the kernel source code
// After the hardware board fs6818 can be supported, the hardware device driver on the board needs to be installed
3 " Configure the kernel based on menu options
Execute the command: make menuconfig
In the actual development, the configuration of the menu options along with the kernel is to use the make menuconfig command
Question 1:
The first time you use make menuconfig, you need to install a tool with a graphical interface
The graphical map needs to be installed before configuration (make meuconfig):
sudo apt-get install libncurses5-dev
Question 2:
The following error occurs:
cripts/kconfig/mconf Kconfig
Your display is too small to run Menuconfig!
It must be at least 19 lines by 80 columns.
make[1]: *** [menuconfig] Error 1
make: *** [menuconfig] Error 2
Reason: The font of the terminal is too large, shrink it a little
4 "Compile the kernel to generate uImage
make uImage
//Compiling may be relatively long, you don't need to compile, use the teacher to send it, some plug-ins may not be complete, you can use it.
time make uImage -jx
time: Echo compilation time
-jx: compile using multithreading
x can be 2,4,6,8
The following errors may occur during compilation:
"mkimage" command not found - U-Boot images will not be built
make[1]: *** [arch/arm/boot/uImage] Error 1
make: *** [uImage] Error 2
The reason for the error: the mkimage command cannot be found,
According to the prompt analysis, mkimage should exist in the uboot source code directory
The uboot source code must be compiled before there will be an mkimage executable program
way of solving the problem:
Put the mkimage in the tools directory of the uboot source code,
sudo cp mkimage /usr/bin
Copy it to the /use/bin directory of ubuntu:
sudo cp ./tools/mkimage /usr/bin
uboot directory ubuntu directory
Compile the kernel source again to pass.
5 "Copy the uImage in the arch/arm/boot/ directory to the tftpboot directory,
Test whether uImage can start normally, and mount the root file system ( restart the development board, as shown in the figure below, automatic method: first download uImage to the memory, then bootcmd starts the kernel, and then passes parameters to the kernel )
Download uImage to memory through tftp,
Mount the root file system through nfs.
Pay attention to check whether the settings of bootcmd and bootargs are correct.
Interview question: Relationship between Makefile .config Kconfig files
Makefile: Guide the kernel to compile
.config: stores the configuration information of the kernel + hardware
Kconfig: store menu options (source code of menuconfig)
Execute the make fs6818_defconfig command to generate a .config file according to the configuration information in the fs6818_config file and the Kconfig file
When make menuconfig is executed, the graphical interface of the menu is generated according to Kconfig,
If the configuration is performed according to the menu graphical interface, the .config file will be updated
Makefile文件根据.config文件中的信息,决定将那些文件
编译到uIamge中,那些不编译到uImage中。
例子:
驱动移植
1.需要有一个驱动对应的.c的代码
2. Kconfig .config Makefile
第一步:修改Kconfig:(Kconfig是产生菜单的文件)
53 config CHEN_JINGJING //在.config中生成选项
54 bool "chengjingjing said welcome to hqyj!" //选项菜单中的名字(OBJ目标文件名:obj-$(CONFIG_CHEN_JINGJING))
(tristate:有三种选项,三态(Y(把驱动编译到内核中),M(模块),N(不编译驱动)),bool:两态)
添加完选项以后,回到最顶层目录执行make menuconfig
在这个目录下面执行:make menuconfigCO
//通过选项选择要安装的驱动,图形界面能让我们快熟的找到想安装的驱动。
找到上图的device drivers,然后按回车,出现下面界面,找到character devices,
按回车
看到上面的自己的添加的内容,根据自己的需求按键盘上面的Y,M,N三选一;选中以后到exit退出即可(选择yes)
vi .config
CONFIG_MA_XIAOLI = y
vi Makefile ( path to open Makefile )
obj-$(CONFIG_FARSIGHT_DEMO) +=demo.o
Compile:
make uImage-->uImage ( contains the new driver kernel)
The corresponding path (/dev) on the board has the driver file of chenjingjing.
make modules -->demo.ko ( M (compile and generate modules ))
--------------------
Makefile
modules:
Rules for Compiling Modules
Y (to be compiled into the kernel) M (to compile and generate modules) N (to not compile the driver)
sudo insmod demo.ko install driver
sudo rmmod demo Uninstall the driver
In the actual development, the driver development is carried out in a modular way.
When you need to use the driver, load it into the kernel through the insmod command,
When the driver is not needed, use the rmmod command to uninstall the driver.
Benefits: 1. Development is more convenient and convenient
2. Save development time
3. Easy to find mistakes
If it is compiled into the kernel, once the kernel crashes, it is not sure whether it is a kernel problem or a driver problem.
If you compile in a modular way, the kernel can be started successfully, indicating that there is no problem with the kernel.
If the driver is loaded, the kernel crashes, indicating that there is a problem with the driver.
15. Case 1: Compile the LED driver into the kernel
The driver is to provide an interface for the application layer, and the driver provides an interface for turning on and off the light.
Specifically, whether you implement a running light or a breathing light depends on the code of the application layer.
1. Copy the fs6818_led.c and fs6818_led.h files to
In the drivers/char directory
cp /mnt/hgfs/share/led-driver/fs6818_led.c
~/kernel/kernel-3.4.39/drivers/char
cp /mnt/hgfs/share/led-driver/fs6818_led.h
~/kernel/kernel-3.4.39/drivers/char
2. Open the Makefile in the drivers/char directory, and add the compilation information of the corresponding driver
obj-$(CONFIG_FS6818_RGB) += fs6818_led.o
3. Open Kconfig under the drivers/char directory, add the corresponding menu options, and add the following information
12 config FS6818_RGB
13 bool "FS6818_LED_DREVERS"
14 default y
15 help
16 This is FS6818 LED Driver!~
4. Execute make menuconfig to configure menu options
Pressing <Y> includes, (includes)
<N> excludes, (not included)
<M> modularizes features. (modular)
Press <Esc><Esc> to exit, (退出)
<?> for Help, (see help manual)
</> for Search.
Legend: [*] built-in (* compile)
[ ] excluded (empty does not compile)
<M> module
< > module capable
How to find out which submenu the FS6818_LED_DREVERS menu option is in?
Press the "/" key, a search dialog box appears, enter the content after config:
"FS6818_RGB", press Enter to get the following information.
│ Symbol: FS6818_RGB [=y] configuration information
│ Type : boolean menu option type
│ Prompt: FS6818_LED_DREVERS menu option
│ Defined at drivers/char/Kconfig:12
│ Location:
│ -> Device Drivers submenu
│ -> Character devices
(The information obtained by Makefile is y, and compile the following XXX.o into uImage)
5. Compile the kernel to generate uImage
make uImage
CC drivers/char/fs6818_led.o
6. Copy uImage to the tftpboot directory
cp arch/arm/boot/uImage ~/tftpboot
Restart the development board, download the kernel through the network, and mount the root file system
7. Test whether the drive can work normally
Compile the code of the application layer to generate executable files
cp /mnt/hgfs/share/led-driver ~/ -raf
arm-none-linux-gnueabi-gcc fs6818led_test.c
最终生成a.out可执行文件
拷贝a.out可执行文件到根文件系统中,
cp led-driver/a.out ~/nfs/rootfs
进入超级终端,执行./a.out
案例2:将led灯的驱动使用模块化的方式进行编译《模块》
问题1. make uImage 编译时间问题
如果执行make clean 所有的.o文件都需要重新生成,所以编译时间会很长
如果之前编译过,如果对应的.c文件没有修改过,编译时不在重新生成.o文件,只重新编译生成修改过的.c文件,重新生成.o文件。
Makefile可以根据文件的时间戳,进行分析
1. 拷贝fs6818_led.c和fs6818_led.h文件到
drivers/char目录下
cp /mnt/hgfs/share/led-driver/fs6818_led.c
~/kernel/kernel-3.4.39/drivers/char
cp /mnt/hgfs/share/led-driver/fs6818_led.h
~/kernel/kernel-3.4.39/drivers/char
2. 打开drivers/char目录下的Makefile,添加对应的
驱动的编译信息
obj-$(CONFIG_FS6818_RGB) += fs6818_led.o
3. 打开drivers/char目录下的Kconfig,添加对应的
menu option, add the following information
12 config FS6818_RGB
13 tristate "FS6818_LED_DREVERS"
14 default y
15 help
16 This is FS6818 LED Driver!~
bool:
[*] : The corresponding driver is compiled into the kernel uImage image
[ ] : The corresponding driver is not compiled into the kernel uImage image
stistate: tristate
<*> : The corresponding driver is compiled into the kernel uImage image
< > : The corresponding driver is not compiled into the kernel uImage image
<M>: The corresponding driver is not compiled into the kernel uImage image,
Instead, compile the driver in a modular way and generate ****.ko
To use the driver, load the driver into the kernel through the command,
If you don't need to use the driver, you can uninstall the driver from the kernel by command
4. Execute make menuconfig to configure menu options
How to find out which submenu the FS6818_LED_DREVERS menu option is in?
Press the "/" key, a search dialog box appears, enter the content after config:
"FS6818_RGB", press Enter to get the following information.
│ Symbol: FS6818_RGB [=y] configuration information
│ Type : tristate menu option type: tristate
│ Prompt: FS6818_LED_DREVERS menu option
│ Defined at drivers/char/Kconfig:12
│ Location:
│ -> Device Drivers submenu
│ -> Character devices
Modify the following menu options to M
<M> FS6818_LED_DREVERS
5. Compile the kernel, regenerate uImage, and delete the driver code of the LED light in the previous uImage
make uImage
Re-copy the uImage file to tftpboot in the home directory
cp arch/arm/boot/uImage ~/tftpboot
6. Modularly compile the kernel source code to generate a .ko file
make modules
The compilation information is as follows, indicating success:
LD [M] drivers/char/fs6818_led.ko
Copy the fs6818_led.ko file to the home directory of the root file system
cp drivers/char/fs6818_led.ko ~/nfs/rootfs/home
7. Test whether the drive can work normally
Compile the code of the application layer to generate executable files
cp /mnt/hgfs/share/led-driver ~/ -raf
arm-none-linux-gnueabi-gcc fs6818led_test.c
Finally generate a.out executable file
Copy the a.out executable file to the home directory of the root file system,
cp led-driver/a.out ~/nfs/rootfs/home
Restart the development board, download the kernel through tftp, mount the root file system through nfs,
After successful startup,
On the HyperTerminal, enter cd home to enter the home directory of the root file system.
Load the driver into the kernel using the modular command,
insmod fs6818_led.ko installs the modular driver into the kernel
lsmod View modularly loaded drivers
rmmod fs6818_led uninstall driver, no need to add .ko
mknod creates a device node mo in the /dev directory
Format: mknod /dev/led c 500 0
Name of device node Character device Major device number Minor device number
Enter HyperTerminal, execute ./a.out
# rmmod fs6818_led
rmmod: can't change directory to '/lib/modules': No such file or directory
Solution:
Create a modules folder in the lib directory
cd lib
mkdir modules
Execute rmmod fs6818_led again
rmmod: can't change directory to '3.4.39-farsight': No such file or directory
Solution:
Create a 3.4.39-farsight folder in the lib/modules directory
cd lib/modules
mkdir 3.4.39-farsight
16. Root file system (method 1)
【1】Concept
Root file system: some files that the system must depend on to run
(such as scripts, libraries, configuration files...), the essence is directories and files. Root file system image: a single file rootfs -----> ramdisk.img generated after the root file system is packaged and compressed in a certain format
Filesystem: A software mechanism for managing and accessing disks,
Different file systems have different mechanisms for managing and accessing disks
[2] busybox, a tool for transplanting the root file system
1. Short and dapper
2. The version is updated quickly, and there is little difference between versions
【3】 How to get busybox
https://busybox.net/downloads/
[4] Introduction to directories in the root file system
Note: function analysis of each file
bin: command file (made by busybox tool)
dev: device file (the device recognized by the operating system has a corresponding file, that is, when the device is running)
etc: configuration file (configure some information related to the kernel)
lib: Library files, such as the standard library of C (copied from the cross-compilation toolchain)
linuxrc: the first program to run after the root file system is mounted (created by the busybox tool)
mnt: Shared directory (not necessary), such as when mounting an SD card, mount the SD card in this directory
proc: Process-related files (files only exist when a process is running)
root: user authority (the board itself runs as root user)
sbin: Superuser commands, not available to general users (the board itself is made by superusers through busybox tools)
sys: system files (when the system is running, there will be files only after the system is loaded)
tmp: Temporary files (for example, temporary files will be generated when a new device is inserted)
usr: user file (created by busybox tool)
var: Store downloaded files and software (optional)
mkdir lib mnt proc root sys tmp var
[5] Using the busyBox tool to create a root file system requires the use of the gcc-4.9.4 version of the cross-compilation toolchain, and the cross-compilation toolchain needs to be reconfigured
step:
1. In the ubuntu home directory (~), create a toolchain
mkdir toolchain
2. Copy gcc-4.9.4.tar.xz to the toolchain directory
cp shared directory/gcc-4.9.4.tar.xz ~/toolchain
3. Unzip the cross-compilation toolchain
tar -vxf gcc-4.9.4.tar.xz
4. Configure environment variables
Open sudo vi /etc/bash.bashrc
Add the following on the last line:
export PATH=$PATH:/home/hqyj/toolchain/gcc-4.9.4/bin/
Modify to your own path
Note: gcc-4.9.1 needs to be commented out
5. Make environment variables take effect immediately
source /etc/bash.bashrc
6. Test whether the cross-compilation toolchain is successfully installed
After switching the cross-compilation toolchain, the ubuntu terminal needs to be restarted
arm-none-linux-gnueabi-gcc -v
Print the following content, indicating success
gcc version 4.9.4 (Sourcery G++ Lite 2010.09-50)
【6】Use busybox tool to make rootfs root file system
1. Copy busybox-1.31.1.tar.bz2 to the home directory of ubuntu
cp /mnt/hgfs/share/busybox-1.31.1.tar.bz2 ~/
2. Unzip the root file system and switch to the busybox-1.31.1 directory
tar -vxf busybox-1.31.1.tar.bz2
cd busybox-1.31.1
3. Analyze README
Not getting too much important information
4. Obtain help information for compilation through make help
Clear intermediate files
make clean - delete temporary files created by build
make distclean - delete all non-source files (including .config)
compile
make all - Executable and documentation
Configuration via GUI
make menuconfig - interactive curses-based configurator
Install and uninstall
make install - install busybox into CONFIG_PREFIX
make uninstall
5. Modify the Makefile and configure it as a cross-compilation toolchain
Open the Makefile
Put the following content:
164 CROSS_COMPILE ?=
190 ARCH ?= $(SUBARCH)
change into
164 CROSS_COMPILE ?= arm-none-linux-gnueabi-
190 ARCH ?= arm
6. Execute the make menuconfig command to configure busybox
1 " Use a static library instead of a shared library. If the shared library is transplanted to the board, it cannot be used
Settings --->
[*] Build static binary (no shared libs) (NEW)
2 " Configure as a vi-style line editing command
Settings --->
[*] vi-style line editing commands (NEW)
3 " Configure the installation path of the root file system
Settings --->
(./_install) Destination path for 'make install'
Modify ./_install to /home/hq/rootfs
After entering, press the tab key first, and then modify it
4 " Configure commands that support driver modularization
Linux Module Utilities --->
Previously, the previous default was *, modify * to empty
[ ] Simplified modutils
[*] depmod (27 kb) (NEW)
[*] insmod (22 kb) (NEW)
[*] lsmod (1.9 kb) (NEW)
[*] Pretty output (NEW)
[*] modinfo (24 kb) (NEW)
[*] modprobe (28 kb) (NEW)
[*] Blacklist support (NEW)
[*] rmmod (3.3 kb) (NEW)
7. Compile the source code
make / make all
8. Install the root file system to /home/hqyj/rootfs
make install
9. Test the root file system and deploy the root file system
1》First change the rootfs of the nfs directory to another name
cd ~/nfs
mv rootfs rootfs-ok
2》Copy the rootfs file you made to ~/nfs
cp ~/rootfs ~/nfs -raf
10. Restart the development board and test whether the rootfs can be used normally
Premise, use tftp to download uImage
Mount the root file system using nfs
1》The mount is successful, but the error message is printed:
can't run '/etc/init.d/rcS': No such file or directory
can't open /dev/tty2: No such file or directory
can't open /dev/tty3: No such file or directory
can't open /dev/tty4: No such file or directory
Solution:
Cd ~/nfs/rootfs
Create a dev folder, create an etc/init.d folder,
Create rcS file in etc/init.d directory
mkdir -p etc/init.d
mkdir dev
cd etc/init.d
touch rcS
2》Continue to restart the development board for testing
The following problem occurs:
can't run '/etc/init.d/rcS': Permission denied
The solution to this problem: modify rcS with maximum authority
chmod 777 rcS
In the rcS file the following must be added:
#!/bin/sh
/bin/mount -a
echo /sbin/mdev > /proc/sys/kernel/hotplug
/sbin/mdev -s
explain:
mount -a: The system will automatically parse the fstab configuration file, the system according to
This configuration file performs a series of hook actions
echo /sbin/mdev > /proc/sys/kernel/hotplug:
Writing the string "/sbin/mdev" to the file /proc/sys/kernel/hotplug actually tells the kernel driver that the program to create device files in the future is /sbin/mdev
mdev -s: The system starts, and the device file corresponding to the kernel driver is automatically created
can't open /dev/tty2: No such file or directory
can't open /dev/tty3: No such file or directory
can't open /dev/tty4: No such file or directory
This problem will be solved later
Reboot the board again:
3》Create the file fstab inittab in the etc directory of the root file system
cd etc
touch fstab
touch inittab
Open the inittab file and add the following:
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
Explanation: The process of system startup
Open the fstab file and add the following
#device mount-point type options dump fsck orde
proc /proc proc defaults 0 0
tpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
Parse:
First column: device type
Second column: mount point
Third column: type
Columns 4,5,6: Access Rights
Restart the development board again, basically successful,
4"Create other files:
mkdir lib mnt proc root sys tmp var
Restart the development board again, and the root file system is created successfully.
5》Add user name
Create a profile file in the etc directory of the root file system ,
Open the profile file and add the following:
export HOSTNAME=hq
export USER=root
export HOME=ot
#export PS1="\[\u@\h \W\]\$ "
#cd root
export PS1="[$USER@$HOSTNAME \W\]\# "
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH
Restart the development board
6 "Transplant the shared library to the root file system,
Create test.c in ubuntu and write the following code
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("hello world!\n");
return 0;
}
Use the cross-compilation toolchain to compile test.c to generate the test executable program
arm-none-linux-gnueabi-gcc test.c -o test Copy the test executable program to the newly created file system
cp test ~/nfs/rootfs
Restart the development board, and run the application program test on the HyperTerminal terminal
./test
The following error message appears:
-/bin/sh: ./test: not found
Cause of the problem: The dynamic library necessary for the application to run is missing.
Solution: Copy the library in the cross-compilation toolchain to the lib directory of the root file system
cp /home/hq/toolchain/gcc-4.9.4/arm-none-linux-gnueabi/sysroot/lib/* ~/nfs/rootfs/lib
cp /home/hq/toolchain/gcc-4.6.4/arm-arm1176jzfssf-linux-gnueabi/sysroot/lib/* ~/nfs/rootfs/lib
To the end of this course:
The production of the specific ramdisk.img root file system can be done according to the steps required in the experiment manual
Cross-compilation toolchain, 132M, too large, do not install cross-compilation toolchain in the board
17. Making the root file system
【Purpose】
Master the creation and transplantation of the root file system
Note: In the experiment, the command line prompt is "$" means running on the host, "#" means running on the target board
【lab environment】
- ubuntu 12.04 release
- FS6818 platform
【Experimental steps】
1. Copy the "busybox-1.22.1.tar.bz2" file in the "Tools and Source Code\Third Day\BusyBox-1.22.1" directory in the student materials to a directory in ubuntu and decompress it
$ tar -xvf busybox-1.22.1.tar.bz2
2. Enter the top-level directory of the busybox source code to configure the source code
$ cd busybox-1.22.1/
Execute the following command to enter the configuration interface
$ make menuconfig
In the graphical configuration interface, enter the "Busybox Settings --->" menu, then enter the "Build Options --->" menu, select the "Build BusyBox as a static binary (no shared libs)" option in the "Build Options" interface, delete the "Build with Large File Support (for accessing files > 2 GB)" option, and then fill in the used compilation in the "Cross Compiler prefix" prefix "arm-none-linux-gnueabi-".
3. After exiting the configuration interface, execute the following command to compile the busybox source code
$ make
4. Execute the following command to install busybox
$ make install
After the installation is complete, the _install directory is generated under the top-level directory of the source code, and the relevant files required by the root file system are generated under the _install directory, as shown in the figure:
5. Improve other directories in the root file system
Go to the _install directory
$ cd _install/
Create related directories
$ mkdir dev etc home lib mnt proc root sys tmp var
Copy the libraries in the cross-compilation toolchain to the lib directory
$ cp /home/linux/toolchain/toolchain-4.5.1-farsight/arm-none-linux-gnueabi/libc/lib/ . -a
Remove the static library in it
$ sudo rm lib/*.a
Delete the symbol table in the library file to reduce the size of the library file:
$ arm-none-linux-gnueabi-strip lib/*
Copy the contents of etc in the original root file system to the root file system made by yourself
$ cp -rf /home/linux/rootfs/etc/* etc/
Delete the previous root file system
$ rm -rf /home/linux/rootfs/*
Copy the root file system made by yourself to the nfs mount directory
$ cp -rf * /home/linux/rootfs/
6. Follow the steps 3 and 4 in Experiment 4 to test whether the root file system generated by yourself can be mounted and used. If it cannot be mounted and used normally, check whether the above steps are correct.
The root file system is a hash file. If we want to burn it into EMMC, we must pack and compress these files into an image file of a certain format.
7. Make the root file system into a root file system image
Go to the home directory of ubuntu
$ cd ~
Execute the following command to create an image file with a size of 8M
$ dd if=/dev/zero of=ramdisk bs=1k count=8192
Format the image as ext2
$ mkfs.ext2 -F ramdisk
Mount the image file to the /mnt directory under ubuntu
$ sudo mount -t ext2 ramdisk /mnt
Copy all the files in the root file system we made to the image
$ sudo cp -a /home/linux/nfs/rootfs/* /mnt/
unmount
$ sudo umount /mnt
compressed image file
$ gzip --best -c ramdisk > ramdisk.gz
Use the mkimage tool to add a verification header to the image file and then generate a usable image ramdisk.img
$ mkimage -n "ramdisk" -A arm -O linux -T ramdisk -C gzip -d ramdisk.gz ramdisk.img
Copy the self-made root file system image to the tftp download directory and modify its permissions
$ cp ramdisk.img /home/linux/tftpboot/
$ chmod 777 /home/linux/tftpboot/ramdisk.img
8. Reconfigure the linux kernel to support the ramdisk file system
Enter the top-level directory of the linux source code used in Experiment 7
$ cd kernel-3.4.39/
Execute the following command to enter the kernel configuration interface
$ make menuconfig
Enter the "Device Drivers --->" menu in the graphical interface, and then enter the "[*] Block devices --->" menu, select "RAM block device support" as "Y", and change "Default RAM disk size (kbytes)" to "8192", as shown in the figure:
Save and exit after the configuration is complete.
Go back to the top directory of the kernel source code and recompile the kernel source code:
$ make uImage
Copy the generated uImage file to the download directory of the tftp server:
$ cp arch/arm/boot/uImage /home/linux/tftpboot/
$ chmod 777 /home/linux/tftpboot/uImage
9. According to steps 6 and 7 of Test 4, burn the newly generated kernel image uImage and root file system image ramdisk.img into EMMC and start the test from EMMC
4. Drive
1. Drive the syllabus
- kernel module
- character device driver
- to interrupt
2. What is the difference between ARM bare metal code and driver?
- What they have in common: Both can operate the hardware
- difference:
- The bare metal uses C language to write values into the corresponding registers, and the driver writes values into the registers according to a certain routine
- Arm bare metal is compiled and executed separately, and the driver relies on kernel compilation and execution ( according to the architecture and configuration specified by the kernel )
- The arm bare metal can only execute one code at the same time, and the driver can execute multiple codes at the same time (and when operating the serial port, programmers don’t have to write part of the code written by the kernel, which is more convenient)
- The arm bare metal only needs one main, and the corresponding logic code can be written in the main function to drive, which depends on the framework of the kernel and the process of operating the hardware.
(Registers for operating LED lights in the driver) (The driver module relies on the kernel framework to execute code)
3. Linux system composition
- The user space of 0-3G is the space of 0-3G for each process alone
- System call (soft interrupt swi) ---- (the application layer interacts with the bottom layer through the system call, swi, switches the application layer to the kernel layer.
Note: 1G of physical memory is mapped to 0~4G of virtual memory, each process can access the kernel, 0~3G is owned by each process independently, and 3G~4G is shared by all. The code runs on physical memory, and writing values to virtual memory is actually written on physical memory
- kernel : 【3-4G】
5 major functions of the kernel :
- Process management : process creation, destruction, scheduling and other functions
Note: Can be interrupted, can not be interrupted, that is, whether it is interrupted by a signal. How to change from the running state to the interruptible waiting state, and the uninterruptible waiting state. The operating system will start to allocate a time slice to each process. When the sleep function is written in the process, the process will go from running to sleeping state, but at this time the CPU cannot wait. There are two methods, 1: According to the time slice, the CPU automatically jumps, 2: You can write the code that can cause CPU scheduling in the program.
- File management : organize and manage files through the file system ext2/ext3/ext4 yaff jiffs, etc.
- Network management : encapsulation and disassembly of data processes through the network protocol stack (OSI, TCP) (data sending and receiving are completed through the network card driver, and the network card driver does not generate files (there is no corresponding file under the Linux system dev), so functions such as open cannot be used, but sockets are used).
- Memory management : application and release of user space and kernel space memory through the memory manager
- Device management : device driver management (corresponding to driver engineers)
- Character device driver: (led mouse keyboard lcd touchscreen (touch screen))
1. Access in units of bytes, sequential access (access in sequence)
2. The device file will be created, open read write close to access
- Block device driver : (camera u disk emmc)
1. Access according to the block (512 bytes) (sector), can be accessed sequentially, can be accessed out of order
2. The device file will be created, open read write close to access
- Network card device driver : (cat)
1. Send and receive according to network data packets.
4. Macrokernel, Microkernel
- Macro kernel: Integrate processes, networks, files, devices, memory and other functions into one kernel
Features: The code runs efficiently.
Disadvantage: If one part goes wrong, the whole kernel will crash.
eg:ubuntu Android
- Microkernel: Only processes and memory mechanisms are integrated into the kernel, and files, devices, and drivers are outside the operating system. Let the whole system run through the API interface.
Disadvantage: Low efficiency Advantage: Strong stability (Huawei mobile phone)
eg: Hongmeng
5. Drive module (three elements: entrance; exit; license)
- Entrance: Application for resources
- Export: release of resources
- License: GPL (writing a module requires open source, because the Linux system is open source, so you need to write a license agreement)
#include <linux/init.h>
#include
static int __init hello_init(void)
( __init may not be specified, and may not be written, but it is normally written)
//__init puts hello_init in the .init.text section
{
return 0;
}
static void __exit hello_exit(void)
//__exit puts hello_exit in the .exit.text section
{
}
module_init(hello_init);
//Tell the entry address of the kernel driver (the function name is the first address of the function)
module_exit(hello_exit);
//Tell the exit address of the kernel driver
MODULE_LICENSE("GPL");
//license
- Makefile:
KERNELDIR:= /lib/modules/$(shell uname -r)/build/ //path to Ubuntu kernel
#KERNELDIR:= /home/linux/kernel/kernel-3.4.39/
(board kernel path)
PWD:=$(shell pwd) //path to drive file
(Open a terminal to see the path of the terminal)
all: //target
make -C $(KERNELDIR) M=$(PWD) modules
(-C: enter the top directory)
Note: Enter the kernel directory and execute the command make modules
If M=$(PWD) is not specified, the .c file in the kernel directory will be compiled into .ko
M=$(PWD) the path where you want to compile the module
clean:
make -C $(KERNELDIR) M=$(PWD) clean
obj-m:=hello.o //Specify the name of the compiled module
chasing code
Create index file
ctags -R
on the terminal
vi -t xxx
jump in code
ctrl + ]
ctrl + t
The kernel path corresponding to the Ubuntu kernel
hello.c代码部分:
#include <linux/init.h>
#include <linux/module.h>
//入口函数:申请资源
//存储类型 数据类型 制定存放区域 函数名(形参)
static int __init hello_init(void)
{
return 0;
}
//出口函数:释放资源
static void __exit hello_exit(void)
{
}
//入口
module_init(hello_init);
//出口
module_exit(hello_exit);
//许可证
MODULE_LICENSE("GPL");
Makefile代码部分:
#KERNEL_PATH=/home/hq/kernel/kernel-3.4.39 #开发板内核路径
KERNEL_PATH=/lib/modules/$(shell uname -r)/build
#pc电脑上的路径 gcc
PWD=$(shell pwd)
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
#在内核顶层目录执行make modules才可以将hello.c生成驱动
#-C 路径:找到这个路径执行这个命令
.PHONY:clean
clean:
make -C $(KERNEL_PATH) M=$(PWD) clean
obj-m = hello.o
6. Command :
sudo insmod hello.ko install driver module
sudo rmmod hello uninstall driver module
lsmod view modules
dmesg view messages
sudo dmesg -C directly clears the message without echoing
sudo dmesg -c empty after echo
7. The printing function in the kernel
Search for a function, after you find it, find any one in it, and look at the prototype of the function, it’s OK
printk(print level "content")
printk(KERN_ERR "Fail%d",a);
printk(KERN_ERR "%s:%s:%d\n",__FILE__,__func__,__LINE__);
(Which file, which function, which line is the driver in)
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
vi -t KERN_ERR (check the kernel printing level)
include/linux/printk.h
#define KERN_EMERG "<0>" /* system is unusable */ (system is not used)
#define KERN_ALERT "<1>" /* action must be taken immediately */ (be taken immediately)
#define KERN_CRIT "<2>" /* critical conditions */ (critical conditions, critical resources)
#define KERN_ERR "<3>" /* error conditions */ (Error)
#define KERN_WARNING "<4>" /* warning conditions */(警告)
#define KERN_NOTICE "<5>" /* normal but significant condition */(提示)
#define KERN_INFO "<6>" /* informational */ (level when printing information)
#define KERN_DEBUG "<7>" /* debug-level messages */ (debug level)
0 ------ 7
highest lowest
linux@ubuntu:~$ cat /proc/sys/kernel/printk 4 4 1 7
Terminal's level Default level for messages Maximum level for terminal Minimum level for terminal
#define console_loglevel (console_printk[0])
#define default_message_loglevel (console_printk[1])
#define minimum_console_loglevel (console_printk[2])
#define default_console_loglevel (console_printk[3])
The message will only be displayed if the level of the message is greater than the terminal level
But if our Ubuntu is modified by the developer, all messages will not be automatically echoed .
Modify the system default level
su root
echo 4 3 1 7 > /proc/sys/kernel/printk
Default for virtual machines:
The default situation of the board:
If you want to modify the printing level corresponding to the development board
vi rootfs/etc/init.d/rcS
echo 4 3 1 7 > /proc/sys/kernel/printk
After adding it in rootfs /etc/init.d/rcS and then starting the board, the level of the board is as follows:
Messages are printed when installing and uninstalling drivers.
printk.c代码部分:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
//入口函数-申请资源
static int __init printk_init(void)
{
printk("hello wrold.\n");
printk(KERN_INFO "%s %s %d\n",__FILE__,__func__,__LINE__);//6
printk(KERN_ALERT "---%s %s %d\n",__FILE__,__func__,__LINE__);//1
return 0;
}
//出口函数-释放资源
static void __exit printk_exit(void)
{
printk("welcome to hqyj.\n");
printk(KERN_INFO "%s %s %d\n",__FILE__,__func__,__LINE__);//6
printk(KERN_ALERT "***%s %s %d\n",__FILE__,__func__,__LINE__);//1
}
module_init(printk_init);
module_exit(printk_exit);
MODULE_LICENSE("GPL");
Makefile代码部分:
KERNEL_PATH=/home/hq/kernel/kernel-3.4.39 #开发板内核路径
#KERNEL_PATH=/lib/modules/$(shell uname -r)/build
#pc电脑上的路径 gcc
PWD=$(shell pwd)
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
#在内核顶层目录执行make modules才可以将hello.c生成驱动
#-C 路径:找到这个路径执行这个命令
.PHONY:clean
clean:
make -C $(KERNEL_PATH) M=$(PWD) clean
obj-m = 1-printk.o
8. Drive multi-file compilation
hello.c add.c
Makefile
obj-m:=demo.o
demo-y+=hello.o add.o
( -y effect: put hello.o add.o into demo.o )
Finally generate the demo.ko file
9. Module passing parameters
- How the command is delivered
sudo insmod demo.ko hello world
---------------------------------------------------------
* Standard types are:
* byte, short, ushort, int, uint, long, ulong (char not found!!!!!!!!!)
* charp: a character pointer
* bool: a bool, values 0/1, y/n, Y/N.
* invbool: the above, only sense-reversed (N = true).
- module_param(name, type, perm)
Function: Receive the parameters passed by the command line
parameter:
@name : the name of the variable
@type : the type of the variable
@perm : permission 0664 0775 (other users only have read and execute permissions for me, no write permissions )
modinfo hello.ko (check variables)
- MODULE_PARM_DESC(_parm, desc)
Function: Describe the function of the variable
parameter:
@_parm: variable
@desc : description field
Can only pass decimal, can not write hexadecimal
practise:
1. How to use the byte type (ascii is used to pass parameters)
2. How to pass a string to a pointer
sudo insmod hello.ko a=20 b=30 c=65 p="hello_world"
Note: When passing characters, write the A SCII code value; when passing strings, there must be no spaces
- module_param_array(name, type, nump, perm)
Function: Receive the array passed by the command line
parameter:
@name : array name
@type : the type of the array
@nump : the number of parameters, the address of the variable
@perm : permission
sudo insmod hello.ko a=121 b=10 c=65 p="hello" ww=1,2,3,4,5
param.c代码部分:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/moduleparam.h>
char ch = 'A';
module_param(ch, byte, 0770);
MODULE_PARM_DESC(ch, "this is char value.");
short a = 10;
module_param(a, short, 0775);
MODULE_PARM_DESC(a, "this is short value.");
char *p = NULL;
module_param(p, charp, 0774);
MODULE_PARM_DESC(p, "this is char pointer.");
int st[8];
int num;
module_param_array(st,int,&num,0770);
MODULE_PARM_DESC(st,"this is 8 int number.");
//入口函数-申请资源
static int __init printk_init(void)
{
int i;
printk("hello wrold. num=%d\n",num);
printk("ch=%c a=%d p=%s\n", ch, a, p);
printk(KERN_INFO "%s %s %d\n", __FILE__, __func__, __LINE__); //6
printk(KERN_ALERT "---%s %s %d\n", __FILE__, __func__, __LINE__); //1
for(i=0;i<num;i++)
{
printk("%d\n",st[i]);
}
return 0;
}
//出口函数-释放资源
static void __exit printk_exit(void)
{
printk("welcome to hqyj.\n");
printk("ch=%c a=%d p=%s-----\n", ch, a, p);
printk(KERN_INFO "%s %s %d\n", __FILE__, __func__, __LINE__); //6
printk(KERN_ALERT "***%s %s %d\n", __FILE__, __func__, __LINE__); //1
}
module_init(printk_init);
module_exit(printk_exit);
MODULE_LICENSE("GPL");
Makefile代码部分:
#KERNEL_PATH=/home/hq/kernel/kernel-3.4.39 #开发板内核路径
KERNEL_PATH=/lib/modules/$(shell uname -r)/build
#pc电脑上的路径 gcc
PWD=$(shell pwd)
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
#在内核顶层目录执行make modules才可以将hello.c生成驱动
#-C 路径:找到这个路径执行这个命令
.PHONY:clean
clean:
make -C $(KERNEL_PATH) M=$(PWD) clean
obj-m = param.o
10. 复习
1.模块
三要素
- 入口
static int __init hello_init(void)
{
return 0;
}
module_init(hello_init);
- 出口
static void __exit hello_exit(void)
{
}
module_exit(hello_exit)
- 许可证
MODULE_LICENSE("GPL");
- 多文件编译
obj-m:=demo.o
demo-y+=hello.o add.o
- 内核中的打印:
printk(打印级别 “打印的内容”);
printk(“打印的内容”);
/proc/sys/kernel/printk
4 4 1 7
出现这个错误,提示,说明scripts下没有生成相应的文件,cd到kernel所在目录,执行: make scripts
然后 make 就可以编译了
11. 字符设备驱动
linux系统中一切皆文件
- 应用层: APP1 APP2 ...
fd = open("led驱动的文件",O_RDWR);
read(fd);
write();
close();
- 内核层:
对灯写一个驱动
led_driver.c
driver_open();
driver_read();
driver_write();
driver_close();
struct file_operations {
int (*open) (struct inode *, struct file *);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*release) (struct inode *, struct file *);(close)
}
cdev:
Device number 1 Device number 2 Device number n
Device Driver 1 Device Driver 2 .... Device Driver n
Device number: 32 bits, unsigned number
High 12 bits: major equipment number: which type of equipment is distinguished
Lower 20 bits: minor device number: distinguish which device of the same type
- Hardware layer: LED uart ADC PWM
Each driver has a corresponding file_operations
- The process of o pen:
open opens the file, which is related to the device number of the underlying driver.
Access the open function in struct file_operations in the device driver through the device number .
- The process of read :
The open function will have a return value, the file descriptor fd, and the read function will pass fd
Find the read function in the struct file_operations in the driver .
Led driver: character device Steps:
- Register character device driver - get a character device driver frame, and get the device number
- Determine the hardware device for operation - LED light (initialization light)
- Initialize the lamp (first establish the mapping between the actual physical address and the virtual address of the lamp)-
Based on operating system development, operating virtual memory,
- The interaction of copying user space data to kernel space data (when the user uses it, the driver will actually run, involving data interaction)
- Create a device file (device node) at the application layer
12. Registration of character device driver
- int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
Function: Register a character device driver
parameter:
@major: major device number
: If the value you fill in is greater than 0, it thinks this is the main device number
: If the value you fill in is 0, the operating system will assign you a major device number
@name : name cat /proc/devices
(
When registering a character device driver.
If successful, when you use the cat /proc/devices command to view, you can see the major device number and the name automatically assigned by the system
@fops : operation method structure
Return value: major>0, return 0 if successful, return error code ( negative number) if failed vi -t EIO
major=0, success major equipment number, failure return error code (negative number)
- void unregister_chrdev(unsigned int major, const char *name)
Function: Unregister a character device driver
parameter:
@major: major device number
@name: name
Return value: None
chrdev.c code part:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#define NAME "chrdev_led"
unsigned int major = 0;
int chrdev_open(struct inode *node_t, struct file *file_t)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t chrdev_read(struct file *file_t, char __user *ubuf, size_t n, loff_t *off_t)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t chrdev_write(struct file *file_t, const char __user *ubuf, size_t n, loff_t *off_t)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
return 0;
}
int chrdev_close(struct inode *node_t, struct file *file_t)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
return 0;
}
struct file_operations fops = {
.open = chrdev_open,
.read = chrdev_read,
.write =chrdev_write,
.release =chrdev_close,
};
//入口函数-申请资源
static int __init printk_init(void)
{
//注册字符设备驱动
major=register_chrdev(major, NAME, &fops);
if(major < 0)
{
printk("register_chrdev err.");
return -EINVAL;
}
return 0;
}
//出口函数-释放资源
static void __exit printk_exit(void)
{
//注销设备驱动
unregister_chrdev(major,NAME);
}
module_init(printk_init);
module_exit(printk_exit);
MODULE_LICENSE("GPL");
user_app.c code part:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
int fd=open("./led",O_RDWR);
char buf[32];
read(fd,buf,sizeof(buf));
write(fd,buf,sizeof(buf));
close(fd);
return 0;
}
Makefile.c code part:
#KERNEL_PATH=/home/hq/kernel/kernel-3.4.39 #开发板内核路径
KERNEL_PATH=/lib/modules/$(shell uname -r)/build
#pc电脑上的路径 gcc
PWD=$(shell pwd)
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
#在内核顶层目录执行make modules才可以将hello.c生成驱动
#-C 路径:找到这个路径执行这个命令
.PHONY:clean
clean:
make -C $(KERNEL_PATH) M=$(PWD) clean
obj-m = chrdev.o
13. Manually create device files
sudo mknod led (the path is arbitrary) c/b major device number minor device number
Remember to add -rf when sudo – rf led is deleted
14. Light up the LED on the board through the character device driver
app: test.c char buf[3]
1 0 0
0 1 0
0 0 1
------------------|------------------------
kernel: led_driver.c
-------------------|------------------------
hardware: RGB_led
- How the application passes data to the driver ( the direction of reading and writing is from the user 's point of view )
#include
- int copy_from_user(void *to, const void __user *from, int n)
Function: Copy data from user space to kernel space ( when the user needs to write data )
parameter:
@to : the first address of memory in the kernel
@from: the first address of user space
@n : the length of the copied data (bytes)
Return value: returns 0 on success, returns the number of uncopied bytes on failure
- int copy_to_user(void __user *to, const void *from, int n)
Function: copy data from kernel space to user space ( user starts to read data )
parameter:
@to : the first address of user space memory
@from: the first address of the kernel space
@n : the length of the copied data (bytes)
Return value: returns 0 on success, returns the number of uncopied bytes on failure
chCode of rdev.c:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define NAME "chrdev_led"
unsigned int major = 0;
char kbuf[32]="welcome to hqyj";
int chrdev_open(struct inode *node_t, struct file *file_t)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t chrdev_read(struct file *file_t, char __user *ubuf, size_t n, loff_t *off_t)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
//将内核空间的数据拷贝到用户空间
if (sizeof(kbuf) < n)
n = sizeof(kbuf);
if (copy_to_user(ubuf, kbuf, n) != 0)
{
printk("copy_to_user err.");
return -EINVAL;
}
return 0;
}
ssize_t chrdev_write(struct file *file_t, const char __user *ubuf, size_t n, loff_t *off_t)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
if (sizeof(kbuf) < n)
n = sizeof(kbuf);
//将用户空间的数据拷贝到内核空间
if (copy_from_user(kbuf, ubuf, n) != 0)
{
printk("copy_from_user err.");
return -EINVAL;
}
printk("kbuf=%s\n", kbuf);
return 0;
}
int chrdev_close(struct inode *node_t, struct file *file_t)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
return 0;
}
struct file_operations fops = {
.open = chrdev_open,
.read = chrdev_read,
.write = chrdev_write,
.release = chrdev_close,
};
//入口函数-申请资源
static int __init printk_init(void)
{
//注册字符设备驱动
major = register_chrdev(major, NAME, &fops);
if (major < 0)
{
printk("register_chrdev err.");
return -EINVAL;
}
return 0;
}
//出口函数-释放资源
static void __exit printk_exit(void)
{
//注销设备驱动
unregister_chrdev(major, NAME);
}
module_init(printk_init);
module_exit(printk_exit);
MODULE_LICENSE("GPL");
user_app.c:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
int fd = open("./led", O_RDWR);
char buf[32];
char buf_r[32];
fgets(buf, sizeof(buf), stdin); //hello world
read(fd, buf_r, sizeof(buf_r));
printf("buf_r=%s\n", buf_r);
write(fd, buf, sizeof(buf));
read(fd, buf_r, sizeof(buf_r));
printf("buf_r=%s\n", buf_r);
close(fd);
return 0;
}
Makefile.c code:
#KERNEL_PATH=/home/hq/kernel/kernel-3.4.39 #开发板内核路径
KERNEL_PATH=/lib/modules/$(shell uname -r)/build
#pc电脑上的路径 gcc
PWD=$(shell pwd)
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
#在内核顶层目录执行make modules才可以将hello.c生成驱动
#-C 路径:找到这个路径执行这个命令
.PHONY:clean
clean:
make -C $(KERNEL_PATH) M=$(PWD) clean
obj-m = chrdev.o
- How the driver operates registers
The register of the rgb_led light is a physical address . After the linux kernel is started, when using the address, all operations are virtual addresses . Physical addresses need to be converted to virtual addresses . The virtual address operated in the driver code is equivalent to the actual physical address operated .
physical address <------> virtual address
- void * ioremap(phys_addr_t offset, unsigned long size)
( When __iomen tells the compiler, it is a byte size when fetched )
Function: Map physical address to virtual address
parameter:
@offset : The first address of the physical object to be mapped
@size : size (bytes) ( the mapping is in units of jobs, one page is 4K, that is, when you are less than 4k, the mapped area is 4k )
Return value: return virtual address successfully, return NULL((void *)0);
- void iounmap(void *addr)
Function: Unmap
parameter:
@addr : virtual address
Return value: None
#define ENOMEM 12 /* Out of memory */
15. Lit
- The idea of software programming to control hardware:
We only need to write or read values to the control registers to allow our processor to complete certain functions.
RGB_led
1》GPIOxOUT: control pin output high and low level
RED_LED--->GPIOA28
GPIOAOUT ---> 0xC001A000
GPIOA28 output high level:
GPIOAOUT[28] <-- write-- 1
GPIOA28 output low level:
GPIOAOUT[28] <-- write -- 0
2 "GPIOxOUTENB: Control the input and output mode of the pin
GPIOAOUTENB ---> 0xC001A004
Set the GPIOA28 pin to output mode:
GPIOAOUTENB[28] <-- write -- 1
3》GPIOxALTFN: Control pin function selection
GPIOAALTFN1 ---> 0xC001A024
Set GPIOA28 pin as GPIO function:
GPIOAALTFN1[25:24] <--写-- 0b00
00 = ALT Function0
01 = ALT Function1
10 = ALT Function2
11 = ALT Function3
GPIO pin function selection: every two bits control a GPIO pin
red :gpioa28
GPIOXOUT: 0xC001A000 to control high and low levels
GPIOxOUTENB: input and output mode 0xC001A004
GPIOxALTFN1: function register 0xC001A024
( 36 bytes per register )
green:gpioe13
0xC001e000
blue :gpiob12
0xC001b000
practise:
1. Character device driver to realize running water lamp (30 minutes)
// read, modify, write
writel(v,c)
Function: Write a value to the address
parameter:
@ v : the value to write
@ c : address
readl(c)
Function: read an address and return the value in the address
parameter:
@c : address
head.h代码:
#ifndef __HEAD_H__
#define __HEAD_H__
#include <asm-generic/ioctl.h>
#define RED_ON _IO('A',1)
#define RED_OFF _IO('A',0)
#endif
chrdev.c代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
#include "head.h"
#define NAME "chrdev_led"
//定义宏表示实际物理首地址
#define RED_BASE 0xc001a000 //#GPIOA28
#define GREE_BASE 0xc001e000 // #GPIOE13
#define BLUE_BASE 0xc001b000 //#GPIOB12
unsigned int major = 0;
char kbuf[32] = "welcome to hqyj";
//定义指针保存映射后的虚拟地址的首地址
unsigned int *red_addr = NULL;
unsigned int *gree_addr = NULL;
unsigned int *blue_addr = NULL;
struct class *cls = NULL;
struct device *dev = NULL;
int chrdev_open(struct inode *node_t, struct file *file_t)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
return 0;
}
/*ssize_t chrdev_read(struct file *file_t, char __user *ubuf, size_t n, loff_t *off_t)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
//将内核空间的数据拷贝到用户空间
if (sizeof(kbuf) < n)
n = sizeof(kbuf);
if (copy_to_user(ubuf, kbuf, n) != 0)
{
printk("copy_to_user err.");
return -EINVAL;
}
return 0;
}
ssize_t chrdev_write(struct file *file_t, const char __user *ubuf, size_t n, loff_t *off_t)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
if (sizeof(kbuf) < n)
n = sizeof(kbuf);
//将用户空间的数据拷贝到内核空间
if (copy_from_user(kbuf, ubuf, n) != 0)
{
printk("copy_from_user err.");
return -EINVAL;
}
printk("kbuf=%s\n", kbuf);
if (kbuf[0] == 1)
{
//红灯亮
*red_addr |= (1 << 28);
}
else if (kbuf[0] == 0)
{
//红灯灭
*red_addr &= (~(1 << 28));
}
if (kbuf[1] == 1)
{
*gree_addr |= (1 << 13);
}
else if (kbuf[1] == 0)
{
*gree_addr &= (~(1 << 13));
}
if (kbuf[2] == 1)
{
*blue_addr |= (1 << 12);
}
else if (kbuf[2] == 0)
{
*blue_addr &= (~(1 << 12));
}
return 0;
}*/
long chrdev_ioctl(struct file *file_t, unsigned int request, unsigned long n)
{
switch(request)
{
case RED_ON:
*red_addr |= (1 << 28);
break;
case RED_OFF:
*red_addr &= (~(1 << 28));
break;
}
return 0;
}
int chrdev_close(struct inode *node_t, struct file *file_t)
{
printk("%s %s %d\n", __FILE__, __func__, __LINE__);
return 0;
}
struct file_operations fops = {
.open = chrdev_open,
// .read = chrdev_read,
// .write = chrdev_write,
.unlocked_ioctl= chrdev_ioctl,
.release = chrdev_close,
};
//入口函数-申请资源
static int __init printk_init(void)
{
//注册字符设备驱动
major = register_chrdev(major, NAME, &fops);
if (major < 0)
{
printk("register_chrdev err.");
return -EINVAL;
}
//初始化灯-引脚功能(GPIO) 输出功能 灭
//1.基于操作系统开发。建立灯物理地址和虚拟地址之间映射
//红灯
red_addr = (unsigned int *)ioremap(RED_BASE, 40);
if (red_addr == NULL)
{
printk("ioremap err.");
return -EINVAL;
}
//gree
gree_addr = (unsigned int *)ioremap(GREE_BASE, 40);
if (gree_addr == NULL)
{
printk("ioremap gree err.");
return -EINVAL;
}
blue_addr = (unsigned int *)ioremap(BLUE_BASE, 40);
if (blue_addr == NULL)
{
printk("ioremap blue err.");
return -EINVAL;
}
//通过虚拟地址操作实际物理地址向对应寄存器写值
//配置引脚GPIO
*(red_addr + 9) &= (~(3 << 24));
*(red_addr + 1) |= (1 << 28); //输出模式
*red_addr &= (~(1 << 28));
*(gree_addr + 8) &= (~(3 << 26));
*(gree_addr + 1) |= (1 << 13);
*gree_addr &= (~(1 << 13));
*(blue_addr + 8) |= (1 << 25);
*(blue_addr + 8) &= (~(1 << 24));
*(blue_addr + 1) |= (1 << 12);
*blue_addr &= (~(1 << 12));
//设置自动创建设备节点
//1.提交目录信息
cls = class_create(THIS_MODULE, NAME);
if (IS_ERR(cls))
{
printk("class_create err.");
return -EINVAL;
}
//2.提交文件信息
dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "led");
if (IS_ERR(dev))
{
printk("class_create err.");
return -EINVAL;
}
return 0;
}
//出口函数-释放资源(先申请的后释放,后申请先释放)
static void __exit printk_exit(void)
{
//销毁创建的设备节点
device_destroy(cls,MKDEV(major,0));
class_destroy(cls);
//取消映射
iounmap(blue_addr);
iounmap(gree_addr);
iounmap(red_addr);
//注销设备驱动
unregister_chrdev(major, NAME);
}
module_init(printk_init);
module_exit(printk_exit);
MODULE_LICENSE("GPL");
user_app.c代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "head.h"
int main(int argc, const char *argv[])
{
int fd = open("/dev/led", O_RDWR);
while(1)
{
ioctl(fd,RED_ON);
sleep(1);
ioctl(fd,RED_OFF);
sleep(1);
}
close(fd);
return 0;
}
Makefile.c:
KERNEL_PATH=/home/hq/kernel/kernel-3.4.39 #开发板内核路径
#KERNEL_PATH=/lib/modules/$(shell uname -r)/build
#pc电脑上的路径 gcc
PWD=$(shell pwd)
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
#在内核顶层目录执行make modules才可以将hello.c生成驱动
#-C 路径:找到这个路径执行这个命令
.PHONY:clean
clean:
make -C $(KERNEL_PATH) M=$(PWD) clean
obj-m = chrdev.o
16. Device node creation problem (udev/mdev)
(mknod hello c 243 0, manually create device node hello)
( The macro has a return value : the result of executing the last sentence)
#include
Automatically create device nodes:
struct class *cls;
- cls = class_create(owner, name) /void class_destroy(struct class *cls)//销毁
Function: Submit directory information to user space (creation of kernel directory)
parameter:
@owner :THIS_MODULE ( add THIS_MODULE when you see owner )
@name : directory name
Return value: successfully return struct class * pointer
Failure to return error code pointer int (-5)
if(IS_ERR(cls)){
return PTR_ERR(cls); ( PTR_ERR : Convert error code pointer to error code)
}
IS_ERR() : The return value is 0, not in the error code address range, non-zero, in the error code address range
Starting from address 0xffffffff, the kernel reserves 4K space as the address of the error code in the direction of address reduction.
- struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...) (creation of kernel files), each file corresponds to a peripheral (hardware device)
/void device_destroy(struct class *class, dev_t devt)//销毁
Function: Submit file information to user space
parameter:
@class : directory name
@parent:NULL
@devt : device number (major<< 12 |0 <=> MKDEV(major,0) )
@drvdata :NULL
@fmt : the name of the file
Return value: successfully return struct device * pointer
Failure returns error code pointer int (-5)
----------------------------------------------------------------
17. ioctl function
Note: What the user program does is tell the driver what it wants to do through the command code. As for how to explain these commands and how to implement them, this is what the driver has to do. The driver provides support for ioctl, and the user can use the ioctl function in the user program to control the I/O channel of the device // GRE RED_ON E_ON BLUE_ON
(Function: control of input and output )
- user:
#include
- int ioctl(int fd, int request, ...);(RED_ON)
( Let the lighting code become concise )
parameter:
@fd : the file descriptor generated by opening the file
@request: request code (reading and writing | the number of bytes passed by the third parameter),
: There is a definition method of this request code in sys/ioctl.h.
@... :Writable or not, if you want to write, write a memory address
--------------------------------------------------------
- Kernel:
( In the body of the ioctl function implemented in the driver, there is actually a switch{case} structure, each case corresponds to a command code, and some corresponding operations are performed. How to implement these operations is the responsibility of each programmer ; )
fops:
- long (*unlocked_ioctl) (struct file *file,
unsigned int request, unsigned long args);
When using the ioctl function, the main thing is the design of the request code, which is mainly designed in the sys/ioctl.h file.
Indicates the size of the bytes I read or write this time; look down how to combine the four fields when calling _IOC .
Look one by one, put the mouse on _IOC_DIRSHIFT , jump, and the following students will appear
#define _IO(type,nr)
_IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)
_IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define RDE_LED _IO(type,nr)
These macros help you complete the encapsulation of the request code.
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
dir << 30 | size<<16 | type << 8 | nr << 0
2 14 8 8
Direction Size Type Serial No.
(Direction: 00 01 10 11 related to reading and writing,)
(size: sizeof(variable name))
(Type: Combined into a unique non-overlapping integer, usually pass a character)
(Serial number: indicates the number of the same type, write 0 when the light is turned on, and do not write 0 when it is turned off).
#define RLED_ON _IOWR('a',0,int) // Light on
#define RLED_OFF _IOWR('a',1,int) //灭灯
The fields of command codes used in the kernel are declared in the following documents.
vi kernel-3.4.39/Documentation/ioctl$ vi ioctl-number.txt
( The 2^32 power = 4G number, so it can be used. The idea of the kernel is: each number represents one, and the function corresponds to the number one by one, but it is also possible to use the same when different drivers are used )
practise:
- The use of ioctl function
-
request.h代码: #ifndef __REQUEST_H__ #define __REQUEST_H__ #include <asm-generic/ioctl.h> //命令码的约定,相当于获取唯一的一个key #define RED_type 'A' #define RED_ON _IO(RED_type,1) #define RED_OFF _IO(RED_type,0) #define GREE_type 'B' #define GREE_ON _IO(GREE_type,1) #define GREE_OFF _IO(GREE_type,0) #define BLUE_type 'C' #define BLUE_ON _IO(BLUE_type,1) #define BLUE_OFF _IO(BLUE_type,0) #endif chrdev.c代码: #include <linux/init.h> #include <linux/module.h> #include <linux/printk.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <asm/io.h> #include <linux/device.h> #include "request.h" #define NAME "chrdev_led" //用宏保存物理地址,物理地址不可更改 #define RED_BASE 0xc001a000 //GPIOA28 #define GREE_BASE 0xc001e000 //GPIOE13 #define BLUE_BASE 0xc001b000 //GPIOB12 //定义指针保存映射后的虚拟地址 unsigned int *red_addr = NULL; unsigned int *gree_addr = NULL; unsigned int *blue_addr = NULL; struct class *cls = NULL; struct device *dev = NULL; int major = 0; char kbuf[32]; int ret; int myopen(struct inode *node_t, struct file *file_t) { printk("%s %s %d\n", __FILE__, __func__, __LINE__); return 0; } int myclose(struct inode *inode_t, struct file *file_t) { printk("%s %s %d\n", __FILE__, __func__, __LINE__); return 0; } long myioctl(struct file *file_t, unsigned int request, unsigned long args) { //判断是那个请求完成对应的硬件控制 switch (request) { case RED_ON: *red_addr |= (1 << 28); break; case RED_OFF: *red_addr &= (~(1 << 28)); break; case GREE_ON: *gree_addr |= (1 << 13); break; case GREE_OFF: *gree_addr &= (~(1 << 13)); break; case BLUE_ON: *blue_addr |= (1 << 12); break; case BLUE_OFF: *blue_addr &= (~(1 << 12)); break; } return 0; } //点等法赋值 struct file_operations fop = { .open = myopen, .unlocked_ioctl = myioctl, .release = myclose, }; static int __init hello_init(void) { //注册字符设备驱动 major = register_chrdev(major, NAME, &fop); if (major < 0) { printk("register chrdev error.\n"); return -EINVAL; } //灯 - 操作硬件需要建立虚拟地址和物理地址的映射关系 //红灯 red_addr = (unsigned int *)ioremap(RED_BASE, 40); if (red_addr == NULL) { printk("ioremap red led err.\n"); return -EINVAL; } //绿灯 gree_addr = (unsigned int *)ioremap(GREE_BASE, 40); if (gree_addr == NULL) { printk("ioremap gree led err.\n"); return -EINVAL; } //蓝灯 blue_addr = (unsigned int *)ioremap(BLUE_BASE, 40); if (blue_addr == NULL) { printk("ioremap blue led err.\n"); return -EINVAL; } //初始化灯,全部初始化为关闭状态 *(red_addr + 9) &= (~(3 << 24)); *(red_addr + 1) |= (1 << 28); *red_addr &= (~(1 << 28)); //灭 *(gree_addr + 8) &= (~(3 << 26)); *(gree_addr + 1) |= (1 << 13); *gree_addr &= (~(1 << 13)); *(blue_addr + 8) &= (~(1 << 24)); *(blue_addr + 8) |= (1 << 25); *(blue_addr + 1) |= (1 << 12); *blue_addr &= (~(1 << 12)); //设置自动创建设备节点 //提交目录信息 cls = class_create(THIS_MODULE, NAME); if (IS_ERR(cls)) { printk("class create err.\n"); return -EINVAL; } //提交文件信息 dev = device_create(cls, NULL, MKDEV(major, 0), NULL, NAME); if (IS_ERR(dev)) { printk("device create err.\n"); return -EINVAL; } return 0; } static void __exit hello_exit(void) { device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); //取消映射 iounmap(blue_addr); iounmap(gree_addr); iounmap(red_addr); //注销字符设备驱动 unregister_chrdev(major, NAME); } module_init(hello_init); //入口:申请资源 本质-回调一个自己写的函数 module_exit(hello_exit); //出口:释放资源 MODULE_LICENSE("GPL"); //许可证 :公共许可协议(开源协议) use_app.c代码: #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include "request.h" int main(int argc, const char *argv[]) { int fd=open("/dev/chrdev_led",O_RDWR); if(fd < 0) { perror("open led err."); return -1; } while(1) { ioctl(fd,RED_ON,NULL); sleep(1); ioctl(fd,RED_OFF,NULL); sleep(1); ioctl(fd,GREE_ON,NULL); sleep(1); ioctl(fd,GREE_OFF,NULL); sleep(1); ioctl(fd,BLUE_ON,NULL); sleep(1); ioctl(fd,BLUE_OFF,NULL); sleep(1); } close(fd); return 0; } Makefile代码; KERNELDIR = /home/hq/kernel/kernel-3.4.39 #开发板路径 #KERNELDIR=/lib/modules/$(shell uname -r)/build #pc机 PWD=$(shell pwd) all: make -C $(KERNELDIR) M=$(PWD) modules #基于内核框架将驱动代码编译生成驱动模块 #需要在内核的顶层目录下执行make modules. #-C:指定到那个路径下执行这个命令 #M赋值:要将那个路径下的驱动文件编译生成驱动模块 .PHONY:clean clean: make -C $(KERNELDIR) M=$(PWD) clean obj-m += chrdev.o
18. Linux kernel interrupt
Eg:
When the button is pressed in ARM , it will first execute the irq in the exception vector table in the assembly file start.s , and perform some operations in the irq .
Then jump to C's do _irq();
Operation: 1) Judge the sequence number of the interrupt; 2) Process the interrupt; 3) Clear the interrupt;
The principles of Linux kernel implementation and ARM logic implementation of interrupt are the same.
Kernel: When the button is pressed, it still goes to the exception vector table, and then to the handler_irq function (hard-coded). An array is defined in handler_irq. Each member of the array stores a structure. There is a function pointer in the structure. This function pointer points to the name of the function we submitted; (the subscript of the array is the soft interrupt number of the Linux kernel, and there is a mapping relationship between it and the hardware interrupt number). When the kernel implements interrupts, the interrupt registers are initialized in the handler_irq function. We only need to get the soft interrupt number and bind my interrupt processing function.
- int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
Function: Register Interrupt
parameter:
@irq : soft interrupt number
gpio soft interrupt number
Soft interrupt number = gpio_to_irq( gpi no number ); //160--"0-159
gpiono = m*32+n (n: serial number in the group)
m: Which group A B C D E (5 groups)
0 1 2 3 4
gpioa28 = 0*32+28
gpiob8 =1*32+8 gpiob16 = 1*32+16
Controller Interrupt Number (A DC ):
find -name irqs.h (find in the kernel source code)
find -name irqs.h ./arch/arm/mach-s5p6818/include/mach/irqs.h
find -name s5p6818_irq.h ./arch/arm/mach-s5p6818/include/mach/s5p6818_irq.h
#define IRQ_PHY_ADC (41 + 32) //IRQ_PHY_ADC soft interrupt number
@handler: interrupt handler
irqreturn_t (*irq_handler_t)(int irqno, void *dev);
IRQ_NONE //The interrupt has not been processed
IRQ_HANDLED //Interrupt normal processing completed
@flags : How the interrupt is triggered
#define IRQF_DISABLED 0x00000020 //Quick interrupt (write it in the processing function, handle this interrupt first)
#define IRQF_SHARED 0x00000080 // Shared interrupt ( There are fewer interrupt interfaces, but all devices want to be interrupted, so two pins need to be connected externally. There is an interrupt status flag in the register. Check whether the interrupt status flag is set . One port cannot link two buttons, and the buttons cannot be distinguished )
#define IRQF_TRIGGER_RISING 0x00000001 ( rising edge trigger )
#define IRQF_TRIGGER_FALLING 0x00000002 ( falling edge trigger )
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
@name :名字 cat /proc/interrupts
@dev :向中断处理函数中传递参数 ,不想传就写为NULL
返回值:成功0,失败返回错误码
- void free_irq(unsigned int irq, void *dev_id)
功能:注销中断
参数:
@irq :软中断号
@dev_id:向中断处理函数中传递的参数,不想传就写为NULL
- Eg:按键所对应的中断号是多少?及找所对应的GPIO;
- 第一步:找底板原理图,找到按键
- 第二步:拷贝网络标号,到核心板
及对应的软中断号为:gpio_to_irq (gpiob8 = 1*32+8);gpio_to_irq (gpiob16 = 1*32+16)
ARRAY_SIZE计算数组里面元素的个数;
- 问题解决方法
[root@farsight]#insmod farsight_irq.ko
[ 21.262000] request irq146 error
insmod: can't insert 'farsight_irq.ko': Device or resource busy
通过 cat /proc/interrupts
146: GPIO nxp-keypad
154: GPIO nxp-keypad
说明中断号已经被占用了
- 解决办法:在内核中将这个驱动删掉
How to determine who is the name of the driver file?
grep "nxp-keypad" * -nR
arch/arm/mach-s5p6818/include/mach/devices.h:48:
#define DEV_NAME_KEYPAD "nxp-keypad"
grep "DEV_NAME_KEYPAD" * -nR
drivers/input/keyboard/nxp_io_key.c:324: .name = DEV_NAME_KEYPAD,
The name of the driver file is nxp_io_key.c
Find the name of the macro, know it in Makefine;
How to remove him from the kernel?
The name of the options menu? Kconfig
config KEYBOARD_NXP_KEY
tristate "SLsiAP push Keypad support"
make menuconfig
<>SLsiAP push Keypad support
After removing the * in the graphical interface, you can delete nxp _io_key . o , so that when you compile the kernel again, you can see whether nxp _io_key . c is ready for compilation. If it is compiled, the corresponding .
make uImage recompiles the kernel
cp arch/arm/boot/uImage ~/tftpboot
Reboot the board;
install driver:
Then press the key to test;
interrupt.c代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#define GPIO_NO(n,m) (n*32+m)
#define GPIONO_B8 GPIO_NO(1,8)
#define GPIONO_B16 GPIO_NO(1,16)
int i;
int gpiono[]={GPIONO_B8,GPIONO_B16};
char *name[]={"interrupt_b8","interrupt_b16"};
//中断处理函数
irqreturn_t handler_irq(int irqno, void *dev)
{
if(irqno==gpio_to_irq(GPIONO_B8))
{
printk(KERN_ERR "++++++++++++++++++++++++++++++++++\n");
}
if(irqno==gpio_to_irq(GPIONO_B16))
{
printk(KERN_ERR "-----------------------------------\n");
}
return IRQ_HANDLED;
}
static int __init interrupt_init(void)
{
//注册中断
for(i=0;i<sizeof(gpiono)/sizeof(int);i++)
{
if(request_irq(gpio_to_irq(gpiono[i]),handler_irq,IRQF_TRIGGER_FALLING,name[i],NULL)!=0)
{
printk("request irq err.");
return -EINVAL;
}
}
return 0;
}
static void __exit interrupt_exit(void)
{
//注册中断
for(i=0;i<sizeof(gpiono)/sizeof(int);i++)
{
free_irq(gpio_to_irq(gpiono[i]),NULL);
}
}
module_init(interrupt_init);
module_exit(interrupt_exit);
MODULE_LICENSE("GPL");
Makefile.c 代码:
KERNEL_PATH=/home/hq/kernel/kernel-3.4.39 #开发板内核路径
#KERNEL_PATH=/lib/modules/$(shell uname -r)/build
#pc电脑上的路径 gcc
PWD=$(shell pwd)
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
#在内核顶层目录执行make modules才可以将hello.c生成驱动
#-C 路径:找到这个路径执行这个命令
.PHONY:clean
clean:
make -C $(KERNEL_PATH) M=$(PWD) clean
obj-m = interrupt.o
19. Linux kernel timer
- How to get the current time of the timer?
jiffies: number of kernel clock ticks
jiffies start counting at the moment the board is powered on, as long as
The board is constantly powered on, and this value keeps increasing (64 bits). exist
It can be used directly in the driver code.
- How long does it take to add 1 to the timer?
在内核顶层目录下有.config
CONFIG_HZ=1000
周期 = 1/CONFIG_HZ
周期是1ms;
- 分配的对象
struct timer_list mytimer;
- 对象的初始化
struct timer_list {
unsigned long expires; //定时的时间
void (*function)(unsigned long); //定时器的处理函数
unsigned long data; //向定时器处理函数中填写的值
};
void timer_function(unsigned long data) //定时器的处理函数
{
}
mytimer.expries = jiffies + 1000; //1s
mytimer.function = timer_function;
mytimer.data = 0;
init_timer(&mytimer); //内核帮你填充你未填充的对象
- 对象的添加定时器
void add_timer(struct timer_list *timer);
//同一个定时器只能被添加一次,
//在你添加定时器的时候定时器就启动了,只会执行一次
int mod_timer(struct timer_list *timer, unsigned long expires)
//再次启动定时器 jiffies+1000
- 4.对象的删除
int del_timer(struct timer_list *timer)
//删除定时器
Int gpio_get_value(int gpiono);//通过gpiono获取当权gpio的所处状态
返回0,低电平 非0:高电平
interrupt_time.c代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/timer.h>
#define GPIO_NO(n,m) (n*32+m)
#define GPIONO_B8 GPIO_NO(1,8)
#define GPIONO_B16 GPIO_NO(1,16)
int i;
int gpiono[]={GPIONO_B8,GPIONO_B16};
char *name[]={"interrupt_b8","interrupt_b16"};
//分配一个定时器对象
struct timer_list mytimer;
//中断处理函数
irqreturn_t handler_irq(int irqno, void *dev)
{
//再次启动定时器
mod_timer(&mytimer,jiffies+120);
return IRQ_HANDLED;
}
//定时处理函数,时间到就调用这个函数
void time_fun(unsigned long data)
{
//1.获取引脚状态 0 - 处理中断
if(gpio_get_value(GPIONO_B8)==0)
{
printk(KERN_ERR "++++++++++++++++++++++++++++++++++\n");
}
if(gpio_get_value(GPIONO_B16)==0)
{
printk(KERN_ERR "----------------------------------\n");
}
}
static int __init interrupt_init(void)
{
//初始化定时器
mytimer.expires=jiffies+120;
mytimer.function=time_fun;
mytimer.data=0;
init_timer(&mytimer);
//添加定时器
add_timer(&mytimer);
//注册中断
for(i=0;i<sizeof(gpiono)/sizeof(int);i++)
{
if(request_irq(gpio_to_irq(gpiono[i]),handler_irq,IRQF_TRIGGER_FALLING,name[i],NULL)!=0)
{
printk("request irq err.");
return -EINVAL;
}
}
return 0;
}
static void __exit interrupt_exit(void)
{
//注册中断
for(i=0;i<sizeof(gpiono)/sizeof(int);i++)
{
free_irq(gpio_to_irq(gpiono[i]),NULL);
}
}
module_init(interrupt_init);
module_exit(interrupt_exit);
MODULE_LICENSE("GPL");
Makefile。c代码:
KERNEL_PATH=/home/hq/kernel/kernel-3.4.39 #开发板内核路径
#KERNEL_PATH=/lib/modules/$(shell uname -r)/build
#pc电脑上的路径 gcc
PWD=$(shell pwd)
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
#在内核顶层目录执行make modules才可以将hello.c生成驱动
#-C 路径:找到这个路径执行这个命令
.PHONY:clean
clean:
make -C $(KERNEL_PATH) M=$(PWD) clean
obj-m = interrupt.o
20. 模块导出符号表
Thinking 1 : There are two app programs in the application layer, app1 has an add function, can app2 call the add function in app1 when app1 is running? No, because the space where the application layer app runs is private (0-3G) and not shared.
Thinking 2 : Two driver modules, can the functions in module1 be called by module2? Yes, they share (3-4G) kernel space, just need to find the address of the function . Benefits: Reduce code redundancy, and code will not be loaded repeatedly in memory. The code is more streamlined, some codes do not need to be written, just call the functions written by others directly.
When writing driver code to find functions in other drivers, you need to use the module export symbol table to export the function, and the recipient can use this function. He is a macro function .
In one module of the driver, to use a function/variable in another module, only need to use EXPORT_SYMBOL_GPL to export the address of the variable or function. Users can use this address to call it.
EXPORT_SYMBOL_GPL(sym)
sym: variable name or function name
Code Example 1 : Two independent code-driven modules
Code example 2: The provider is a driver that has been installed and used by the kernel
moudule1.c代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
int number=1000;
int add(int a,int b)
{
return a+b;
}
int sub(int a,int b)
{
return a-b;
}
EXPORT_SYMBOL_GPL(number);
EXPORT_SYMBOL_GPL(add);
EXPORT_SYMBOL_GPL(sub);
static int __init module1_init(void)
{
printk("%s add=%d sub=%d\n",__func__,add(2,3),sub(2,3));
return 0;
}
static void __exit module1_exit(void)
{
printk("module1 exit.\n");
}
module_init(module1_init);
module_exit(module1_exit);
MODULE_LICENSE("GPL");
Makefile.c代码:
#KERNEL_PATH=/home/hq/kernel/kernel-3.4.39 #开发板内核路径
KERNEL_PATH=/lib/modules/$(shell uname -r)/build
#pc电脑上的路径 gcc
PWD=$(shell pwd)
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
#在内核顶层目录执行make modules才可以将hello.c生成驱动
#-C 路径:找到这个路径执行这个命令
.PHONY:clean
clean:
make -C $(KERNEL_PATH) M=$(PWD) clean
obj-m = module1.o
Moudle2.c代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
extern int number;
extern int add(int a,int b);
extern int sub(int a,int b);
static int __init module2_init(void)
{
printk("%s add=%d sub=%d\n",__func__,add(5,2),sub(5,2));
printk("number=%d\n",number);
return 0;
}
static void __exit module2_exit(void)
{
printk("bye~\n");
}
module_init(module2_init);
module_exit(module2_exit);
MODULE_LICENSE("GPL");
Makefile.c代码:
#KERNEL_PATH=/home/hq/kernel/kernel-3.4.39 #开发板内核路径
KERNEL_PATH=/lib/modules/$(shell uname -r)/build
#pc电脑上的路径 gcc
PWD=$(shell pwd)
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
#在内核顶层目录执行make modules才可以将hello.c生成驱动
#-C 路径:找到这个路径执行这个命令
.PHONY:clean
clean:
make -C $(KERNEL_PATH) M=$(PWD) clean
obj-m = module2.o
Summary :
Compile:
1. Compile the provider first, and a Module.symvers will be generated after the compilation is completed
2. Copy Module.symvers to the directory of the caller
3. Just compile the caller
Install:
Install the provider first
reinstall caller
Uninstall:
Uninstall the caller first
Uninstall the provider again
If the caller and the provider are two independent (xx.ko) driver modules, when the address is passed between them, it is passed through Module.symvers.
If the provider is a kernel module (uImage), there is no need for the Module.symvers file to pass information between the caller and the provider .
Summarize:
The bottom layer of linux: arm, linux system transplantation, driver development
arm-program operation principle, hardware control principle
c language, basic framework idea of circuit
, and solution to the problem.
Practical operation - many questions - patience,
more theory - boring
LED light control: Through analyzing
the circuit diagram, the corresponding pin outputs a high level light on the pin
- multi-function pin (GPIO) input
or output - output function
output high or low level - output high level light on, low level light off 2. Modify cpsr 1 "arm state 2" switch to the corresponding mode 3 "prohibit the corresponding interrupt 3.
Save the return address to lr 4. pc jumps to the corresponding exception handling position in the exception vector table
. Exception vector table: 0x00000000 4 bytes are reserved for each exception to deal with. The corresponding exception is a fixed address space, which specifies 0x00000000-0x0000001c
Write by yourself: exception handling function (recovery process)
push stack to save scene stmfd sp!,{r0-r12,lr}
processing process
pop out stack and restore scene-cpsr->spsr pc->lr ldmfd sp!,{r0-r12,pc}^
The principle of controlling hardware:
led -
1. Pin function selection
2. Input or output - output function
3. Output high or low level - output high level light on
GPIOA28-red
GPIOE13-gree
GPIOB12-blue
Configure the ip address of the development board:
setenv ipaddr 192.168.1.66
setenv netmask 255.255.255.0
setenv gatewayip 192.168.1.1
setenv serverip 192.168.1.99
saveenv
开发阶段系统部署:
举例:手动验证
uImage放到tftpboot文件中
uboot命令终端:tftp 0x48000000 uImage 下载内核到开发板的内存中
bootm 0x48000000 从开大坂内存中启动程序
设置自启动:
bootcmd - 设置自动下载内核镜像
bootargs - 设置自动挂载跟文件系统
setenv bootcmd tftp 0x48000000 uImage\;bootm 0x48000000
setenv bootargs root=/dev/nfs nfsroot=192.168.1.99:/home/hq/nfs/rootfs rw console=ttySAC0,115200 init=/linuxrc ip=192.168.1.66
saveenv
移植的代码:
1.uboot - 第一段代码(裸机代码)
作用:初始化部分硬件、引导加载内核、给内核传递参数、uboot命令
uboot命令-setenv saveent 设置更改、保存(flash)环境变量的值
ping ip ->检测是否能对应ip通信
loadb ->串口下载代码
go ->启动程序
uboot: tftp 0x42000000 ubootpak.bin update_mmc 2 2ndboot 0x42000000 0x200 0x78000
uImage:
tftp 0x42000000 uImage
mmc write 0x42000000 0x800 0x4000
root file:
tftp 0x42000000 ramdisk.img
mmc write 0x42000000 0x20800 0x20800
/*You can start directly without network: read
the code from flash Start the process of reading
mmc read 0x48000000 0x800 0x4000
mmc read 0x49000000 0x20800 0x20800
bootm 0x48000000 0x49000000 in memory
. Direct operation and file system startup fail. */
Set self-starting parameters: bootcmd bootargs
setenv bootcmd mmc read 0x48000000 0x800 0x4000\;mmc read 0x49000000 0x20800 0x20800\;bootm 0x48000000 0x49000000 setenv
bootar gs root=/dev/ram rw initrd=0x49000040,0x1000000 rootfstype=ext4 init=/linuxrc console=ttySAC0,115200
saveenv
The process of uboot compilation:
preparation-configure code, configure compilation tools, modify Makefile
to execute compilation instructions:
1.make clean/distclean clear
2.make fs6818_config support public board
3.make compile
内核的编译过程:
准备-配置代码、配置编译工具、修改Makefile
指令:
1.make clean/distclean 清除
2.make fs6818_defconfig 支持公板
3.make menuconfig
菜单选项(选择是否需要编译到内核的驱动,是否编译生成模块)
// make
4.make uImage 直接编译生成uImage镜像
三个文件:Makefile Kconfig .config
举例:将led灯的驱动
1》将驱动编译到内核中 - 内核移植成功,直接使用
1.将驱动放到对应文件
2.vi Makefile
obj-$(CONFIG_RGB_LED) += fs6818_led.o
3.vi Kconfig
config RGB_LED
tristate "rgb_led char driver"
help
this is led driver!!!
bool/tristate 两态/三态
bool - y(编译到内核) n(不编译到内核)
tristate - y M(编译生成模块) N
4.回到内核顶层目录执行:
make menuconfig 选择将驱动添加编译到内核
5.make uImage
生成的uImage就有驱动
2 "Compile the driver separately to generate the driver module
- first transplant the kernel and start it successfully, then install the driver, and then the driver can use
different steps:
make menuconfig Select to compile the driver separately to generate a module
make modules - get the driver module
fs6818_led.ko
The steps to install the driver: first transplant a kernel that does not include the led driver ,
put fs6818_led.ko into the mounted root file system , and complete the transplant: execute insmod fs6818_led.ko
on the development board to install the driver lsmod Check the driver rmmod fs6818_led Unloading the driver Compiling the driver into the kernel or compiling and generating the driver module What are the respective advantages? make Target Makefile: Target: Dependency <tab>Command. PHONY: Target 1 Target 1: <tab> Command = - "Recursive Assignment : = -" Immediate Assignment += - "Append Recursive Assignment ? = -" Ask Assignment
make工具的特点->根据文件时间戳编译文件
Makefile ->工程文本文件,make唯一读入配置文件
内容是工程的编译过程。
命令的三要素:
命令名称 [选项 ...] [参数] ....
ls -l 文件名
特殊makefile变量:一般情况下程序默认命令规则
CC=编译器
CFLAGS=选项
OBJS=目标
RM=rm -f
-C 路径 ->到指定路径执行对应指令
M=路径 ->到某个路径编译路径下的c程序
变量=$(shell ls) -》将命令执行的结果赋值给一个变量
驱动:
裸机开发和驱动开发都是操作硬件
区别:
内核五大功能:
进程、网络、设备、内存、文件
设备驱动
字符设备驱动:
块设备驱动:
网卡设备驱动:
宏内核、微内核
驱动三要素:
入口:申请资源
出口:释放资源
许可证:GPL
Open read write close will assign a unique device number when creating the driver, through the device number
The created device node (device file). There is a one-to-one correspondence between them.
Device number = major device number + minor device number
to build a framework for character device drivers:
register character device driver - register_chrdev
major device number = register_chrdev(major device number (0-automatically assigned by the system), driver name, & fops) copy_from_user copy_to_user major
device
number
= register_chrdev
initialize hardware - based on operating system development, the operation is virtual memory physical
memory and virtual memory mapping
virtual address first address = ioremap (physical address, size)
io unmap (virtual address)
automatically creates device nodes:
cls=class_create(THIS_MODULE,NAME);
dev=device_create(cls,NULL,MKDEV(major device number,0-minor device number),NULL,"led")