Linux设备驱动的种类:字符设备驱动,块设备驱动,网络设备驱动
字符设备驱动:按照字节流来访问,只能顺序访问,不能无序访问的设备。
块设备驱动:按照block(512字节)来访问,可以顺序访问,也可以无序访问的设备
网络设备驱动:网络设备驱动没有设备文件,网络设备驱动主要是用来实现网络数据的收发工作
入口:安装驱动的时候执行(insmod),资源申请
出口:卸载驱动的时候执行(rmmod),资源释放
许可证:编写的内核模块要遵从GPL协议
#include
#include
// 入口
// static:限定作用域,只能在当前文件被使用
// int:当前函数的返回值类型
// __init:告诉编译器将驱动的入口函数放在.init.text段中
// #define __init __section(".init.text")
// demo_init:驱动入口函数的名字,例如led_init,adc_init,uart_init
// (void):当前函数没有参数
static int __init demo_init(){
return 0;
}
// 出口
// static:限定作用域,只能在当前文件被使用
// void:没有返回值
// __exit:告诉编译器将驱动的出口函数放在.exit.text段中
// #define __exit __section(".exit.text")
// demo_exit:驱动出口函数的名字,例如led_exit,adc_exit,uart_exit
// (void):当前函数没有参数
static void __exit demo_exit(){
}
module_init(demo_init);//告诉内核入口函数的地址
module_exit(demo_exit);//告诉内核出口函数的地址
MODULE_LICENSE("GPL");// 许可证
内部编译:在内核源码目录下编译就是内部编译(适合产品节点)
Kconfig
.config
Makefile
2.外部编译:在内核源码目录之外编译就是外部编译(适合开发阶段)
KERNELDIR:= /home/linux/linux-5.10.61
#在Makefile定义一个KERNELDIR的变量,赋值内核的路径
PWD := $(shell pwd)
#Makefile中的变量,在Makefile中期一个shell终端,执行pwd命令,将结果赋值给PWD变量
all:
make -C $(KERNELDIR) M=$(PWD) modules
@#make -C $(KERNELDIR)切换路径到内核顶层目录下,对着内核顶层目录执行make
@#在内核执行make modules,表示要进行模块化编译,将内核中配置为m编译生成xxx.ko
@#M=$(PWD):M是Makefile中的一个变量,表示编译模块的路径,是当前目录
clean:
make -C $(KERNELDIR) M=$(PWD) clean
@#make -C $(KERNELDIR)切换路径到内核顶层目录下,对着内核顶层目录执行make
@#在内核执行make clean,清除编译生成的中间文件
@#M=$(PWD):M是Makefile中的一个变量,表示清除当前目录的文件
obj-m:= demo.o #编译编译的模块是demo
编译ARM格式:make arch=arm modname=demo
编译X86格式 : make arch=x86 modname=demo
arch ?= arm
modname ?= demo
ifeq ($(arch),arm)
KERNELDIR:= /home/linux/linux-5.10.61
else
KERNELDIR := /lib/modules/$(shell uname -r)/build/
endif
#在Makefile定义一个KERNELDIR的变量,赋值内核的路径
PWD := $(shell pwd)
#Makefile中的变量,在Makefile中期一个shell终端,执行pwd命令,将结果赋值给PWD变量
all:
make -C $(KERNELDIR) M=$(PWD) modules
@#make -C $(KERNELDIR)切换路径到内核顶层目录下,对着内核顶层目录执行make
@#在内核执行make modules,表示要进行模块化编译,将内核中配置为m编译生成xxx.ko
@#M=$(PWD):M是Makefile中的一个变量,表示编译模块的路径,是当前目录
clean:
make -C $(KERNELDIR) M=$(PWD) clean
@#make -C $(KERNELDIR)切换路径到内核顶层目录下,对着内核顶层目录执行make
@#在内核执行make clean,清除编译生成的中间文件
@#M=$(PWD):M是Makefile中的一个变量,表示清除当前目录的文件
obj-m:= $(modname).o #编译编译的模块是demo
安装
sudo insmod xxx.ko
卸载
sudo rmmod xxx
查看命令
lsmod
printk(内核打印级别 "控制格式",变量);
或
printk("控制格式",变量);
printk打印语句的打印级别一个有8种类,数值越小打印级别越高,
打印级别可以用于过滤打印信息。
#define KERN_EMERG "0" /* system is unusable */
#define KERN_ALERT "1" /* action must be taken immediately */
#define KERN_CRIT "2" /* critical conditions */
#define KERN_ERR "3" /* error conditions */
#define KERN_WARNING "4" /* warning conditions */
#define KERN_NOTICE "5" /* normal but significant condition */
#define KERN_INFO "6" /* informational */
#define KERN_DEBUG "7" /* debug-level messages */
只有当消息的级别高于终端级别的时候消息才会在终端上显示
cat /proc/sys/kernel/printk
4 4 1 7
4:终端级别
4:默认消息级别
1:终端的最大级别
7:终端的最小级别
#include
#include
static int __init demo_init(void)
{
// 入口函数中的打印语句
printk(KERN_ERR "this is test first driver demo...\n");//级别和字符串之间没有逗号!!!
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
static void __exit demo_exit(void)
{
// 出口函数中的打印语句
printk(KERN_ERR "good bye first driver demo...\n");
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
对于ubuntu的终端不管任何级别的消息都不会主动回显,可以使用虚拟终端验证
上述的实例,进入和退出虚拟终端的方法如下:
进入虚拟终端:fn+ctrl+alt+[F2~F6]
退出虚拟终端:fn+ctrl+alt+F1
注:修改默认消息的级别
su root
echo 4 3 1 7 > /proc/sys/kernel/printk
dmesg
:打开内核打印信息(红色级别高于终端,白色级别低于终端)dmesg --level=err,warn
:只查看err,warn级别的信息
sudo dmesg -C/-c
:清除打印信息
我一般采用
make arch=x86 modename=demo
sudo dmesg -C
sudo insmod demo.ko
dmesg
就可以查看到打印的内容
module_param(name, type, perm)
功能:接收命令传递的参数
参数:
@name:变量名
@type:变量类型
/* Standard types are:
* byte, hexint, short, ushort, int, uint, long, ulong
* charp: a character pointer
* bool: a bool, values 0/1, y/n, Y/N.
* invbool: the above, only sense-reversed (N = true).
*/
@perm:变量的权限
返回值:无
MODULE_PARM_DESC(_parm, desc)
功能:对传参变量进行描述,可以通过modinfo命令查看描述
参数:
@_parm:变量名
@desc:描述的字符串
#include
#include
int a = 10;
module_param(a,int,0664);
MODULE_PARM_DESC(a,"this is int type var");
static int __init demo_init(void)
{
printk("init:a = %d\n",a);
return 0;
}
static void __exit demo_exit(void)
{
printk("exit: a = %d\n",a);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
查看传参变量 modinfo demo.ko
安装驱动的时候传参
sudo insmod demo.ko a=123
通过属性文件传参(模块安装了)
cd /sys/module/驱动模块名/parameters/a
su root
echo 255 > a #修改a变量的值
cat a #查看a变量的值
#include
#include
char ch;
char *p="hello";
module_param(ch,byte,0664);
MODULE_PARM_DESC(ch,"this is char type var");
module_param(p,charp,0664);
MODULE_PARM_DESC(ch,"this is charp type var");
static int __init demo_init(){
printk("init:ch = %c\n",ch);
return 0;
}
static void __exit demo_exit(){
printk("exit:p = %s\n",p);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
出现错误
解决方法:在函数参数括号内,填写void,即使没有参数也要写void
#include
#include
char ch='a';
char *p="hello";
module_param(ch,byte,0664);
MODULE_PARM_DESC(ch,"this is a char type var\n");
module_param(p,charp,0664);
MODULE_PARM_DESC(p,"this is a charp type var\n");
static int __init demo_init(void){
printk("init:ch=%c\n",ch);
printk("init:p=%s\n",p);
return 0;
}
static void __exit demo_exit(void){
printk("exit:ch=%c\n",ch);
printk("exit:p=%s\n",p);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
注:
1.byte类型传递的时候不能够传参字符,只能够传递一个字节的整数
2.charp类型传递的时候字符串间不能有空格
3.属性文件的最大权限是0664
4.模块安装的命令如下:
sudo insmod demo.ko backlight=222 ch=65 p=hello_DC23041_everyone!
直接安装驱动
因为内核模块都是运行在3-4G的内核空间中,假如demoA模块中有一个add,
需要将add函数的符号表导出,在编译demoB模块的时候使用这个符号表,此时
在运行demoB模块的时候就可以调用demoA模块中的add函数。
EXPORT_SYMBOL_GPL(sym)
功能:导出符号表
参数:
@sym:被导出函数的名字
返回值:无
demoA.c
#include
#include
int add(int a,int b){
return a+b;
}
EXPORT_SYMBOL_GPL(add);
static int __init demoA_init(void){
return 0;
}
static void __exit demoA_exit(void){
}
module_init(demoA_init);
module_exit(demoA_exit);
MODULE_LICENSE("GPL");
demoB.c
#include
#include
extern int add(int a,int b);
static int __init demoB_init(void){
printk("%d\n",add(100,200));
return 0;
}
static void __exit demoB_exit(void){
}
module_init(demoB_init);
module_exit(demoB_exit);
MODULE_LICENSE("GPL");
先编译demoA模块,会生成符号表文件Module.symvers
0x72f367e8 add /home/linux/work/day1/04demo_export/demoA/demoA EXPORT_SYMBOL_GPL
在编译demoB模块钱需要将这个符号表文件拷贝到demoB目录下
编译才能通过,否则会提示add undefined!
在5.10内核版本中,符号表不支持拷贝的方式,可以在demoB模块的Makefile中
指定demoA模块下的符号表路径。
KBUILD_EXTRA_SYMBOLS += /home/linux/work/day1/04demo_export/demoA/Module.symvers
先安装demoA模块,在安装demoB模块,因为demoB模块依赖demoA模块
先卸载demoB模块,在卸载demoA模块
#include
int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
功能:创建(注册)字符设备驱动
参数:
@major:主设备号 //次设备号[0-255]
major = 0:系统会自动分配主设备号
major > 0:静态指定设备号(但有可能失败)
@name:驱动的名字 cat /proc/devices
linux@ubuntu:/dev/input$ cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
| |
主设备号 设备的名字
@fops:操作方法结构体
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 *);
}
返回值:
major=0:成功返回主设备号,失败返回错误码
major>0:成功返回0,失败返回错误码
void unregister_chrdev(unsigned int major, const char *name)
功能:注销字符设备驱动
参数:
@major:主设备号
@name:设备名字
返回值:无
#include
#include
#include
#include
int mycdev_open (struct inode *inode, struct file *file){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t mycdev_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
int mycdev_release (struct inode *inode, struct file *file){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
#define CNAME "mycdev"
int major;
const struct file_operations fops={
.open=mycdev_open,
.read=mycdev_read,
.write=mycdev_write,
.release=mycdev_release,
};
static int __init mycdev_init(void){
//1.注册字符设备驱动
if(0>(major=register_chrdev(0,CNAME,&fops))){
printk("register_chrdev error\n");
return -EAGAIN;
}
printk("register chrdev success,major = %d\n", major);//不加这句话,dmesg看不见
return 0;
}
static void __exit mycdev_exit(void){
//2.注销字符设备驱动
unregister_chrdev(major,CNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
编译驱动
make arch=x86 modname=mycdev
安装驱动
sudo insmod mycdev.ko
查看驱动
dmesg
cat /proc/devices
创建设备文件
sudo mknod /dev/mycdev c 235 0
mknod :创建设备文件的命令
/dev/mycdev:设备文件的路径及名字(路径和名字是任意的)
c : c字符设备 b块设备
235 :主设备号
0 :次设备号[0-255]
编写应用程序
#include
int main(int argc, char const *argv[])
{
int fd;
char buf[128]={0};
if (fd=open("/dev/mycdev",O_RDWR)){
ERRLOG("open error");
}
write(fd,buf,sizeof(buf));
read(fd,buf,sizeof(buf));
close(fd);
return 0;
}
执行应用程序看现象
sudo chmod 0777 /dev/mycdev
内核不能够通过直接指向用户空间内存地址,如果通过这个方式访问用户空间的数据,
此时如果进程意外终止内核也会崩溃。用户空间也不能够通过指针直接修改内核空间的
数据,不安全,用户空间进入内核空间的方法只有一种系统调用。如果用户和内核需要
进行数据传输使用copy_to_user/copy_from_user函数完成。
#include
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
功能:将内核空间的数据拷贝到用户空间(read)
参数:
@to:用户空间的地址
@from:内核空间的地址
@n:大小(字节)
返回值:成功返回0,失败返回未拷贝的字节的个数
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
功能:将用户空间的数据拷贝到内核空间(write)
参数:
@to:内核空间的地址
@from:用户空间的地址
@n:大小(字节)
返回值:成功返回0,失败返回未拷贝的字节的个数
#include
#include
#include
#include
#define CNAME "mycdev"
int major;
int ret;
char kbuf[128]={0};
int mycdev_open (struct inode *inode, struct file *file){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t mycdev_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if(size>sizeof(kbuf)){
size=sizeof(kbuf);
}
ret=copy_to_user(ubuf,kbuf,size);
if(ret){
printk("copy to user error\n");
return -EIO;
}
return size;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if(size>sizeof(kbuf)){
size=sizeof(kbuf);
}
ret=copy_from_user(kbuf,ubuf,size);
if(ret){
printk("copy from user error\n");
return -EIO;
}
return size;
return 0;
}
int mycdev_release (struct inode *inode, struct file *file){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
const struct file_operations fops={
.open=mycdev_open,
.read=mycdev_read,
.write=mycdev_write,
.release=mycdev_release,
};
static int __init mycdev_init(void){
//1.注册字符设备驱动
if(0>(major=register_chrdev(0,CNAME,&fops))){
printk("register_chrdev error\n");
return -EAGAIN;
}
return 0;
}
static void __exit mycdev_exit(void){
//2.注销字符设备驱动
unregister_chrdev(major,CNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
test.c
#include
int main(int argc, char const *argv[])
{
int fd;
char buf[128]="快去看封神!!!";
if (-1==(fd=open("/dev/mycdev",O_RDWR))){
ERRLOG("open error");
}
write(fd,buf,sizeof(buf));
memset(buf,0,sizeof(buf));
read(fd,buf,sizeof(buf));
printf("buf = %s\n",buf);
close(fd);
return 0;
}
传输unsigned int类型的整数
传输img_t结构体变量
typedef struct{
int width;
int high;
}img_t;
驱动和上述2.3标题中的一样,应用程序如下:
#include
typedef struct{
int width;
int high;
}img_t;
int main(int argc, char const *argv[])
{
int fd;
unsigned int unum=10;
img_t uimg={.high=100,
.width=200};
char buf[128]="快去看封神!!!";
if (-1==(fd=open("/dev/mycdev",O_RDWR))){
ERRLOG("open error");
}
write(fd,buf,sizeof(buf));
memset(buf,0,sizeof(buf));
read(fd,buf,sizeof(buf));
printf("buf = %s\n",buf);
write(fd,&unum,sizeof(unum));
unum=0;
read(fd,&unum,sizeof(unsigned int));
printf("num=%d\n",unum);
write(fd,&uimg,sizeof(uimg));
memset(&uimg,0,sizeof(uimg));
read(fd,&uimg,sizeof(uimg));
printf("uimg=%d %d\n",uimg.high,uimg.width);
close(fd);
return 0;
}
对应LED操作是通过操作寄存器完成的,寄存器的地址是物理地址。但是
在Linux内核启动之后在程序中使用地址是虚拟地址,不能够在内核空间直接
操作物理地址。如果想要在内核空间操作LED灯,就需要将LED灯的物理地址
映射到内核空间对应的虚拟地址,在内核空间操作这个虚拟地址就相当于在到
LED的寄存器的物理地址。
#include
void *ioremap(phys_addr_t offset, size_t size)
功能:地址映射
参数:
@offset:物理地址
@size:映射内存大小
返回值:成功返回虚拟地址,失败返回NULL
void iounmap(volatile void *cookie)
功能:取消映射
参数:
@cookie:虚拟地址
返回值:无
RCC_MP_AHB4ENSETR 0x50000a28 [4] 1 GPIOE时钟使能
GPIOx_MODER 0x50006000 [21:20] 01 输出
GPIOx_ODR 0x50006014 [10] 1 LED1亮 0 LED1灭
注:映射可以放在open中也可以放在init中,调用频率高放在init中,可以避免重复的映射取消映射;调用频率低放在open中,避免内存一直被占用。
#ifndef _MYLED_H
#define _MYLED_H
#define RCC_MP_AHB4ENSETR 0x50000A28
#define GPIOE_MODER 0x50006000
#define GPIOE_ODR 0x50006014
#endif /*_MYLED_H*/
#include
#include
#include
#include
#include
#include "myled.h"
#define CNAME "myled"
int major;
int ret;
char kbuf[128]={0};
unsigned int *rcc,*moder,*odr;//定义映射指针
int myled_open (struct inode *inode, struct file *file){
//1.映射
if(NULL==(rcc=ioremap(RCC_MP_AHB4ENSETR,4))){
printk("ioremap rcc error\n");
return -ENOMEM;
}
if(NULL==(moder=ioremap(GPIOE_MODER,4))){
printk("ioremap moder error\n");
return -ENOMEM;
}
if(NULL==(odr=ioremap(GPIOE_ODR,4))){
printk("ioremap odr error\n");
return -ENOMEM;
}
//2.初始化led
//使能时钟
*rcc |=(1<<4);
//输出模式
*moder &=~(3<<20);
*moder |=(1<<20);
//默认灯灭
*odr &=~(1<<10);
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t myled_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if(size>sizeof(kbuf)){
size=sizeof(kbuf);
}
ret=copy_to_user(ubuf,kbuf,size);
if(ret){
printk("copy to user error\n");
return -EIO;
}
return size;
}
ssize_t myled_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if(size>sizeof(kbuf)){
size=sizeof(kbuf);
}
ret=copy_from_user(kbuf,ubuf,size);
if(ret){
printk("copy from user error\n");
return -EIO;
}
//3.控制灯亮灭
// kbuf[0]=1 LED1亮 kbuf[0]=0 LED1熄灭
if(kbuf[0]==1){
*odr |= (1<<10);
}else{
*odr &=~(1<<10);
}
return size;
}
int myled_release (struct inode *inode, struct file *file){
//4.取消映射
iounmap(rcc);
iounmap(moder);
iounmap(odr);
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
const struct file_operations fops={
.open=myled_open,
.read=myled_read,
.write=myled_write,
.release=myled_release,
};
static int __init myled_init(void){
//1.注册字符设备驱动
if(0>(major=register_chrdev(0,CNAME,&fops))){
printk("register_chrdev error\n");
return -EAGAIN;
}
return 0;
}
static void __exit myled_exit(void){
//2.注销字符设备驱动
unregister_chrdev(major,CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
#include
typedef struct{
int width;
int high;
}img_t;
int main(int argc, char const *argv[])
{
int fd;
char buf[1]={0};
if (-1==(fd=open("/dev/myled",O_RDWR))){
ERRLOG("open error");
}
while(1){
buf[0] = !buf[0];
write(fd,buf,sizeof(buf));
sleep(1);
}
close(fd);
return 0;
}
#ifndef _MYLED_H
#define _MYLED_H
#define RCC_MP_AHB4ENSETR 0x50000A28
#define GPIOE_MODER 0x50006000
#define GPIOE_ODR 0x50006014
#define GPIOF_MODER 0x50007000
#define GPIOF_ODR 0x50007014
#endif /*_MYLED_H*/
#include
#include
#include
#include
#include
#include "myled.h"
#define CNAME "myled"
int major;
int ret;
char kbuf[128]={0};
unsigned int *rcc,*modere,*odre,*moderf,*odrf;//定义映射指针
int myled_open (struct inode *inode, struct file *file){
//1.映射
if(NULL==(rcc=ioremap(RCC_MP_AHB4ENSETR,4))){
printk("ioremap rcc error\n");
return -ENOMEM;
}
if(NULL==(modere=ioremap(GPIOE_MODER,4))){
printk("ioremap moder error\n");
return -ENOMEM;
}
if(NULL==(odre=ioremap(GPIOE_ODR,4))){
printk("ioremap odr error\n");
return -ENOMEM;
}
if(NULL==(moderf=ioremap(GPIOF_MODER,4))){
printk("ioremap moder error\n");
return -ENOMEM;
}
if(NULL==(odrf=ioremap(GPIOF_ODR,4))){
printk("ioremap odr error\n");
return -ENOMEM;
}
//2.初始化led
//使能GPIOE GPIOF时钟
*rcc |=(3<<4);
//输出模式
//PE10
*modere &=~(3<<20);
*modere |=(1<<20);
//PF10
*moderf &=~(3<<20);
*moderf |=(1<<20);
//PE8
*modere &=~(3<<16);
*modere |=(1<<16);
//默认灯灭
*odre &=~(1<<10);//PE10
*odrf &=~(1<<10);//PF10
*odre &=~(1<<8);//PE8
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t myled_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if(size>sizeof(kbuf)){
size=sizeof(kbuf);
}
ret=copy_to_user(ubuf,kbuf,size);
if(ret){
printk("copy to user error\n");
return -EIO;
}
return size;
}
ssize_t myled_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if(size>sizeof(kbuf)){
size=sizeof(kbuf);
}
ret=copy_from_user(kbuf,ubuf,size);
if(ret){
printk("copy from user error\n");
return -EIO;
}
//3.控制灯亮灭
// kbuf[0]=1 LED1亮 kbuf[0]=0 LED1熄灭 PE10
// kbuf[1]=1 LED2亮 kbuf[1]=0 LED2熄灭 PF10
// kbuf[2]=1 LED3亮 kbuf[2]=0 LED3熄灭 PE8
if(kbuf[0]==1){
*odre |= (1<<10);
}else if(kbuf[0]==0){
*odre &=~(1<<10);
}
if(kbuf[1]==1){
*odrf |= (1<<10);
}else if(kbuf[1]==0){
*odrf &=~(1<<10);
}
if(kbuf[2]==1){
*odre |= (1<<8);
}else if(kbuf[2]==0){
*odre &=~(1<<8);
}
return size;
}
int myled_release (struct inode *inode, struct file *file){
//4.取消映射
iounmap(rcc);
iounmap(modere);
iounmap(odre);
iounmap(moderf);
iounmap(odrf);
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
const struct file_operations fops={
.open=myled_open,
.read=myled_read,
.write=myled_write,
.release=myled_release,
};
static int __init myled_init(void){
//1.注册字符设备驱动
if(0>(major=register_chrdev(0,CNAME,&fops))){
printk("register_chrdev error\n");
return -EAGAIN;
}
printk("register chrdev success,major = %d\n", major);
return 0;
}
static void __exit myled_exit(void){
//2.注销字符设备驱动
unregister_chrdev(major,CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
#include
int main(int argc, char const *argv[])
{
int fd;
char buf[3]={0};
if (-1==(fd=open("/dev/myled",O_RDWR))){
ERRLOG("open error");
}
while(1){
//led1
buf[0] =1;
write(fd,buf,sizeof(buf));
sleep(1);
buf[0] =0;
write(fd,buf,sizeof(buf));
sleep(1);
//led2
buf[1] =1;
write(fd,buf,sizeof(buf));
sleep(1);
buf[1] =0;
write(fd,buf,sizeof(buf));
sleep(1);
//led3
buf[2] =1;
write(fd,buf,sizeof(buf));
sleep(1);
buf[2] =0;
write(fd,buf,sizeof(buf));
sleep(1);
}
close(fd);
return 0;
}
tftpboot服务器:tftp服务器它是通过UDP实现的,它的端口号是69。开发板在uboot启动后,uboot
通过tftp客户端从ubuntu的tftp服务器下载uImage(内核)和stm32mp157a-fsmp1a.dtb(设备树)。
就可以启动内核,当内核启动之后uboot的任务就完成了。
nfs服务器:nfs是网络文件系统的简称(根文件通过网络的形式共享给开发板),在ubuntu上nfs的
服务器目录是/home/linux/rootfs(把这个目录下的所有内容共享给开发板)。nfs服务器是通过TCP
实现的,它的端口号是2049。
桥接模式:相当于windows和ubuntu各自都有自己的IP地址,外部可以通过它们各自的IP地址访问到对应的系统
NAT模式:ubuntu和window只有一个IP,这个IP地址就是windows的IP地址,外部如果想要访问ubuntu,必须首先
访问windows的ip地址,然后借助端口转发访问ubuntu。
开发板:
setenv ipaddr 192.168.2.10
setenv serverip 192.168.2.210
setenv netmask 255.255.255.0
setenv gatewayip 192.168.2.1
setenv bootcmd tftp 0xc2000000 uImage\;tftp 0xc4000000 stm32mp157a-fsmp1a.dtb\;bootm 0xc2000000 - 0xc4000000
setenv bootargs root=/dev/nfs nfsroot=192.168.2.210:/home/linux/rootfs,tcp,v4 rw console=ttySTM0,115200 init=/linuxrc ip=192.168.2.10
saveenv
windows:
将windows的有线网卡或者usb转网卡设置为静态IP
VMware:
设置VMware的NAT模式的端口转发