栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

IMX6DL-C支持EIM总线

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

IMX6DL-C支持EIM总线

目的:上位机可控制FPGA,FPGA可将采集的数据传输给上位机 方案:通过EIM口实现FPGA与IMX6DL-C通信,采取DMA的方式进行数据传输。 步骤一:配置EIM总线 前景提要

  需要添加EIM总线的配置,实际上,和添加一个串口类似,同样是三步:添加EIM总线节点、配置引脚复用、去掉其他的引脚功能复用。
  配置引脚根据Linux内核版本的差异,存在两个方式,本文在此使用的是方案商飞凌提供的Linux3.0.35版本,此版本不支持设备树。
  EIM口介绍:通过阅读i.MX6D芯片手册Chapter 22 External Interface Module (EIM),可以得到imx6dl-c芯片EIM比较重要的硬件资源主要有26根地址线(EIM_DA0–EIM_DA15/EIM_A16–EIM_A25),16根数据线(EIM_D16–EIM_D31),1根时钟线(EIM_BCLK),4根片选线(EIM_CS0–EIM_CS3),其中EIM_CS2–EIM_CS3为复用做片选线),1根读信号(EIM_OE),1根写信号(EIM_RW),(EIM_EB0~EIM_EB3),(EIM_WAIT),(EIM_LBA)。

IOMUX介绍

Soc芯片上有很多引脚,每个引脚对应特定的功能。例如控制电机需要控制电机的功能引脚1和引脚2,控制传感器需要控制传感器功能的引脚3和引脚4。对于使用Soc芯片的需求方来说,可能只是控制电机,或者只是控制传感器。这样Soc芯片上的4个引脚就浪费了2个引脚。为了能提供更多的功能,减少引脚的浪费,可以把引脚1,2和引脚3,4合并,通过复用引脚来解决问题。
i.mx6的Soc芯片提供IOMUX机制就是用来解决引脚复用的问题。
如何确定使用引脚12,还是使用引脚34,是下面要介绍的主要内容
概念

    Pad 对应的是Soc芯片上的引脚
    Signal 对应的是控制电机的功能
    内核源代码

需要配置的寄存器

    Pad控制寄存器
    Mux控制寄存器
    Select Input寄存器

配置IOMUX的必备工具

    芯片原理图
    芯片软件手册
    内核源代码

配置步骤

  1. 查看原理图
    下面是USB转串口芯片的原理图,我们需要配置UART1_RX这个接口。

    通过UART1_RX,我们可以在i.mx6的原理图中找到对应的引脚CSI0_DAT11。
  2. 查找软件手册(引脚功能)

软件手册第4章
查找CSI0_DAT11,可以得到两个重要的信息

Pad的控制寄存器名称 SW_PAD_CTL_PAD_CSI0_DATA11
CSI0_DAT11的功能UART1_RX_DATA 用于查找其他两个寄存器


查找UART1_RX_DATA,可以得到两个寄存器

Mux的控制寄存器名称 IOMUX_SW_MUX_CTL_PAD_CSI0_DATA11
Select Input的寄存器名称 IOMUXC_UART1_UART_RX_DATA_SELECT_INPUT


3. 查找软件手册(IOMUX功能配置)

软件手册第36章, 介绍了IOMUX需要配置寄存器的所有信息。根据三个寄存器的名称进行查找

    Pad的控制寄存器名称 SW_PAD_CTL_PAD_CSI0_DATA11
    Mux的控制寄存器名称 IOMUX_SW_MUX_CTL_PAD_CSI0_DATA11
    Select Input的寄存器名称 IOMUXC_UART1_UART_RX_DATA_SELECT_INPUT

IOMUX_SW_MUX_CTL_PAD_CSI0_DATA11
偏移地址:284h
配置MUX_MODE的模式为ALT3,就能使用UART1_RX_DATA的功能
IOMUX_SW_PAD_CTL_PAD_CSI0_DATA11
偏移地址:654h
Pad控制寄存器主要用于控制引脚的上拉电阻,下拉电阻和电压控制

IOMUXC_UART1_UART_RX_DATA_SELECT_INPUT
偏移地址:920h

下表是我统计的EIM引脚常用属性,这几个偏移地址对我们配置引脚至关重要,由于飞凌提供的内核已经配置好了,在此只记录一下网上的用法解析。

在linux或android系统中,假如我们要配置飞思卡尔IMX6处理器的GPIO管脚,比如是GPIO_19这个管脚,那么要像这样:

#define MX6Q_PAD_GPIO_19__GPIO_4_5		(_MX6Q_PAD_GPIO_19__GPIO_4_5|MUX_PAD_CTRL(NO_PAD_CTRL))

其中_MX6Q_PAD_GPIO_19__GPIO_4_5定义为:

#define _MX6Q_PAD_GPIO_19__GPIO_4_5  IOMUX_PAD(0x0624, 0x0254, 5, 0x0000, 0, 0)

这个IOMUX_PAD宏是定义GPIO的关键宏,其原型为:

#define IOMUX_PAD(_pad_ctrl_ofs, _mux_ctrl_ofs, _mux_mode, _sel_input_ofs, _sel_input, _pad_ctrl)

IOMUX_PAD宏有6个参数,每个参数的意思是:

具体的含义要结合IMX6数据手册【Chapter 36 IMOUX Controller(IOMUXC)】的内容。

开始配置

  非设备树:
1、配置引脚复用(/linux-3.0.35/arch/arm/mach-mx6/board-mx6dl_c_sabresd.h)

static iomux_v3_cfg_t mx6dl_weimram_pads[] __initdata = {
    
    MX6DL_PAD_EIM_OE__WEIM_WEIM_OE,// write signal
    MX6DL_PAD_EIM_RW__WEIM_WEIM_RW,// read signal
	MX6DL_PAD_EIM_CS0__WEIM_WEIM_CS_0,
   // MX6DL_PAD_EIM_EB0__WEIM_WEIM_EB_0,
	MX6DL_PAD_EIM_WAIT__WEIM_WEIM_WAIT,
    MX6DL_PAD_EIM_EB1__WEIM_WEIM_EB_1,
    MX6DL_PAD_EIM_EB2__WEIM_WEIM_EB_2,
    MX6DL_PAD_EIM_EB3__WEIM_WEIM_EB_3,
    
    //MX6DL_PAD_DISP0_DAT8__GPIO_4_29,

    MX6DL_PAD_EIM_LBA__WEIM_WEIM_LBA,
    MX6DL_PAD_EIM_BCLK__WEIM_WEIM_BCLK,

    
	//16根数据线
    MX6DL_PAD_EIM_D16__WEIM_WEIM_D_16,
    MX6DL_PAD_EIM_D17__WEIM_WEIM_D_17,
    MX6DL_PAD_EIM_D18__WEIM_WEIM_D_18,
    MX6DL_PAD_EIM_D19__WEIM_WEIM_D_19,
    MX6DL_PAD_EIM_D20__WEIM_WEIM_D_20,
    MX6DL_PAD_EIM_D21__WEIM_WEIM_D_21,
    MX6DL_PAD_EIM_D22__WEIM_WEIM_D_22,
    MX6DL_PAD_EIM_D23__WEIM_WEIM_D_23,
    MX6DL_PAD_EIM_D24__WEIM_WEIM_D_24,
    MX6DL_PAD_EIM_D25__WEIM_WEIM_D_25,
    MX6DL_PAD_EIM_D26__WEIM_WEIM_D_26,
    MX6DL_PAD_EIM_D27__WEIM_WEIM_D_27,
    MX6DL_PAD_EIM_D28__WEIM_WEIM_D_28,
    MX6DL_PAD_EIM_D29__WEIM_WEIM_D_29,
    MX6DL_PAD_EIM_D30__WEIM_WEIM_D_30,
    MX6DL_PAD_EIM_D31__WEIM_WEIM_D_31,

    
    
    //MX6DL_PAD_EIM_A24__GPIO_5_4,
	//16根地址线
    MX6DL_PAD_EIM_DA0__WEIM_WEIM_DA_A_0,
    MX6DL_PAD_EIM_DA1__WEIM_WEIM_DA_A_1,
    MX6DL_PAD_EIM_DA2__WEIM_WEIM_DA_A_2,
    MX6DL_PAD_EIM_DA3__WEIM_WEIM_DA_A_3,
    MX6DL_PAD_EIM_DA4__WEIM_WEIM_DA_A_4,
    MX6DL_PAD_EIM_DA5__WEIM_WEIM_DA_A_5,
    MX6DL_PAD_EIM_DA6__WEIM_WEIM_DA_A_6,
    MX6DL_PAD_EIM_DA7__WEIM_WEIM_DA_A_7,
    MX6DL_PAD_EIM_DA8__WEIM_WEIM_DA_A_8,
    MX6DL_PAD_EIM_DA9__WEIM_WEIM_DA_A_9,
    MX6DL_PAD_EIM_DA10__WEIM_WEIM_DA_A_10,
    MX6DL_PAD_EIM_DA11__WEIM_WEIM_DA_A_11,
    MX6DL_PAD_EIM_DA12__WEIM_WEIM_DA_A_12,
    MX6DL_PAD_EIM_DA13__WEIM_WEIM_DA_A_13,
    MX6DL_PAD_EIM_DA14__WEIM_WEIM_DA_A_14,
    MX6DL_PAD_EIM_DA15__WEIM_WEIM_DA_A_15,

    // MX6DL_PAD_EIM_A16__WEIM_WEIM_A_16,
    // MX6DL_PAD_EIM_A17__WEIM_WEIM_A_17,
    // MX6DL_PAD_EIM_A18__WEIM_WEIM_A_18,
    // MX6DL_PAD_EIM_A19__WEIM_WEIM_A_19,
    // MX6DL_PAD_EIM_A20__WEIM_WEIM_A_20,
    // MX6DL_PAD_EIM_A21__WEIM_WEIM_A_21,
    // MX6DL_PAD_EIM_A22__WEIM_WEIM_A_22,
	// MX6DL_PAD_EIM_A23__WEIM_WEIM_A_23,
	// MX6DL_PAD_EIM_A24__WEIM_WEIM_A_24,//bs add
	// MX6DL_PAD_EIM_A25__WEIM_WEIM_A_25,//bs add


	// //用作中断处理
    // MX6DL_PAD_EIM_D28__GPIO_3_28,
    // MX6DL_PAD_EIM_D29__GPIO_3_29,
    // MX6DL_PAD_EIM_D30__GPIO_3_30,
    // MX6DL_PAD_EIM_D31__GPIO_3_31,

};
#endif

2、去掉其他的引脚功能复用
把所有你需要用到的EIM引脚的功能复用全部屏蔽

	
//eim bus	MX6DL_PAD_EIM_CS0__ECSPI2_SCLK,
//eim bus	MX6DL_PAD_EIM_CS1__ECSPI2_MOSI,
//eim bus	MX6DL_PAD_EIM_OE__ECSPI2_MISO,
//eim bus	MX6DL_PAD_EIM_LBA__GPIO_2_27,
	//	MX6Q_PAD_EIM_D23__GPIO_3_23,		
//eim bus	MX6DL_PAD_EIM_A22__GPIO_2_16,	
//eim bus	MX6DL_PAD_EIM_A21__GPIO_2_17,	
//eim bus	MX6DL_PAD_EIM_A20__GPIO_2_18,	
//eim bus	MX6DL_PAD_EIM_A19__GPIO_2_19,	
//eim bus	MX6DL_PAD_EIM_A18__GPIO_2_20,	
//eim bus	MX6DL_PAD_EIM_A17__GPIO_2_21,	
//eim bus	MX6DL_PAD_EIM_A16__GPIO_2_22,	
//eim bus	MX6DL_PAD_EIM_RW__GPIO_2_26,	
	//MDLX6Q_PAD_EIM_LBA__GPIO_2_27,	
//eim bus	MX6DL_PAD_EIM_EB1__GPIO_2_29,	
//eim bus	MX6DL_PAD_EIM_EB3__GPIO_2_31,	

	
//eim bus	MX6DL_PAD_EIM_DA0__GPIO_3_0,	
//eim bus	MX6DL_PAD_EIM_DA1__GPIO_3_1,	
//eim bus	MX6DL_PAD_EIM_DA2__GPIO_3_2,	
//eim bus	MX6DL_PAD_EIM_DA3__GPIO_3_3,	
//eim bus	MX6DL_PAD_EIM_DA4__GPIO_3_4,	
//eim bus	MX6DL_PAD_EIM_DA5__GPIO_3_5,	
//eim bus	MX6DL_PAD_EIM_DA6__GPIO_3_6,	
//eim bus	MX6DL_PAD_EIM_DA7__GPIO_3_7,	
//eim bus	MX6DL_PAD_EIM_DA8__GPIO_3_8,	
//eim bus	MX6DL_PAD_EIM_DA9__GPIO_3_9,	
//eim bus	MX6DL_PAD_EIM_DA10__GPIO_3_10,	
//eim bus	MX6DL_PAD_EIM_DA11__GPIO_3_11,	
//eim bus	MX6DL_PAD_EIM_DA12__GPIO_3_12,	
//eim bus	MX6DL_PAD_EIM_DA13__GPIO_3_13,	
//eim bus	MX6DL_PAD_EIM_DA14__GPIO_3_14,	
//eim bus	MX6DL_PAD_EIM_DA15__GPIO_3_15,	

//eim bus	MX6DL_PAD_EIM_D29__GPIO_3_29,

//eim bus	MX6DL_PAD_EIM_D16__GPIO_3_16,

	
//eim bus	MX6DL_PAD_EIM_WAIT__GPIO_5_0,
//eim bus	MX6DL_PAD_EIM_A24__GPIO_5_4,	

	
//eim bus	MX6DL_PAD_EIM_A23__GPIO_6_6,	

//uart2		MX6DL_PAD_EIM_D26__UART2_TXD,
//uart2		MX6DL_PAD_EIM_D27__UART2_RXD,

//eim bus	MX6DL_PAD_EIM_D22__GPIO_3_22,
//eim bus	MX6DL_PAD_EIM_D21__USBOH3_USBOTG_OC,
//eim bus	MX6DL_PAD_EIM_D30__USBOH3_USBH1_OC,

//eim bus	MX6DL_PAD_NANDF_D3__GPIO_2_3,

3、添加EIM总线节点
搜索文件可得board-mx6dl_c_sabresd.h头文件被board-mx6q_c_sabresd.c所引用,由此可知前两步激活的引脚配置需在此文件中激活使用。

static int mx6q_setup_weim_cs(void)
{
        //nor
        void __iomem *ram_reg = MX6_IO_ADDRESS(WEIM_BASE_ADDR);//EIM memory base phy addr=0x21B8000
        void __iomem *ccm_reg = MX6_IO_ADDRESS(CCM_BASE_ADDR);
        unsigned int reg;
        struct clk *clk;
        u32 rate;
        
        //reg = readl(IOMUXC_GPR1);
        //printk("gpr1 reg=%xn",reg); 
        
        reg = readl(ccm_reg + 0x80);
        reg |= 0x00000C00;
        writel(reg, ccm_reg + 0x80);
        clk = clk_get(NULL, "emi_slow_clk");
        if (IS_ERR(clk))
                printk(KERN_ERR "emi_slow_clk not foundn");
    	rate=clk_get_rate(clk);
        //printk("emi_slow_clk rate=%dn",rate);
        if(rate!=132000000)
                printk(KERN_ERR "Warning: emi_slow_clk not set to 132 MHz!"
                                " WEIM NOR timing may be incorrect! rate %d n", rate);
    	//rate=clk_round_rate(clk,264000000);//37MHZ
    	//clk_set_rate(clk,rate); 
        printk("emi_slow_clk rate=%dn",rate);
        clk_put(clk);
	
        writel(0x00020081, ram_reg);//EIM_CS0GCR1 
	
        writel(0x00000000, ram_reg + 0x4); //EIM_CS0GCR2
	
        writel(0x01080000, ram_reg + 0x8);//EIM_CS0RCR1
	
        writel(0x000000000, ram_reg + 0xc);//EIM_CS0RCR2
	
        writel(0x01000000, ram_reg + 0x10);//EIM_CS0WCR1 WWSC=3 EIM clk

	

        writel(0x00000000, ram_reg + 0x14);
        writel(0x00000000, ram_reg + 0x090);
	//init pad
  	if (cpu_is_mx6dl())
        	mxc_iomux_v3_setup_multiple_pads(mx6dl_weimram_pads,ARRAY_SIZE(mx6dl_weimram_pads));
  	else
        	mxc_iomux_v3_setup_multiple_pads(mx6q_weimram_pads,ARRAY_SIZE(mx6q_weimram_pads));
	//init pad
        printk("imx6q weim_cs0 initn");
}

static struct eim_mxc_pdata eim_mxc = {
	.init = mx6q_setup_weim_cs,
};

//CS0_BASE_ADDR 0x08000000 phyaddr + 0x8000000(128MB)
static struct resource weim_fpga_resource = {
        .start      = CS0_BASE_ADDR,
        .end        = CS0_BASE_ADDR  +  SZ_128M - 1,
        .flags      = IORESOURCE_MEM,
};

static struct platform_device weim_fpga_device = {
		.name          = "imx6q-fpga",
        .id            = -1,
        .num_resources = 1,
        .resource      = &weim_fpga_resource,
		.dev	       = {
                .platform_data = &eim_mxc,
        }
};

且在mx6_sabresd_board_init函数中,注册设备。若注册成功在/sys/bus/platform/devices/可见imx6q-fpga设备。
在/sys/bus/platform/drivers/可见imx6q-fpga驱动,此处不知原因,按理说并没有注册驱动,不知为何驱动会产生,导致后续在ko中注册驱动失败。

	
	platform_device_register(&weim_fpga_device);

注:此处需根据需要修改writel函数内6个寄存器的值。

  设备树:(未验证)
一、首先进行节点添加。
1、arch/arm/boot/dts/imx6qdl-sabresd.dtsi文件中添加如下

&weim {
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_weim_fpga &pinctrl_weim_cs0>;
        #address-cells = <2>;
        #size-cells = <1>;
        ranges = <0 0 0x08000000 0x08000000>;
        status = "disabled"; 
       fsl,weim-cs-gpr = <&gpr>;

        fpga@0,0 {
                compatible = "fpga-user";
                reg = <0 0 0x08000000>;
                #address-cells = <1>;
                #size-cells = <1>;
                bank-width = <2>;
                fsl,weim-cs-timing = <0x00640081 0x00000010 0x1c020000
                                0x0000c000 0x1404a38e 0x00000000>;
        };
};

为eim总线的一些配置信息。
2、arch/arm/boot/dts/imx6dl-c-sabresd.dts文件中添加weim节点的控制。

&weim {
       status = "okay";
};

添加一个weim节点的控制,并设置其状态为okay。

二、添加引脚配置
arch/arm/boot/dts/imx6qdl-sabresd.dtsi文件中添加一个fpga的pinctrl和一个cs引脚的pinctrl。这部分在第一步的weim节点中的pinctrl-0参数对这两个pinctrl有调用。对应的是相应的名字。

 pinctrl_weim_fpga: weimfpgagrp {
                        fsl,pins = <
                                MX6QDL_PAD_EIM_OE__EIM_OE_B             0xb0b1
                                MX6QDL_PAD_EIM_RW__EIM_RW               0xb0b1
                                MX6QDL_PAD_EIM_WAIT__EIM_WAIT_B         0xb060
                                MX6QDL_PAD_EIM_DA5__EIM_AD05            0xb0b1
                                MX6QDL_PAD_EIM_DA4__EIM_AD04            0xb0b1
                                MX6QDL_PAD_EIM_DA3__EIM_AD03            0xb0b1
                                MX6QDL_PAD_EIM_DA2__EIM_AD02            0xb0b1
                                MX6QDL_PAD_EIM_DA1__EIM_AD01            0xb0b1
                                MX6QDL_PAD_EIM_DA0__EIM_AD00            0xb0b1
                                MX6QDL_PAD_CSI0_DATA_EN__EIM_DATA00     0xb0b1 
                                MX6QDL_PAD_CSI0_VSYNC__EIM_DATA01       0xb0b1
                                MX6QDL_PAD_CSI0_DAT4__EIM_DATA02        0xb0b1
                                MX6QDL_PAD_CSI0_DAT5__EIM_DATA03        0xb0b1
                                MX6QDL_PAD_CSI0_DAT6__EIM_DATA04        0xb0b1
                                MX6QDL_PAD_CSI0_DAT7__EIM_DATA05        0xb0b1
                                MX6QDL_PAD_CSI0_DAT8__EIM_DATA06        0xb0b1
                                MX6QDL_PAD_CSI0_DAT9__EIM_DATA07        0xb0b1
                               
                        >;
                };

                pinctrl_weim_cs0: weimcs0grp {
                        fsl,pins = <
                                MX6QDL_PAD_EIM_CS0__EIM_CS0_B           0xb0b1
                        >;
                };

三、去掉用到的引脚的其他复用功能。

  pinctrl_i2c1: i2c1grp {
                        fsl,pins = <
-                               MX6QDL_PAD_CSI0_DAT8__I2C1_SDA          0x4001b8b1
-                               MX6QDL_PAD_CSI0_DAT9__I2C1_SCL          0x4001b8b1
+                       
                        >;
                };
 
@@ -1097,10 +1127,10 @@
                                MX6QDL_PAD_CSI0_DAT17__IPU1_CSI0_DATA17    0x80000000
                                MX6QDL_PAD_CSI0_DAT18__IPU1_CSI0_DATA18    0x80000000
                                MX6QDL_PAD_CSI0_DAT19__IPU1_CSI0_DATA19    0x80000000
-                               MX6QDL_PAD_CSI0_DATA_EN__IPU1_CSI0_DATA_EN 0x80000000
-                               MX6QDL_PAD_CSI0_DATA_EN__IPU1_CSI0_DATA_EN 0x80000000
+                               //MX6QDL_PAD_CSI0_DATA_EN__IPU1_CSI0_DATA_EN 0x80000000
                                MX6QDL_PAD_CSI0_PIXCLK__IPU1_CSI0_PIXCLK   0x80000000
                                MX6QDL_PAD_CSI0_MCLK__IPU1_CSI0_HSYNC      0x80000000
-                               MX6QDL_PAD_CSI0_VSYNC__IPU1_CSI0_VSYNC     0x80000000
+                               //MX6QDL_PAD_CSI0_VSYNC__IPU1_CSI0_VSYNC     0x80000000
                                MX6QDL_PAD_SD1_DAT2__GPIO1_IO19            0x80000000
                                MX6QDL_PAD_SD1_DAT0__GPIO1_IO16            0x80000000
                                MX6QDL_PAD_GPIO_2__GPIO1_IO02              0x80000000


步骤二:编写KO(EIM口控制程序)

由简入繁,先介绍下最简单的字符设备的创建方法

一、注册老字符设备驱动模块

Linux 驱动有两种运行方式:

将驱动编译进Linux内核中, 这样当Linux内核启动的时候就会自动运行驱动程序。
将驱动编译成模块(扩展名为 .ko), 在Linux内核启动以后使用“insmod”命令加载驱动模块。

在驱动开发阶段一般都将其编译为模块,不需要编译整个Linux代码,方便调试驱动程序。当驱动开发完成后,根据实际需要,可以选择是否将驱动编译进Linux内核中。
Linux中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成。

主设备号:表示某一个具体的驱动
次设备号:表示使用这个驱动的各个设备
#define CHRDEVBASE_MAJOR	200				
#define CHRDEVBASE_NAME		"chrdevbase" 	


static int chrdevbase_open(struct inode *inode, struct file *filp)
{
	printk("[BSP] chrdevbase open!n");
	return 0;
}


static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	printk("[BSP] chrdevbase read!n");
	return 0;
}


static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	printk("[BSP] chrdevbase write!n");
	return 0;
}


static int chrdevbase_release(struct inode *inode, struct file *filp)
{
	printk("[BSP] chrdevbase release!n");
	return 0;
}


static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE,	
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};


static int __init chrdevbase_init(void)
{
	int retvalue = 0;

	
	retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
	if(retvalue < 0)
    {
		printk("[BSP] chrdevbase driver register failedn");
	}
	printk("[BSP] chrdevbase init!n");
	return 0;
}


static void __exit chrdevbase_exit(void)
{
	
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
	printk("[BSP] chrdevbase exit!n");
}


module_init(chrdevbase_init);
module_exit(chrdevbase_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("yanggang");

通过如下三条指令,可加载驱动、查看驱动、查看系统是否列举该设备,主设备号为程序中设定的200。

insmod chrdevbase.ko
lsmod
cat /proc/devices 

驱动加载后,还要在/dev目录下创建一个对应的设备节点文件(应用程序就是通过该节点文件实现对设备的操作)。
输入如下2条命令创建/dev/chrdevbase这个设备节点文件,并查看结果:

mknod /dev/chrdevbase c 200 0 
ls /dev/chrdevbase -l

本节介绍了嵌入式Linux驱动开发中的基础驱动——字符驱动开发的基本模式,注册了一个虚拟的字符设备驱动。

二、注册新字符设备驱动模块

使用register_chrdev函数注册字符设备,需要指定一个设备号,这就造成:

1、 需要事先确定好哪些主设备号没有使用
2、会将一个主设备号下的所有次设备号都使用掉,比如主设备号为200,那么 0~1048575(2^20-1)这个区间的次设备号就全部都被占用了

回顾上一篇的操作,先是加载驱动:

加载完,还有手动使用mknod指令来手动创建该设备节点,并且指定驱动程序中写死的设备号:

本节,就要使用一种新的字符驱动编写方式,实现设备号的自动分配,省去mknod指令操作。
创建和删除类

自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在cdev_add函数后面添加自动创建设备节点相关代码。
1、创建class类
首先要创建一个class类,其实是个结构体,定义在include/linux/device.h里面。class_create是类创建函数(宏定义):

#define class_create(owner, name)  
({  
    static struct lock_class_key __key;  
    __class_create(owner, name, &__key);  
}) 

struct class *__class_create(struct module *owner, 
                             const char *name, 
                             struct lock_class_key *key) 

卸载驱动程序的时候需要使用函数为class_destroy删除掉类:

void class_destroy(struct class *cls); 

2、创建设备
创建好类以后还不能实现自动创建设备节点,还需要在这个类下创建一个设备。使用device_create函数创建设备:

struct device *device_create(struct clas *class,  
                             struct device *parent,  
                             dev_t devt,  
                             void *drvdata,  
                             const char *fmt, ...) 

参数最后的…表示这在是一个可变参数的函数。
3、设置文件私有数据
每个硬件设备都有一些属性, 比如主设备号(dev_t),类(class)、设备(device)、开关状态(state)等等,在编写驱动的时候你可以将这些属性全部写成变量的形式:

dev_t         devid;      
struct cdev   cdev;       
struct class  *class;     
struct device *device;    
int           major;      
int           minor;      

可以将所有属性信封装到结构体中, 并在编写驱动open函数的时候将其作为私有数据添加到设备文件中:

 
struct test_dev{ 
    dev_t         devid;      
    struct cdev   cdev;       
    struct class  *class;     
    struct device *device;    
    int           major;      
    int           minor;      
}; 
struct test_dev testdev; 

 
static int test_open(struct inode *inode, struct file *filp) 
{ 
    filp->private_data = &testdev;  
    return 0; 
} 

源码如下:

#define CHRDEVBASE_CNT			1		
#define CHRDEVBASE_NAME	 "chrdevbase"	


struct newchr_dev{
	dev_t         devid;	
	struct cdev   cdev;		
	struct class  *class;	
	struct device *device;	
	int           major;	
	int           minor;	
};

struct newchr_dev chrdevbase; 


static int chrdevbase_open(struct inode *inode, struct file *filp)
{
	printk("chrdevbase open!rn");
    filp->private_data = &chrdevbase; 
	return 0;
}


static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	printk("[BSP] chrdevbase read!n");
	return 0;
}


static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	printk("[BSP] chrdevbase write!n");
	return 0;
}


static int chrdevbase_release(struct inode *inode, struct file *filp)
{
	printk("[BSP] chrdevbase release!n");
	return 0;
}


static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE,	
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};


static int __init chrdevbase_init(void)
{
    
	if (chrdevbase.major) //1、创建设备号:定义了设备号 
    {
		chrdevbase.devid = MKDEV(chrdevbase.major, 0);//大部分驱动次设备号都选择0
	
		register_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT, CHRDEVBASE_NAME);
	} 
	else //1、创建设备号:没有定义设备号
	{
		
		alloc_chrdev_region(&chrdevbase.devid, 0, CHRDEVBASE_CNT, CHRDEVBASE_NAME);	
		chrdevbase.major = MAJOR(chrdevbase.devid);	
		chrdevbase.minor = MINOR(chrdevbase.devid);	
	}
	printk("chrdevbase major=%d,minor=%drn",chrdevbase.major, chrdevbase.minor);	
	
	// 2、初始化cdev
	chrdevbase.cdev.owner = THIS_MODULE;
	
	cdev_init(&chrdevbase.cdev, &chrdevbase_fops);
	// 3、添加一个cdev
	
	cdev_add(&chrdevbase.cdev, chrdevbase.devid, CHRDEVBASE_CNT);
	// 4、创建类
	chrdevbase.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
	if (IS_ERR(chrdevbase.class)) 
	{
		return PTR_ERR(chrdevbase.class);
	}
	// 5、创建设备
	
	chrdevbase.device = device_create(chrdevbase.class, NULL, chrdevbase.devid, NULL, CHRDEVBASE_NAME);
	if (IS_ERR(chrdevbase.device)) 
	{
		return PTR_ERR(chrdevbase.device);
	}
    
	printk("chrdevbase init done!rn");
	return 0;
}


static void __exit chrdevbase_exit(void)
{
    //1、注销字符设备驱动
	cdev_del(&chrdevbase.cdev);
	
	//2、注销设备号
	unregister_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT);
	//3、删除设备:
	device_destroy(chrdevbase.class, chrdevbase.devid);
	//4、删除类:
	class_destroy(chrdevbase.class);
    
	printk("chrdevbase exit done!rn");
}



module_init(chrdevbase_init);
module_exit(chrdevbase_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("yanggang");

通过一张图来对比新旧两种驱动编写方式的区别:
旧方式编写驱动的流程

新方式编写驱动的流程

注:旧的驱动方式需要额外使用mknod指令来手动创建该设备节点。

未完待续

get_user与put_user
在内核空间和用户空间交换数据时,get_user和put_user是两个两用的函数。相对于copy_to_user和copy_from_user(将在另一篇博客中分析),这两个函数主要用于完成一些简单类型变量(char、int、long等)的拷贝任务,对于一些复合类型的变量,比如数据结构或者数组类型,get_user和put_user函数还是无法胜任,这两个函数内部将对指针指向的对象长度进行检查,在arm平台上只支持长度为1,2,4,8的变量。下面我具体分析,首先看get_user的定义(linux/include/asm-arm/uaccess.h):

user app和kernel模块之间传输数据不能使用memcpy,必须使用copy_to/from_user或是put/get_user。原因就是kernel和user app内存不能直接互访。
首先 Linux的kernel和user app运行在不同的模式。在ARM上就是kernel运行在SVC模式(最高级),App运行在user模式。当user app执行一个system call调用kernel代码,kernel会完成CPU的模式切换。SVC模式能访问的寄存器更多,如果在kernel中对user app传入的地址直接访问,可能会有以下问题:传入的地址是错误的,则kernel也可以访问,可能会破坏其他kernel内容,这会引起安全问题,memcpy不会进行地址检查,使用专用的put_user就会执行地址检查,判断是否是<0xc0000000,是否在user app地址空间之内。同时在ARM平台上,最后调用的函数是__put_user_1/2/4/8。这个是一个汇编函数,使用的数据访问指令为ldrbt,这个ARM汇编指令使用user mode来访问内存,如果这个访问非法,这会触发一个exception,在kernel中都加入了一个异常函数表,出现异常则调用__get_user_bad。

put_user ( x, ptr)— 将一个简单的值写入用户空间。
x:要复制到用户空间的值。
ptr:目标地址,在用户空间中。
描述:这个宏将一个简单的值从内核空间复制到用户空间。 它支持简单的类型,如 char 和 int,但不支持更大的数据类型,如结构或数组。仅限用户上下文。 此功能可能会休眠。ptr 必须具有指向简单变量的指针类型,并且 x 必须可分配给取消引用 ptr 的结果。
return:成功返回零,错误返回 -EFAULT。

get_user ( x, ptr);— 从用户空间获取一个简单的变量。
x:存储结果的变量。
ptr:源地址,在用户空间。
描述:这个宏将一个简单的变量从用户空间复制到内核空间。 它支持简单的类型,如 char 和 int,但不支持更大的数据类型,如结构或数组。仅限用户上下文。 此功能可能会休眠。
ptr 必须具有指向简单变量的指针类型,并且取消引用 ptr 的结果必须可分配给 x 而无需强制转换。
成功返回零,错误返回 -EFAULT。 出错时,变量 x 设置为零。

分配页内存的函数们:

unsigned long get_zeroed_page(unsigned int flags);
******分配一页内存,返回一个指向新页的指针并且对该页进行了清零操作

unsigned long __get_free_page(unsigned int flags);
******分配一页内存,返回一个指向新页的指针,没有对该页进行清零操作.

unsigned long __get_free_pages(unsigned int flags, unsigned int order);
******用来分配多页内存的,分配的内存可能是物理上连续的几个页长,但没有清零.

******对于你自己指定要分配多少字节的时候,若要使用__get_free_pages时,需要调用get_order函数来帮你计算这些你分配的字节是多少页,有时分配的比你要求的多,这是因为不足一页的按照一页的大小来计算.
******order是2的幂数,即order=0,是一页,order=1是两页,order=2,是四页,order=3是八页…

******如果order太大没有连续大小的内存区可用,页内存分配会失败.order允许的最大值是10(1024页)或者11(2048页).

int get_order(unsigned long size)
******用来计算size可以分成多少页的,返回值是2的幂

当使用完分配的内存的时候务必要释放这些内存,避免内存泄露(内存越用越少),使用下面的函数

void free_pages(unsigned long addr, unsigned long order)
******用来释放分配的多页内存

void free_page(unsigned long addr)
******用来释放分配的一页内存.

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/976445.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号