嵌入式中一些问题

2020年12月5日 | 由 康华 | 2900字 | 阅读大约需要6分钟 | 归档于 linux杂谈 |

嵌入式中一些问题

​ ―― 康华

小论嵌入设备开发中可能用到内存映射方法

本文将以我前期开发的项目为蓝本,就开发中遇到的有关内存映射的问题进行一些总结。希望能帮助新接触Linux内存映射的朋友梳理一下思路。

首先我大致描述一下我们所设计的板卡特性。我们使用的板卡之一是MPC8560,它是一个POWERPC系列的双核(E500 和 CPM)板卡,我们用它来做通讯系统。该板卡的物理地址空间布局如下所示:

Processor Memory Map of MPC8560

0000_0000 7FFF_FFFF <2G System Memory (onboard DRAM)

8000_0000 A000_0000 512M PCI Memory Space

A000_0000 A100_0000 16M PCI I/O Space

E000_0000 E400_0000 64M Solider FLASH

E800 0000 E800 1000 4k Framer register

E800 1000 E810 0000 512K CPLD register

E880 0000 E900 0000 512K Socket

FDF0 0000 FE00 0000 1M Mpc8560 Register

FFF0 0000 FFFF FFFF 1M Book Bank

从上图可以看到,系统物理物理范围不得大于2G,默认空间从0000_0000开始;系统的PCI内存空间大小为512M,默认空间从8000_0000到A000_0000;系统另外还有Framer register,CPLD register和Mpc8560 Register三块寄存器空间(其它空间我们这里不用考虑)。 实际上我们板卡的物理内存为512M。

我们的应用对系统有如下要求(为了能更集中说明问题,所列需求和实际系统配置有所出入):

1. 系统需要有保留208M的PCI内存空间用于导入其它板卡的内存映射(因为要导入13块其它板卡的各16M内存,也就是它需要能访问到其它13个板卡)。

2. 系统本身需要留出16M的连续内存用于导出到其它系统中。

3. 为了和已有系统兼容,新系统需要留出虚拟地址C8000000-C8800000存储配置信息,我们这里称为CDB(configure database)。CBD需要能被应用进程访问。

4. 系统要能操作各种寄存器。

好了,在这些束缚下我们该如何布置我们的内存映射呢?首先我们知道Linux系统中默认情况下内核空间占据高1G(POWERPC中有时占据高2G)的逻辑空间,用户空间占据剩下的逻辑空间(0000_00000到C000_0000 或D000_0000)。而上面第2点需要却要求我们必须预留出空间C8000000-C8800000给用户程序访问,因此最直接有效的方法是,我们需要将内核空间上移动到D0000000处(POWERPC体系映射采用段寄存器,系统共分16个段,每段映射256M,因此映射边界只能依10000000跨度划分)。

然而新问题又出现了。当内核空间缩小到512M后,我们发现要想满足第1个条件,既留出208M的空间,以及剩下的几个小块寄存器地址空间,那么就无法将512M的物理内存全部映射到内核空间中。怎么办了? 解决办法就是将一半物理内存(256M)作为高端内存(而传统上系统只把高于896M以上的内存作为高端内存的),避免启动时便静态映射于内核逻辑空间。这是因为高端内存只能被动态分配且现场映射,不会启动时不经分配就直接映射到内核空间。不过这并不是说我们系统一半的内存不可用,因为系统在需要时仍然可以指明在高端内存分配可用内存,然后建立内存映射,从而使用它(如:_ _get_free_pages(GFP_HIGHMEM,X)``,``kmap( )``)

注释:修改内核地址空间和高端内存空间需要修改**TASK_SIZE, KERNEL_START, LOWMEM_SIZE等值。

另外,我们为了满足第2个条件,即将16M连续物理内存空间保留下来导出。若要分配连续物理内存空间尽可以使用kmalloc来分配,不过现在的内核还无法分配大到16M的连续空间,因此我们需要采用预留内存的方法——留出一段内存不让Linux内核看到(也就是说内核不管理这段区间,不过可要搞清楚,这段内存和高端内存不同,它并未处于高端内存),具体做法是在启动时

通过启动参数“mem=240M”来通知内核其只管理240M的物理内存。然后在使用前再将这部分内存映射到内核空间(如,ioremap (0xF000000 /* 240M */, 0x1000000 /* 16M */);

​ 最后为了能在内核空间访问Framer register、CPLD register和Mpc8560 Register寄存器 我们必须也将其映射到内核空间,方法很简单就是用下面语句:

request_mem_region(CPLD_ADDRESS, CPLD_SIZE, “CPLD”);

CPLD_MAPPED_ADDR = (UBYTE *)ioremap(CPLD_ADDRESS,CPLD_SIZE);

记住上面所的要求和解释,我们下面给出系统的物理内存布局和虚拟地址空间布局。

***MPC8560****物理内存布局*

Kernel (include drivers) 00000000~000480000 <4M 粗略估计值

Initrd Image 00480000~00780000 <3M 粗略估计值

——————————————– 0x00780000

可给应用程序动态分配的物理内存

――――――――――――――――――――――

Pci buffer 0F000000-10000000 16M

——————————————– 0x10000000 256M 以上为高端内存

只能现场分配且映射才可被使用

——————————————– 0x20000000 512M

Linux系统运行后,其虚拟地址空间布局如下:

————————————————– 0x00000000

Application Space

Libraries map Space

—————————————————0xC8000000

CDB Map Space 0xC8000000-0xC8800000 8M

Stack 0xCE000000~0xCFFFFFFF 16M

—————————————————0xD0000000 Kernel Space Begin

Physical memory maps(Kernel logical space) 256M

—————————————————0xE0000000

Kernel virtual space 256M

—————————————————0xFFFFFFFF

注意,上图中的内核虚拟空间(非对应连续物理地址的内核空间,详解请看内核驱动开发一书)存在打散的部分,这些部分中会容纳我们的208M的PCI内存空间 ,16M**的导出地址空间,以及各种寄存器空间等等*。但具体的映射地址则由内核在运行时确定。*

上面的例子初读可能并非显而易见,不明白的地方请多参考资料下载中给出的内核教材,并结合内核源代码进行分析。搞清各种内存映射关系是开发嵌入系统必不可少的功课,希望本文能帮助大家学习。

小谈驱动开发中的时钟调试

我想,调试过驱动的朋友多多少少都接触过时钟。因为时钟对设备来说,就如同心脏对人体供血一样,是不可或缺的。有些设备自己有时钟,有些接收系统外部时钟,总之时钟源是设备工作的基石。

因此检测时钟是否正工作是调试设备的关键之一,本文我将给出大家一个有效的时钟调试手段——不需要用试波器,而用软件方法。

我先说说我碰到的时钟问题,再来看如何调试时钟。

问题发生在开发mcc(多通道控制器)驱动时——在我们测试驱动时,发现无论如何配置mcc参数和触发设备,设备都自岿然不动,好似僵死一般。因此估计时钟为接收到。 经过调查,mcc所使用的时钟源mt9045没有设置对。 这个时钟源将给mcc输入传输、接收时钟,同时还有同步时钟。

由于当时我们缺乏足够的资料配置时钟,而且这个时钟的确比较复杂,所以我们忙了好一阵都没搞通它。 因此我们急需检测时钟输入的正确与否。如果用试波器吧,还阵不知道如何接出硬件管脚(设备封装得很好),最后我们借助一段软件程序来检测端口时钟频率。

 

static

int frequency_count(volatile unsigned long addr, unsigned int mask )

{

  int t, nt, et;

  int v, lv, cnt;

 

  t = Gettick();

  while ( 1 )

  {

    nt = Gettick();

    if ( nt != t )

      break;

  }

  et = nt + GetsysClkRate ();

  cnt = 0;

  lv = v = (*(int*)addr) & mask;

  while ( 1 )

  {
   nt = Gettick();

    if ( nt >= et )

      break;

    v = (*(int*)addr) & mask;

    if ( v != lv )

    {

      lv = v;

      cnt++;

    }

  }

  return cnt/2;

}

上面程序的计算值是近似的――当系统cpu频率约快时,准确性约搞――其中机理留给大家去领会。

函数的用法很简单。当检测某个时钟输出,即特定地址线上的某个针(软件角度就一个位)是否产生何时的频率时,只需要给frequency_count 传入地址地址和输出针的掩码,如地址0xfdf90d50的第0位,则传入0xfdf90d50,0x00000001。 函数输出就时频率值。

要注意其中有两个辅助函数。一个是Gettick ,它用于获取系统当前节拍值;一个是GetsysClkRate用于获得系统节拍频率。

在Linux系统中上述函数可如下实现。

 

static int Gettick()

{

  return (int)jiffies;

}

 

static int GetsysClkRate ()

{

  return (int)HZ;

}

   vxWorks系统上述函数可实现如下

static int Gettick()

{

  return tickGet();

}

 

static int GetsysClkRate ()

{

  return sysClkRateGet();

}