2019-09-19 09:46:38 A15172315112 阅读数 134

写一个QEP的驱动
需要直接操作寄存器,板子是cortexA9 linux内核4.1.18
部分代码如下:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
 
static int major = 231;
static int minor=0;
static dev_t devno;
static struct class *cls;
static struct device *test_device;

static unsigned char* ctrl_conf_gpmcad12;
static unsigned char* soc_pwmss2_reg;
static unsigned char* pwmssBaseAddr;
static unsigned char* pwmssBaseAddr2;
static unsigned char * pwmssBaseAddr3;
static unsigned char * ctrl_clock;
static int demo_buffer[2];

//引脚复用  qep2  引脚
#define CTRL_CONF_GPMC_AD12     0x44E10830//eQEP2A_IN
#define AD12    		0x0000 //eQEP2A_IN                                  
#define AD13    		0x0004 //eQEP2B_IN              
#define AD14    		0x0008 // eQEP2_INDEX
#define AD15    		0x000c //eQEP2_STROBE

#define SOC_PWMSS2_REG          0x48304000//PWMSS的物理地址(图三)
#define PWMSS_CLKCONFIG         0x0008//(图一)
0
#define  PWMSS2BASE             0x48304180 //PWMSS_EQEP的c物理地址(图三)
#define  EQEP_QPOSCNT           0x0   //(图二)
#define  EQEP_QPOSMAX         	0x8   //(图二)
#define  EQEP_QUPRD                0x20 //(图二)

#define  PWMSS2BASE2            0x483041A0
#define  EQEP_QDECCTL       	0x08//eQEP Decoder Control 
#define  EQEP_QEPCTL        	0x0A //eQEP Control 
#define  EQEP_QCAPCTL       	0x0C//eQEP Capture Control

#define  PWMSS2BASE3            0x483041B0
#define  EQEP_QEPSTS             0x08//eQEP Status 

#define CONTROL_MODULE_CTRL_PWMSS    0x44E10664         
#define CTRL_PWMSS    0x0000

static void fs4412_qep_init(void)
{
//管脚寄存器映射  4个寄存器
       ctrl_conf_gpmcad12 = ioremap(CTRL_CONF_GPMC_AD12,16);
       soc_pwmss2_reg = ioremap(SOC_PWMSS2_REG,16);
	   pwmssBaseAddr = ioremap(PWMSS2BASE,40);
	   pwmssBaseAddr2= ioremap(PWMSS2BASE2,20);
	   pwmssBaseAddr3 = ioremap(PWMSS2BASE3,12);
//时钟寄存器映射
    ctrl_clock= ioremap(CONTROL_MODULE_CTRL_PWMSS,4);
//-----------------------------------------------------------------------------------------
//管脚配置
  printk("%x",readw(pwmssBaseAddr2 + EQEP_QDECCTL));
   readl(ctrl_conf_gpmcad12 + AD13);
   readl(soc_pwmss2_reg + PWMSS_CLKCONFIG);
   ..............
   }

每次一到最后一行就是Segementation Fault…
原因可能是soc_pwmss2_reg的指针类型错误?还是指针越界?还是ioremap错误?


这是datasheet相关…

这个是
图1 这个是soc_pwmss2_reg的寄存器偏移地址,用到了CLKCONFIG寄存器

在这里插入图片描述
图2 这个是pwmssBaseAddr /pwmssBaseAddr2 /pwmssBaseAddr3的寄存器偏移
主要用到了QDECCTL QEPCTL QCAPCTL QEPSTS 四个寄存器

在这里插入图片描述
图3 基地址

2012-07-19 10:19:45 heqiuya 阅读数 5661
先上一个测试代码来说明一下:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/io.h>

#define at91_pwm_read(reg)                  __raw_readl(reg)   
#define at91_pwm_write(reg, val)            __raw_writel((val), reg)

#define PWMC_BASE 0xfffb8000
#define PWM_MR_OFF              0x00000000    ///< PWM Mode Register offset.
//#define PWM_MR     __phys_to_pfn(PWMC_BASE + PWM_MR_OFF) ///< PWM Mode Register.  //这里是关键
#define PWM_MR     (PWMC_BASE + PWM_MR_OFF) ///< PWM Mode Register.

static int __init my_led_init(void)
{
	unsigned long value;
	

	
	value = at91_pwm_read(PWM_MR);
	printk("0xfffb8000 = %x", value);
	at91_pwm_write(PWM_MR, 0xA2); 
	value = at91_pwm_read(PWM_MR);
	printk("0xfffb8000 = %x", value);
	
	at91_set_gpio_output(AT91_PIN_PD15, 0);
	at91_set_gpio_value(AT91_PIN_PD15, 0);
    return 0;
}
static void __exit my_led_exit(void)
{
   
	//at91_set_gpio_output(AT91_PIN_PD15, 0);
	at91_set_gpio_value(AT91_PIN_PD15, 1);

    return;
}

module_init(my_led_init)
module_exit(my_led_exit)

MODULE_LICENSE("GPL");


其实这就是一个简单的加载和卸载驱动的模块,因为我昨天做的pwm的驱动下载到开发板的时候总是报错,在代码里面找了很久都没有找到原因,

今天早上我把模块单独拿出来试试,最后确定是__raw_writel和__raw_readl的原因, 开始以为是我定义的地址出错了.后来调试了很久还是没有找到.上一个出错的信息;

<1>Unable to handle kernel paging request at virtual address fffb8000
pgd = c6c14000
[fffb8000] *pgd=705a5031, *pte=00000000, *ppte=00000000
Internal error: Oops: 17 [#1]
Modules linked in: myled(+) [last unloaded: myled]
CPU: 0    Not tainted  (2.6.30 #28)
PC is at my_led_init+0x10/0x58 [myled]
LR is at do_one_initcall+0x4c/0x17c
pc : [<bf00f010>]    lr : [<c01222d4>]    psr: 60000013
sp : c6c2df08  ip : 00000000  fp : bec68c94
r10: 00000000  r9 : c6c2c000  r8 : c0483140
r7 : bf00f000  r6 : 001e5060  r5 : bf00c01c  r4 : fffb8fff
r3 : 00000000  r2 : c6c2c000  r1 : 00000001  r0 : bf00f000
Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
Control: 0005317f  Table: 76c14000  DAC: 00000015
Process insmod (pid: 450, stack limit = 0xc6c2c268)
Stack: (0xc6c2df08 to 0xc6c2e000)
df00:                   00000000 00000ae4 bf00c320 c01222d4 c888e2ce c79cf420 
df20: c888ea10 00000016 00000018 c888e3d0 bf00c32c c888e768 c6c14000 00000000 
df40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
df60: 00000ae4 bf00c320 001e5060 00000ae4 bf00c320 001e5060 00000000 c0122ea8 
df80: 00000000 c0157f24 c7815740 ffffffff 001e44c0 00000ae4 bec68db1 000002ec 
dfa0: 00000080 c0122d00 00000ae4 bec68db1 001e5060 00000ae4 001e5050 bec68db1 
dfc0: 00000ae4 bec68db1 000002ec 00000080 00000000 00000002 00000000 bec68c94 
dfe0: bec68ab8 bec68aa8 0002cdbc 00009300 20000010 001e5060 00000000 00000000 
[<bf00f010>] (my_led_init+0x10/0x58 [myled]) from [<c01222d4>] (do_one_initcall+0x4c/0x17c)
[<c01222d4>] (do_one_initcall+0x4c/0x17c) from [<c0157f24>] (sys_init_module+0x90/0x194)
[<c0157f24>] (sys_init_module+0x90/0x194) from [<c0122d00>] (ret_fast_syscall+0x0/0x2c)
Code: e92d4030 e59f5048 e3e04a47 e24dd004 (e5141fff) 
---[ end trace 55d98dffd0e5be81 ]---
Segmentation fault

要感谢Crack-Punk
http://blog.chinaunix.net/uid-27039867-id-3236478.html
前人植树,后人乘凉.把贵先生的内容转载到空间里面.

移植路上真是荆棘满路,时不时让你疯狂一两天,却无可奈何...
        网上已有很多相关的移植教程,第一步,当然是照着做。问题是我的Linux是2.6.19.2,与前些版本相比(据查,2.6.17版还在),在移植过程中,我发现有两个变化。一是 include/asm-arm/irq.h 里已删除一些irq操作的预定义,如:
extern void disable_irq(unsigned int);
extern void enable_irq(unsigned int);
int set_irq_type(unsigned int irq, unsigned int type);
而网上的那个cs8900.c驱动比较老,与Linux-2.6.19.2带的cs89x0相差很大(据说这个驱动也没有使用新的驱动模型,不知道,这里应该与dm9000比较一下)。导致出现很多错误,不能编译 cs8900.c ,一天就这样在郁闷中过去...
      旧的不去,新的不来,干脆就直接用cs89x0.c ^_^ google到一个 pathch-cs89x0 ,哪个开心啊...可惜这个patch是针对2.6.14,自动patch失败,手动补齐后再添一些头文件、修改一些小错误,终于编译通过。可是,狂跳几下后又得郁闷了,出现了内核编程常见错误oops:
cs89x0:cs89x0_probe(0x0)
Unable to handle kernel paging request at virtual address f400030a
pgd = c0004000
[f400030a] *pgd=00000000
Internal error: Oops: 5 [#1]
Modules linked in:
CPU: 0
PC is at readword+0x1c/0x2c
LR is at cs89x0_probe1+0xec/0x874
pc : []    lr : []    Not tainted
sp : c032feb0  ip : c032fec0  fp : c032febc
r10: f4000300  r9 : 00000000  r8 : 00000000
r7 : c0f3b800  r6 : c0f3ba60  r5 : c0f3b800  r4 : f4000300
r3 : f400030a  r2 : 00000000  r1 : f400030a  r0 : f4000300
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  Segment kernel
Control: 717F
Table: 30004000  DAC: 00000017
Process swapper (pid: 1, stack limit = 0xc032e250)
Stack: (0xc032feb0 to 0xc0330000)
fea0:                                     c032ff4c c032fec0 c001bec8 c0139fa8
fec0: ffffffff c0f3b803 0000000a c032ff4c ffffffff 00000002 c0f3b800 ffffffff
fee0: c032ff30 c032fef0 c00fa694 c00f9e88 0000000a ffffffff ffffffff 00000002
ff00: 3f0c47ff 00000000 c0f3b800 00000000 00000000 00000000 00000000 c0021290
ff20: c032ff3c c002619c c0f3b800 00000000 00000000 00000000 00000000 c0021290
ff40: c032ff6c c032ff50 c001c6e0 c001bdec c0026164 00000001 00000000 c00217a4
ff60: c032ff88 c032ff70 c001bb04 c001c660 00000001 00000000 c032e000 c032ffa0
ff80: c032ff8c c001bbd0 c001bad0 00000000 c0021208 c032fff4 c032ffa4 c0027100
ffa0: c001bb48 00000001 c0027e24 c003caf4 00000000 00000000 c0027058 c0043128
ffc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
ffe0: 00000000 00000000 00000000 c032fff8 c0043128 c0027068 0a00003e e5dc3001
Backtrace:
[] (readword+0x0/0x2c) from [] (cs89x0_probe1+0xec/0x874)
[] (cs89x0_probe1+0x0/0x874) from [] (cs89x0_probe+0x90/0x1)
[] (cs89x0_probe+0x0/0x108) from [] (probe_list2+0x44/0x78)
 r7 = C00217A4  r6 = 00000000  r5 = 00000001  r4 = C0026164
[] (probe_list2+0x0/0x78) from [] (net_olddevs_init+0x98/0x)
 r6 = C032E000  r5 = 00000000  r4 = 00000001
[] (net_olddevs_init+0x0/0xdc) from [] (init+0xa8/0x274)
 r5 = C0021208  r4 = 00000000
[] (init+0x0/0x274) from [] (do_exit+0x0/0x7d8)
Code: e0801001 e3510201 e1a03001 328334f2 (e1d300b0)
 <0>Kernel panic - not syncing: Attempted to kill init!
      前两天才在《Linux内核设计与实现》里看到的常见错误,想不到这么快就被我遇上...这里就是新版Linux第二个变化引起的错误。尝试更改映射的虚地址,无效。就在准备回去冲凉的瞬间,google里又出现一个好东西 ARM Linux Kernel Porting ,有原理图,有cs8900的实地址为0x19000000的解析(大致就是A24脚的高低电平控制cs8900开关,故+1000000),可惜是韩文,里面的文字不知说什么,但错误现象一样,根据里面的代码再修改cs89x0一遍(这个文件真的很乱了^_^)。十分可惜,还是不能在我睡前干一件好事...
      早上精神好啊,又经过一番搜索,终于发现问题,原来实地址与虚地址的映射结构 smdk2410_iodesc 里有所变化。第二个参数从原来的 unsigned long physical 变为 unsigned long pfn 。而从 smdk2410_iodesc 传入的SMDK2410_PA_CS8900A_BASE 仍然是一个 physical 值,因此出现 Unable to handle kernel paging request 错误。这里很感谢http://bibu.blogchina.com/bibu/4914641.html的作者,根据提示,将实地址作一个小小变化就Ok了 ^_^
#ifndef __ASM_ARCH_SMDK2410_H
#define __ASM_ARCH_SMDK2410_H

#define SMDK2410_PA_CS8900A_BASE       __phys_to_pfn(S3C2410_CS3 + 0x01000000) /* nGCS3 +0x01000000 */
#define SMDK2410_VA_CS8900A_BASE       S3C2410_ADDR(0x04000000)  /* 0xF4000000 */

#endif /* __ASM_ARCH_SMDK2410_H */
虚地址改多少无所谓,如0xE0000000,只要不和其它设备冲突就行。
      下面总结一下整个移植过程:
1. include/asm-arm/arch-s3c2410/map.h 
添加:
 /* CS8900 */
 #define S3C24XX_VA_CS8900   S3C2410_ADDR(0x04000000)
 #define S3C2410_PA_CS8900     __phys_to_pfn(0x19000000)
 #define S3C24XX_SZ_CS8900    SZ_1M
 #define S3C24XX_PA_CS8900    S3C2410_PA_CS8900
2. arch/arm/mach-s3c2410/mach-smdk2410.c 
修改
static struct map_desc smdk2410_iodesc[] __initdata={
        {S3C24XX_VA_CS8900IO, S3C2410_PA_CS8900, S3C24XX_SZ_CS8900, S3C24XX_SZ_CS8900, MT_DEVICE}
};
3. include/asm-arm/irq.h 
添加
int set_irq_type(unsigned int irq, unsigned int type);
4.  drivers/net/cs89x0.c
下面是我的 diff
--- ../linux2/linux-2.6.19.2/drivers/net/cs89x0.c 2007-01-11 03:10:37.000000000 +0800
+++ drivers/net/cs89x0.c 2007-02-04 21:16:42.000000000 +0800
@@ -96,6 +96,9 @@
   Dmitry Pervushin  : dpervushin@ru.mvista.com
                     : PNX010X platform support
 
+  Christian Pell    : chripell@gmail.com
+                    : SMDK2410 platform support, fixed bug with signed ioaddr
+
 */
 
 /* Always include 'config.h' first in case the user wants to turn on
@@ -194,6 +197,27 @@
 #define CIRRUS_DEFAULT_IRQ VH_INTC_INT_NUM_CASCADED_INTERRUPT_1 /* Event inputs bank 1 - ID 35/bit 3 */
 static unsigned int netcard_portlist[] __initdata = {CIRRUS_DEFAULT_BASE, 0};
 static unsigned int cs8900_irq_map[] = {CIRRUS_DEFAULT_IRQ, 0, 0, 0};
+
+#elif defined(CONFIG_ARCH_S3C2410) //Added weibing
+#include <linux/irq.h>
+#include <asm/irq.h>
+#include <asm/arch/irqs.h>
+#include <asm/arch/regs-mem.h>
+#include <asm/arch/regs-gpio.h>
+#include <asm/arch/smdk2410-map.h>
+
+static unsigned int netcard_portlist[] __initdata = {S3C24XX_VA_CS8900+0x300, 0};
+static unsigned int cs8900_irq_map[] = {IRQ_EINT9, 0, 0, 0};
+/*
+#ifdef request_region
+#undef request_region
+#endif
+#ifdef release_region
+#undef release_region
+#endif
+#define request_region(a,s,n) request_mem_region(a,s,n)
+#define release_region(a,s) release_mem_region(a,s)
+*/
 #else
 static unsigned int netcard_portlist[] __initdata =
    { 0x300, 0x320, 0x340, 0x360, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0};
@@ -246,7 +270,7 @@
 
 /* Index to functions, as function prototypes. */
 
-static int cs89x0_probe1(struct net_device *dev, int ioaddr, int modular);
+static int cs89x0_probe1(struct net_device *dev, unsigned int ioaddr, int modular);
 static int net_open(struct net_device *dev);
 static int net_send_packet(struct sk_buff *skb, struct net_device *dev);
 static irqreturn_t net_interrupt(int irq, void *dev_id);
@@ -324,6 +348,10 @@
  io = dev->base_addr;
  irq = dev->irq;
 
+#ifdef CONFIG_ARCH_S3C2410 //Added weibing
+ __raw_writel((__raw_readl(S3C2410_GPGCON)&~(0x3<<2))|(0x2<<2),S3C2410_GPGCON);
+ __raw_writel((__raw_readl(S3C2410_EXTINT1)&~(0x7<<4))|(0x4<<4),S3C2410_EXTINT1);
+#endif
  if (net_debug)
   printk("cs89x0:cs89x0_probe(0x%x)\n", io);
 
@@ -386,6 +414,18 @@
 {
  outw(value, base_addr + (portno << 1));
 }
+#elif defined(CONFIG_ARCH_S3C2410) //Added weibing
+static u16
+readword(unsigned long base_addr, int portno)
+{
+ return __raw_readw(base_addr+portno);
+}
+
+static void 
+writeword(unsigned long base_addr, int portno,u16 value) 
+{ 
+ __raw_writew(value,base_addr+portno); 
+} 
 #else
 static u16
 readword(unsigned long base_addr, int portno)
@@ -507,7 +547,7 @@
  */
 
 static int __init
-cs89x0_probe1(struct net_device *dev, int ioaddr, int modular)
+cs89x0_probe1(struct net_device *dev, unsigned int ioaddr, int modular)
 {
  struct net_local *lp = netdev_priv(dev);
  static unsigned version_printed;
@@ -638,7 +678,18 @@
     the driver will always do *something* instead of complain that
     adapter_cnf is 0. */
 
-#ifdef CONFIG_SH_HICOSH4
+#if defined CONFIG_ARCH_S3C2410 
+ lp->force=FORCE_RJ45; 
+ lp->auto_neg_cnf=IMM_BIT; 
+
+ dev->dev_addr[0]=0x09; /*setMACaddress*/ 
+ dev->dev_addr[1]=0x90; 
+ dev->dev_addr[2]=0x99; 
+ dev->dev_addr[3]=0x09; 
+ dev->dev_addr[4]=0x90; 
+ dev->dev_addr[5]=0x99; 
+
+#elif defined CONFIG_SH_HICOSH4
  if (1) {
   /* For the HiCO.SH4 board, things are different: we don't
      have EEPROM, but there is some data in flash, so we go
@@ -1036,7 +1087,7 @@
 {
 #if !defined(CONFIG_MACH_IXDP2351) && !defined(CONFIG_ARCH_IXDP2X01)
  struct net_local *lp = netdev_priv(dev);
- int ioaddr = dev->base_addr;
+ unsigned int ioaddr = dev->base_addr;
 #endif
  int reset_start_time;
 
@@ -1278,7 +1329,7 @@
  int i;
  int ret;
 
-#if !defined(CONFIG_SH_HICOSH4) && !defined(CONFIG_ARCH_PNX010X) /* uses irq#1, so this won't work */
+#if !defined(CONFIG_SH_HICOSH4) && !defined(CONFIG_ARCH_PNX010X) && !defined(CONFIG_ARCH_S3C2410) /* uses irq#1, so this won't work */
  if (dev->irq < 2) {
   /* Allow interrupts to be generated by the chip */
 /* Cirrus' release had this: */
@@ -1309,7 +1360,7 @@
  else
 #endif
  {
-#if !defined(CONFIG_MACH_IXDP2351) && !defined(CONFIG_ARCH_IXDP2X01) && !defined(CONFIG_ARCH_PNX010X)
+#if !defined(CONFIG_MACH_IXDP2351) && !defined(CONFIG_ARCH_IXDP2X01) && !defined(CONFIG_ARCH_PNX010X) && !defined(CONFIG_ARCH_S3C2410)
   if (((1 << dev->irq) & lp->irq_map) == 0) {
    printk(KERN_ERR "%s: IRQ %d is not in our map of allowable IRQs, which is %x\n",
                                dev->name, dev->irq, lp->irq_map);
@@ -1324,6 +1375,9 @@
   writereg(dev, PP_BusCTL, ENABLE_IRQ | MEMORY_ON);
 #endif
   write_irq(dev, lp->chip_type, dev->irq);
+#if defined(CONFIG_ARCH_S3C2410)
+  set_irq_type(dev->irq, IRQT_RISING);
+#endif
   ret = request_irq(dev->irq, &net_interrupt, 0, dev->name, dev);
   if (ret) {
    if (net_debug)
@@ -1394,7 +1448,7 @@
  case A_CNF_MEDIA_10B_2: result = lp->adapter_cnf & A_CNF_10B_2; break;
         default: result = lp->adapter_cnf & (A_CNF_10B_T | A_CNF_AUI | A_CNF_10B_2);
         }
-#ifdef CONFIG_ARCH_PNX010X
+#if defined(CONFIG_ARCH_PNX0105) || defined(CONFIG_ARCH_S3C2410)
  result = A_CNF_10B_T;
 #endif
         if (!result) {
@@ -1577,7 +1631,8 @@
 {
  struct net_device *dev = dev_id;
  struct net_local *lp;
- int ioaddr, status;
+ unsigned int ioaddr;
+ int status;
   int handled = 0;
 
  ioaddr = dev->base_addr;
@@ -1683,7 +1738,7 @@
  struct sk_buff *skb;
  int status, length;
 
- int ioaddr = dev->base_addr;
+ unsigned int ioaddr = dev->base_addr;
  status = readword(ioaddr, RX_FRAME_PORT);
  length = readword(ioaddr, RX_FRAME_PORT);
5.重新编译,重新启动,重新happy...
好了,现在可以挂NFS了,玩玩去...
...... ......
现在又有一不像问题的问题,网卡工作正常,但启动是提示申请 io 注册失败,request_region(0xf4000300, 0x10) failed 。
cs89x0:cs89x0_probe(0x0)
cs89x0.c: v2.4.3-pre1 Russell Nelson <nelson@crynwr.com>, Andrew Morton 
eth0: cs8900 rev K found at 0xf4000300
cs89x0: Extended EEPROM checksum bad and no Cirrus EEPROM, relying on command le
cs89x0 media RJ-45, IRQ 53, programmed I/O, MAC 09:90:99:09:90:99
cs89x0_probe1() successful
cs89x0:cs89x0_probe(0x0)
cs89x0: request_region(0xf4000300, 0x10) failed
cs89x0: no cs8900 or cs8920 detected.  Be sure to disable PnP with SETUP
更改虚地址也无效,追踪一下,在 kernel/resource.c 里出现问题。不知为什么资源冲突了,我看网上移植cs8900到2.4版也有这个问题...不理,先向下跳,回过头来在处理它...
443                 for (;;) {
444                         struct resource *conflict;
445 
446                         conflict = __request_resource(parent, res);
447                         if (!conflict)
448                                 break;
449 if (conflict != parent) {
450                                 parent = conflict;
451                                 if (!(conflict->flags & IORESOURCE_BUSY))
452                                         continue;
453                         }
454 
455                         /* Uhhuh, that didn't work out.. */
456                         kfree(res);
457                         res = NULL;
458                         break;
459                 }

把内存地址里面的
#define PWM_MR     __phys_to_pfn(PWMC_BASE + PWM_MR_OFF)

就可以编译通过了.内核水很深,大家多多讨论.


2015-05-29 15:49:19 qq_25687403 阅读数 2809

#include <asm/io.h>

u32 temp;



temp=__raw_readl(S5PV210_GPJ0_BASE);;
printk(KERN_ALERT "before READ_GPJ0CON=0x%x\n",temp);
temp=__raw_readl(S5PV210_GPH1_BASE+0x4);
printk(KERN_ALERT "before READ_GPJ0=0x%x\n",temp);
printk("request_irq error,i=%d\n",i);
2017-05-25 17:48:22 c1194758555 阅读数 565


1.内核空间内存的动态申请

linux内核空间中申请内存涉及的函数主要有kmlloc()、__get_free_pages()和vmalloc()等。kmalloc()和__get_free_pages()申请的内存位于常规内存区和DMA的映射区,并且在物理上是连续的,它们与真是的物理区只存在一个固定的偏移量,存在着较为简单的转换关系。而vmalloc()分配的内存位于虚拟内存空间的连续的内存区域,实际上连续的虚拟内存在物理内存上并不连续。vmalloc在进行内存分配时会改变页表项,而kmlloc用的是开机就项映射好的常规内存和DMA区域的页表项。

(1) kmlloc()

void *kmlloc(size_t size,int flags);//size分配内存的大小,flags分配标志用来控制kmlloc的行为。

分配标志有以下几种:

GFP_KERNEL(常用):在内核空间的进程中分配内存,再申请内存时如果暂时不能满足进程进入睡眠等待页,将会阻塞,因此该标志不能用于中断上下文或持有自旋锁的情况下。

GFP_ATOMIC:在申请内存时如果不存在空闲页,则立即返回,因此该标志可以用于中断上下文或持有自旋锁的情况下。

GFP_USER: 为用户空间分配内存,可能会引起阻塞。

GFP_HIGHUSER:类似GFP_USER但是其从高端内存分配。

GFP_DMA:从DMA区域分配内存。

GFP_NOIO:不允许任何I/O初始化。

GFP_NOFS:不允许任何文件系统调用。

使用kfree()释放kmalloc()分配的内存。

(2) __get_free_pages()

__get_free_pages()系类函数/宏是linux内核最底层获取空闲空间的方法,因为底层使用buddy算法一2^n页为单位管理空闲内存,所以最底层内存的申请总是以2^N页为单位。

__get_free_pages()系列函数/宏本包括get_zeroed_page()、__get_free_page()和__get_free_pages()。

get_zeroed_page(unsigned int flags);//返回一个指向一个已经清零的页的指针。

__get_free_page(unsigned int flags);//该宏返回一个新页的指针但是不清零。

__get_free_pages(unsigned int flags,unsigned int order);//该函数分配order页并返回内存的首地址。

释放内存的函数有:void free_pages(unsigned long addr),void free_page(unsigned long addr,unsigned long order)。

(3)vmalloc()

vmalloc()一般为只存在软件的较大顺序缓冲区分配内存,其在分配内存时需要建立新的页表项,代价较大,并且其不能用在中断上下文和持有自旋锁的进程中。

void vmalloc(unsigned long size);//分配内存

void vfree(void *addr);//释放内存

(4)slab

如果完全使用以页为单位的内存分配回收机制容易造成内存的浪费,还有就是操作系统在运行过程中经常会有大量的重复的对象的建立和销毁。如果有合适的方法使相同类型的对象在前后两次的使用同一块内存空间或同类的内存空间 并且保留了基本的数据,则可以大大的提高效率。而slab算法就是针对上述特点设计。

①创建slab缓存

struct kmem_cache *kmem_cache_craete(const char *name,size_t size,size_t align,unsigned long flags, \

void (*ctor)(void*,struct kmem_cache*,unsigned long), \

void (*dtor)(void*,struct kmem_cache*,unsigned long));

kmem_cache_craete创建一个任意数目的全部同样大小的后备缓存。size要分被的每个数据块的大小,flags控制如何进行分配。

② 分配salb缓存

void *kmem_cache_alloc(struct kmem_cache *cachep,gfp_t flags);//在①创建的slab缓存区中分配一块内存并返回首指针。

③ 释放salb缓存

void kmem_cache_free(struct kmem_cache *cachep,void *objp);//释放有②分配的内存。

④ 回收sable缓存

int kmem_cache_destory(struct kmem_cache *cachep);//释放有①创建的缓存区。

(5)内存池

内存池是一种典型的用于分配大量小对象的后备缓存技术。

① mempool_t结构体

typedef struct mempool_s {

        spinlock_t lock; /*保护内存池的自旋锁*/
        int min_nr; /*内存池中最少可分配的元素数目*/
        int curr_nr; /*尚余可分配的元素数目*/
        void **elements; /*指向元素池的指针*/
        void *pool_data; /*内存源,即池中元素真实的分配处*/
        mempool_alloc_t *alloc; /*分配元素的方法*/
        mempool_free_t *free; /*回收元素的方法*/
        wait_queue_head_t wait; /*被阻塞的等待队列*/
    } mempool_t;

②创建内存池

mempool_t *mempool_create(int min_nr,mempool_alloc_t *alloc_fn,mempool_free_t *free_fn,void *pool_data);

min_tr:需要分配对象的数目。

alloc_fn和free_fn分别指向内存池机制提供的标准提供的对象分配和回收函数的指针。

pool_data:是分配和回收函数用到的指针,gfp_mask是分配标志。

③分配和回收对象

void *mempool_alloc(mempool_t *pool,int pfg_mask);

void mempool_free(void *element,mempool_t *pool);

④回收内存池

void mempool_destory(mempool_t *pool);

2. I/O内存

(1)I/O内存的映射及读写
在内核访问I/O内存(通常是芯片内部的各个I^2C、spi、usb等控制器的寄存器或外部总线上的设备)之前,需要先将设备的物理地址映射到内核的虚拟地址上。
①映射到虚拟地址
void *ioremap(unsigned long offset,unsigned long size);//返回一个虚拟地址对虚拟地址的操作就是对设备物理地址的操作。
ioremap()有个devm_ioremap的变体,类似其他以devm_开头的函数,实用devm_ioremap函数映射通常在失败时不需要驱动退出和出错处理的时候使用iounmap。
其原型为:void __iomem *devm_ioremap(struct device *dev,resource_size_t offset,unsigned long size);
②解除映射
void iounmap(void *addr);
③ 读写设备内存。
一般完成映射后就可以直接使用指针对其进行读写,但是内核推荐使用readb_relaxed()、readb_relaxew()、readb_relaxel()、readb()、readw()、readl()和writeb_relaxed()、writew_relaxed()、writel_relaxed()、writeb()、writew()、writel()函数,对映射到虚拟地址的物理地址进行读写。没加_relaxed有内存屏障,b、w、l分别是读/写8位、16位、32位寄存器。
(2)I/O内存申请
①申请(只是告诉内核驱动要访问这片内存,实际什么也不做)
struct resource *request_mem_region(unsigned long satrt,unsigned long end,char *name);
②释放
void release_mem_region(unsigned long satrt,unsigned long end);

3.将设备地址映射到用户空间

(1) 内存映射与VMA
一般情况下,用户空间不能也不应该直接访问设备的寄存器或地址空间,但是可以在设备驱动程序中实现mmap()函数,此函数可以把设备寄存器或地址空间和用户空间相关联,实际就是一个映射过程,当用户访问用户空间时转换为对设备的访问。
内核空间mmap()的原型如下:
int (*mmap)(struct file *,struct vm_area_strucr *);
如果在驱动层实现了该函数,在应用层调用同名的mmap函数则可以完成地址映射。
(2) vma结构体

struct vm_area_struct

         struct mm_struct * vm_mm;       /*虚拟区间所在的地址空间*/

         unsigned long vm_start;         /*在vm_mm中的起始地址*/

         unsigned long vm_end;           /*在vm_mm中的结束地址 */

 

         /* linked list of VM areas per task, sorted by address */

         struct vm_area_struct *vm_next;

 

         pgprot_t vm_page_prot;          /*对这个虚拟区间的存取权限 */

         unsigned long vm_flags;         /*虚拟区间的标志. */

 

         rb_node_t vm_rb;

 

         /*

          * For areas with an address space and backing store,

          * one of the address_space->i_mmap{,shared} lists,

          * for shm areas, the list of attaches, otherwise unused.

          */

         struct vm_area_struct *vm_next_share;

         struct vm_area_struct **vm_pprev_share;

 

         /*对这个区间进行操作的函数 */

         struct vm_operations_struct * vm_ops;

 

         /* Information about our backing store: */

         unsigned long vm_pgoff;         /* Offset (within vm_file) in PAGE_SIZE

                                           units, *not* PAGE_CACHE_SIZE */

         struct file * vm_file;          /* File we map to (can be NULL). */

         unsigned long vm_raend;         /* XXX: put full readahead info here. */

         void * vm_private_data;         /* was vm_pte (shared mem) */

 };

(3) vma_operations_struct结构体
struct vm_operations_struct {
    void (*open)(struct vm_area_struct * area);
    void (*close)(struct vm_area_struct * area);
    int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);
 
    /* notification that a previously read-only page is about to become
     * writable, if an error is returned it will cause a SIGBUS */
    int (*page_mkwrite)(struct vm_area_struct *vma, struct page *page);
 
    /* called by access_process_vm when get_user_pages() fails, typically
     * for use by special VMAs that can switch between memory and hardware
     */
    int (*access)(struct vm_area_struct *vma, unsigned long addr,
              void *buf, int len, int write);
};
在用户调用mmap()时调用open(),在用户调用munmap()时调用close();

4.DMA

DMA是无需CPU参与就可以完成外设和内存间的双向数据交换。DMA可以使CPU在繁忙的I/O操作中解脱出来,极大的提高了系统的吞吐率。
(1) DMA与cache的一致性
cache是为了解决cpu与内存速度的不一致的问题,cache利用程序的局部性原理,把CPU经常访问或即将访问的数据存储到cache中。DMA提高了外设和内存间数据交换的效率,提高CPU的工作效率。如果DMA存入到内存的数据和Cache中存放的内存数据没有交集则没有任何问题。但是如果DMA存入内存的数据和Cache中存放的内存的数据有交集,那么通过DMA存入的新的数据,而Cache中的数据就与内存的数据不一致并且处理器依然访问cache的旧数据,这就是Cache与DMA不一致。
(2) linux下的DMA编程
①DMA区域
使用kmalloc()、__get_free_pages(),使用GFP_DMA标志可以申请到内存中DMA区域,并具有DMA的能力,也可以使用dma_mem_alloc()获取DMA区域。
现代的嵌入式处理器的DMA操作可以是整个的常规内存区域。
②虚拟地、物理地址和总线地址
基于DMA的硬件使用的是总线地址而不是物理地址,总线地址是从设备角度看到的内存地址,物理地址是从CPU(虚拟地址)和MMU看到的内存地址。
虚拟地址和物理地址之间的转换:
unsigned long virt_to_bus(volatile void *address); //虚拟地址到总线地址
void *bus_to_virt(unsigned long address);//总线地址到物理地址的转换
③DMA掩码
int dma_set_mask(struct device *dev,u64 mask);//其本质是修改device中的dma_mask成员。mask就是DMA能访问的内存范围。
④一致DMA缓冲区
DMA的映射包含两方面的工作:分配一片DMA缓冲区,为这片缓冲区产生设备可以访问的地址,DMA映射必须考虑cache一致性问题。
void *dma_alloc_coherenet(struct device *dev,size_t size,dma_addr_t *handle,gfp_t gfp);//返回DMA缓冲区的虚拟地址,handle为带回的总线地址。
void dma_free_coherenet(struct device *dev,size_t size,dma_addr_t *handle,gfp_t gfp); //释放DMA缓冲区
dma_alloc_xxx函数分配的不一定是DMA区。
⑤流式DMA映射


⑥dmaengine标准API

 



2012-01-05 18:25:08 jdh99 阅读数 3105

Linux驱动:内核延时测试


本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明.


环境:

主机:Fedora12

目标板:MINI6410

目标板LINUX内核版本:2.6.38


实现功能:

延迟2S


方法1:利用系统全局变量jiffies

jiffies记录系统节拍,每一次节拍,内核时钟中断函数会将jiffies加1.

HZ在ARM中为100,表示1S被分为100份,系统每个节拍为10ms.

修改上篇《linux驱动编写:LED驱动测试》中的ioctl函数,测试延时

//功能:ioctl操作函数
//返回值:成功返回0
static long led_driver_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	unsigned int temp = 0;
	unsigned long t = 0;
	
	temp = readl(S3C64XX_GPKDAT);
	if (cmd == 0)
	{
		temp &= ~(1 << (arg + 3));
	}
	else
	{
		temp |= 1 << (arg + 3);
	}

	//等待2S
	t = jiffies;
	while (time_after(jiffies,t + 2 * HZ) != 1);
	writel(temp,S3C64XX_GPKDAT);

	printk (DEVICE_NAME"\tjdh:led_driver cmd=%d arg=%d jiffies = %d\n",cmd,arg,jiffies);
	
	return 0;
}
这个方法会使内核忙等待,会影响系统效率.


2.利用宏ndelay(n),延时ns

udelay(n),延时us

mdelay(n),延时ms

这个简单,不做介绍


3.利用内核进行延时

long sleep_on_timeout(wait_queue_head 8q,long time_out);

long interruptible_sleep_on_timeout(wait_queue_head 8q,long time_out);

第2个与第1个相比差别在于会被中断唤醒。调用这两个函数,内核会阻塞当前进程,将其放入等待队列,等待时间到。

代码:

//功能:ioctl操作函数
//返回值:成功返回0
static long led_driver_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	unsigned int temp = 0;
	//unsigned long t = 0;
	wait_queue_head_t wait;
	
	temp = readl(S3C64XX_GPKDAT);
	if (cmd == 0)
	{
		temp &= ~(1 << (arg + 3));
	}
	else
	{
		temp |= 1 << (arg + 3);
	}

	//等待2S
	//t = jiffies;
	//while (time_after(jiffies,t + 2 * HZ) != 1);
	init_waitqueue_head(&wait);
	sleep_on_timeout(&wait,2 * HZ);
	writel(temp,S3C64XX_GPKDAT);

	printk (DEVICE_NAME"\tjdh:led_driver cmd=%d arg=%d jiffies = %d\n",cmd,arg,jiffies);
	
	return 0;
}

linux 驱动笔记(六)

阅读数 897

没有更多推荐了,返回首页