星期四, 十二月 06, 2007

Blogger字距與行距調整

不論是Blogger或Wordpress,預設的字體都是以英文為最佳化的,英文相對中文來說,適合閱讀的字體比較小,字距也比較小,雖然新版Blogger提供一些調整字體的UI,不過功能還是陽春了一點。
在一些預設的Blogger範本中要調整本文的字體大小、字距與行距可以編輯.post.post div這類的CSS欄位,加入以下三行進行微調:

font-size: 115%;
letter-spacing: 1.4px;
line-height: 1.3em;

Read More...

Gentoo 与 Ubuntu 下的字体调整 (12.06 更新)『转』

字体美化,其实我自己看着这两字我都麻木了,还是这老掉牙的东西,不过现在对 Linux 下字体的调整总算是入门了。

从一开始的工具 链、到后来的内核、X Server 、桌面环境、应用软件基本上搞定后,花了一天时间来仔细研究关于 Linux 下的字体调整,总算是把 Gentoo 下搞得漂漂亮亮的了,现在已经基本上开始取代 Ubuntu 成为主要应用的操作系统了。不过 Ubuntu 也坐了一把顺风车,嘿嘿。

一直以来,所做的几次美化,都没有关注 dpi (Dot Per Inch) 这个设置,通常把它设置为 96dpi ( Windows 上的默认设置 ),以前也知道这个设置对于字体的调整有很大关系,但是都没有深入,这次恶补了几下。

现在写日志越来越懒了,还是照旧吧,写重点,要了解具体的选项及作用,看文章结尾的相关链接吧。

1. 设置正确的 DPI (Dot Per Inch)
在 Linux 里,有很多地方都可以设置 DPI ,而且不同地方设置的 DPI 分别管理不同的地方(开始晕了)。不过,我可不管它哪里是管哪的,我只知道,我需要全部都使用正确的 DPI 不就行了,所以一个个的查看一下,再一个一个的改吧。

比较完整的各种显示器的 DPI 设置可以查看这里:
Notebook LCD Display Comparison
Desktop LCD Display Comparison

以我的为例讲解,14.1“ 的显示器,分辨率为 1280 x 800 ,可以查出正确的 DPI 应该是107.1

引用
Size Resolution Density Pixel Size Ratio Pixels Gain
14.1" 1280 x 800 107.1 ppi 0.2373 mm 16:10 0.98 MP 130%

A. 首先来查看一下 X Server 使用的 DPI ,一看,完全正确, Ubuntu 拷贝过来的 Xorg.conf 设置得还是八错的。
引用
# xdpyinfo | grep resolution
resolution: 107x107 dots per inch

假如这个地方不正确,可以通过修改 Xorg.conf 来解决,方法如下:
* 非常老实的拿一块尺子,把你的显示器长宽量下来,单位为毫米,例如我的 303×190,把它加到 /etc/X11/xorg.conf
引用
# vim /etc/X11/xorg.conf 加入
Section "Monitor"
......
DisplaySize 303 190 # 1280x800 107dpi
......
EndSection

* 什么,你真的拿尺子去了? 我败给你了,如果仔细的话,在上面 Notebook LCD Display Comparison 里是可以查到的。
引用
Size Diagonal Width Height Ratio Area Gain
14.1" 35.81 cm 30.37 cm 18.98 cm 16:10 576 cm2 127%

* 记得修改好了重启一下 X ,然后执行下面的命令,看看是否生效
引用
# xdpyinfo | grep dimensions
dimensions: 1280x800 pixels (303x190 millimeters)
# xdpyinfo | grep resolution
resolution: 107x107 dots per inch


B. fontconfig/xft 也有一个 DPI 的设置,如果大家都做过美化的话,对 language-selector.conf / local.conf 等配置文件,再熟悉不过了,嗯,这个 DPI 就在这里设置。不过别问我这个设置对哪些地方有效,因为我…………也不清楚。
这个的设置非常简单,在配置文件里面加上下面这么一段,就可以了。
引用
# vim /etc/fonts/local.conf 或 vim ~/.fonts.conf 加入
<!--
dots per inch
-->
<match target="pattern">
<edit name="dpi" mode="assign" >
<double>107</double>
</edit>
</match>

什么,你已经加入了? 其实,这个设置没多大用的,为什么?请看C

C. dpi in gnome/xfce
为什么说上面 B 所说的没多大用,是因为,现在的桌面环境都比较 BT 了,都提供接口来对 fontconfig/xft 程序设置 dpi ,就算你配置文件写好了,这里一改,配置文件还是瞎搞,所以还是图形化修改吧。
gnome 的设置在 Settings - Fonts 里
xfce 的设置在 Settings - User Interface Settings 里,自己填吧。
在 gnome 下,立即就可以看到效果,在 xfce 里,需要重启一下 X 。
查看 Xft.dpi 值的方法:
引用
# xrdb -query | grep Xft
......
Xft.antialias: 1
Xft.dpi: 107.00000
Xft.hinting: 1
Xft.hintstyle: hintfull
Xft.rgba: rgb

顺便对对用 KDE 的兄弟说声抱歉了,没环境。

2. 配置文件
偷懒开始了,粘贴了事。
配置文件写得非常简单,去掉了一切可以去掉的东西,反正我的环境特简单,中文用 Microsoft YaHei (咳咳,什么什么?),英文用 Redhat 提供的开源字体 Liberation 。
在 Ubuntu 里,把这个保存到 /usr/share/language-selector/fontconfig/zh_CN
在 Gentoo 里,可以保存到 /etc/font/local.conf
当然你也可以保存到 ~/.font.conf ,不过这样就只对本用户有效而已。
---------------------- local.conf 12.06 更新 -----------------------------
引用
<fontconfig>
<!--
# 设置 serif 衬线字体的读取顺序
-->
<alias>
<family>serif</family>
<prefer>
<family>Liberation Serif</family>
<family>Microsoft YaHei</family>
<family>SimSun</family>
</prefer>
</alias>
<!--
# 设置 sans-serif 无衬线字体的读取顺序
-->
<alias>
<family>sans-serif</family>
<prefer>
<family>Liberation Sans</family>
<family>Microsoft YaHei</family>
<family>Simsun</family>
</prefer>
</alias>
<!--
# 设置 monospace 等宽字体的读取顺序
-->
<alias>
<family>monospace</family>
<prefer>
<family>Liberation Mono</family>
<family>Microsoft YaHei</family>
<family>Simsun</family>
</prefer>
</alias>
<!--
# 全局字体的设置
-->
<match target="font">
<test name="family" compare="contains">
<string>Tahoma</string>
<string>verdana</string>
<string>Arial</string>
<string>Times New Roman</string>
</test>
<edit name="globaladvance">
<bool>false</bool>
</edit>
<edit name="spacing">
<int>0</int>
</edit>
<edit name="hinting" mode="assign">
<bool>true</bool>
</edit>
<edit name="autohint" mode="assign">
<bool>false</bool>
</edit>
<edit name="antialias" mode="assign">
<bool>true</bool>
</edit>
<edit name="hintstyle" mode="assign" >
<const>hintfull</const>
</edit>
</match>

<!--
# 单独设置微软雅黑字体的显示
-->
<match target="font">
<test name="family" compare="contains">
<string>Microsoft YaHei</string>
<string>微软雅黑</string>
</test>
<edit name="hinting" mode="assign">
<bool>true</bool>
</edit>
<edit name="autohint" mode="assign">
<bool>false</bool>
</edit>
<edit name="antialias" mode="assign">
<bool>true</bool>
</edit>
<edit name="hintstyle" mode="assign" >
<const>hintfull</const>
</edit>
<edit name="rgba" mode="assign">
<const>gba</const>
</edit>
</match>

<!--
# 单独设置 Liberation Sans 字体的显示
-->
<match target="font">
<test name="family" compare="contains">
<string>Liberation Sans</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
<edit name="autohint" mode="assign">
<bool>false</bool>
</edit>
<edit name="antialias" mode="assign">
<bool>true</bool>
</edit>
<edit name="hintstyle" mode="assign" >
<const>hintfull</const>
</edit>
<edit name="rgba" mode="assign">
<const>none</const>
</edit>
</match>

<!--
# 单独设置 Liberation Mono 字体的显示
-->
<match target="font">
<test name="family" compare="contains">
<string>Liberation Mono</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
<edit name="autohint" mode="assign">
<bool>false</bool>
</edit>
<edit name="antialias" mode="assign">
<bool>true</bool>
</edit>
<edit name="hintstyle" mode="assign" >
<const>hintfull</const>
</edit>
<edit name="rgba" mode="assign">
<const>none</const>
</edit>
</match>
</fontconfig>

---------------------- local.conf 12.06 更新 -----------------------------
3. 系统设置。
gnome : Fonts 里,把 AA、 hinting (Full) 、 Sub-pixel hinting (RGB) 选上
xfce: User Interface Settings 里面把 AA、 hinting (Full) 、 Sub-pixel hinting (RGB) 全部选上

4. 后续
现在你会发现系统基本上已经显示得很完美了,除了 firefox ,firefox 有自己的一套管理方式,会跟你作对的。
For Ubuntu:
用 Ubuntu 可以用下面的方法关闭 firefox 默认使用的 Pango ,从而使用系统里的 fontconfig/xft 来控制
引用
# vi /etc/firefox/firefoxrc
MOZ_DISABLE_PANGO=1

---------------------- gentoo 下 firefox 安装 12.06 更新 -----------------------------
For Gentoo:
在 Gentoo 的 mozilla-firefox 包里,有一个 moznopango 的 USE ,加上这个 USE ,可以关闭 pango 的使用。
# echo " www-client/mozilla-firefox moznopango" >> /etc/portage/package.use
# emerge -av mozilla-firefox
---------------------- gentoo 下 firefox 安装12.06 更新 -----------------------------

如果你在 Gentoo 里,你会发现明显还是不如在 Ubuntu 下漂亮,为什么呢?
那是因为 Gentoo 官方源里的 Cairo 、libXft 两个基础库都不带次像素渲染,这个对字体的显示有着重要影响,所以我们必须将 Cairo 、libXft 两个基础库利用 overlay 进行升级。
# emerge -av layman
# layman -L
# layman -f -a xeffects # 需要 svn 支持
# echo 'source /usr/portage/local/layman/make.conf' >> /etc/make.conf
# env-update && source /etc/profile
# euse -E newspr # 关键就在 newspr 这个 flag,官方源里的 Cairo 、 libXft 是没有这个可选的 Flag , 所以需要 overlay 里的来进行升级
# emerge -av1 cairo libXft

重启 X 后,再看看,雅黑字体不模糊过头了吧,清晰了吧,不会出现彩色光蕴的问题了吧。

OK,顺便上个图看看吧,Gentoo Xfce4 下。
点击在新窗口中浏览此图片


相关链接:
Linux 字体微调 - windows 效果版
字体设置 (Arch Wiki)
http://www.linuxsir.org/bbs/showthread.php?t=317779
SubpixelHinting
这个地方可以测试一下微软的字体
http://www.microsoft.com/typography/web/fonts/default.htm

----------------------12.06 更新 -----------------------------
这次的更新,使英文字体显示更为完美,中文的雅黑无变动。
----------------------12.06 更新 -----------------------------

Read More...

星期一, 十二月 03, 2007

一个Linux下的char型设备的驱动

        这个驱动程序是典型char型设备驱动,它涉及到了如何使用共享中断、相同功能的设备共用驱动程序等一些技巧。个人的愚见,欢迎来讨论。
/****************************************************************************
    直放站 UART3 driver
    shanghai xinmin telecom std.
    diming.feng 2007-05
*****************************************************************************/
#include <linux/init.h>
#include <linux/slab.h>
#include <asm/errno.h>
#include <asm/delay.h>
//#include <asm/fcntl.h>
#include <asm/arch/irqs.h>
#include <linux/mm.h>
#include <linux/poll.h>
#include <linux/module.h>
#include <linux/serial_reg.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include "expuart.h"

#define GPIO_CFG0  VPint(0xFFF83000)
#define GPIO_DIR0  VPint(0xFFF83004)
#define GPIO_DATAOUT0 VPint(0xFFF83008)

#define GPIO_CFG5  VPint(0xFFF83050) 
#define GPIO_DIR5  VPint(0xFFF83054)
#define GPIO_DATAOUT5 VPint(0xFFF83058)

#define GPIO_XICFG  VPint(0xFFF83074)
#define GPIO_XISTATUS VPint(0xFFF83078)

#define UART_TX   VPchar(COM_TX_3)
#define UART_RX   VPchar(COM_RX_3)
#define UART_DLL  VPchar(COM_DLL_3)
#define UART_DLM  VPchar(COM_DLM_3) 
#define UART_IER  VPchar(COM_IER_3)
#define UART_IIR  VPchar(COM_IIR_3)
#define UART_FCR  VPchar(COM_FCR_3)
#define UART_LCR  VPchar(COM_LCR_3) 
#define UART_MCR  VPchar(COM_MCR_3) 
#define UART_LSR  VPchar(COM_LSR_3) 
#define UART_MSR  VPchar(COM_MSR_3) 
#define UART_TOR  VPchar(COM_TOR_3)

#define LSR_TE   0x40
#define LSR_THRE  0x20 
#define LSR_RFDR  0x01

#define nIRQ2   31
#define uart3_IRQ  12

#define NOIRQ   0x01
#define RSTIRQ   0x06 
#define RFVIRQ   0x04
#define RFTIRQ   0x0C
#define THRIRQ   0x02
#define MSTIRQ   0x00

#define RX_FIFO_LEVEL 14
#define UART_FCR_LEVEL  0xc0


#define CMD_BANDRATE 0   // set bandrate
#define CMD_UARTLCR  1   // set data bit
#define CMD_RXTIMEOUT 2
#define CMD_MODE  3   // set RS-485/RS-232 mode
#define CMD_MODEM_SET 4   // set internal modem

#define MAJOR_NUM  248
#define YES    1
#define NO    0
#define TIME_OUT  20000

#define uart3_int(band)   do{     \
         UART_LCR = 0; \
         UART_IER = 0; \
         UART_LCR = DLAB;\
         UART_DLL = band;\
         UART_DLM = 0; \
         UART_LCR = 0x03;\
         UART_FCR = 0x07;\ 
         UART_TOR = 0x00;\
        }while(0)

#define write_reg(reg,dat)  do{      \
         UART_TX = 0x80+reg; \
         while((UART_LSR&LSR_TE)!=LSR_TE); \
         UART_TX = dat; \
         while((UART_LSR&LSR_TE)!=LSR_TE); \
        }while(0)
        
#define read_reg(reg,dat)  do{      \
         UART_TX = reg; \
         while((UART_LSR & LSR_RFDR)!=LSR_RFDR); \
         dat = UART_RX; \
        }while(0)

static int uart3tx_busy;
static uart3_dev exp_uart[EXPUART_NUM]={
 {248, "uart3", 0},
 {247, "uart4", 1},
 {246, "uart5", 2},   // internal mode
 {245, "uart6", 3},
};

DECLARE_WAIT_QUEUE_HEAD(expuart1_queue_Rd);
DECLARE_WAIT_QUEUE_HEAD(expuart1_queue_Wr);

DECLARE_WAIT_QUEUE_HEAD(expuart2_queue_Rd);
DECLARE_WAIT_QUEUE_HEAD(expuart2_queue_Wr);

DECLARE_WAIT_QUEUE_HEAD(expuart3_queue_Rd);
DECLARE_WAIT_QUEUE_HEAD(expuart3_queue_Wr);

DECLARE_WAIT_QUEUE_HEAD(expuart4_queue_Rd);
DECLARE_WAIT_QUEUE_HEAD(expuart4_queue_Wr);

static void ms_delay(int ms)
{
 while(ms--){
  udelay(1000);
 }
}

/*
static void write_reg(char reg_addr, char reg_dat)
{
 char temp;
 
 UART_TX = 0x80+reg_addr;
 do{
  temp = UART_LSR;
 }while((temp&LSR_TE)!=LSR_TE);
 UART_TX = reg_dat;
 do{
  temp = UART_LSR;
 }while((temp&LSR_TE)!=LSR_TE);
}
*/

static int uart3_chkbuf(int write, int read, int reqnum)
{
 int temp = write-read;
 if(temp >= 0){
  if(temp >= reqnum){
   return 1;
  }
 }
 else{
  if((temp + RX_BUFF_SIZE)>= reqnum){
   return 1;
  }
 }
 return 0;
}

static void expuart_handler(int irq, void *dev_id, struct pt_regs * regs)
{
 char sub_sifr, int_no, sub_ssr;
 int  expnum;
 int  ctn;
 
 GPIO_XISTATUS = 0;
 uart3_dev *dev = (uart3_dev *)dev_id;
 expnum = dev->no;
 
 /* disable VK3214 all interrupt */
 write_reg(gIR,0x00);
 
 read_reg(gIR,int_no);   // read current interrupt uart
 int_no = int_no & 0x0f;
 if(1<<expnum != int_no){
  goto out;
 }

/* read sub uart of VK3214 interrupt flags */
 read_reg(SSR(expnum),sub_ssr);
 
 // send data to uart
 if((dev->usr_wr_num>0)&&((sub_ssr&4)==4)){
  ctn = dev->usr_wr_num - dev->tx_ctn;
  if(ctn >15){
   ctn = 15;
  }
  UART_TX = 0xc0+(expnum<<4)+(ctn-1);
  while(ctn--){
   UART_TX = dev->txbuf[dev->tx_ctn++];
  }
  while((UART_LSR & LSR_TE)!=LSR_TE);
  do{
   UART_TX = SSR(expnum);
   while((UART_LSR & LSR_RFDR)!=LSR_RFDR);
  }while((UART_RX&4)!=4);
  if(dev->tx_ctn == dev->usr_wr_num){
   write_reg(SIER(expnum),0x01);   // tx interrupt disable, rx interrupt enable
   dev->usr_wr_num = 0;
   dev->tran_flag = 1;
   uart3tx_busy = NO;
   if(exp_uart[0].wait_flag == 1){
   // printk("wake 0\n");
    exp_uart[0].wait_flag = 0;
    wake_up_interruptible(&expuart1_queue_Wr); //
   }
   if(exp_uart[1].wait_flag == 1){
   // printk("wake 1\n");
    exp_uart[1].wait_flag = 0;
    wake_up_interruptible(&expuart2_queue_Wr); //
   }
   if(exp_uart[2].wait_flag == 1){
   // printk("wake 2\n");
    exp_uart[2].wait_flag = 0;
    wake_up_interruptible(&expuart3_queue_Wr); //
   }
   if(exp_uart[3].wait_flag == 1){
   // printk("wake 3\n");
    exp_uart[3].wait_flag = 0;
    wake_up_interruptible(&expuart4_queue_Wr); //
   }
  }
  goto out;  
 }
 
 // receive data from uart
 if((sub_ssr&1) == 1){
  goto out;
 } 
 read_reg(SFSR(expnum), ctn);
 ctn = UART_RX & 0x0f;
 while(ctn--){
  read_reg(SFDR(expnum), dev->rxbuf[dev->Recv_wr_index++]);
  if(dev->Recv_wr_index == RX_BUFF_SIZE){
   dev->Recv_wr_index = 0;
  }
 }
 dev->recv_flag = uart3_chkbuf(dev->Recv_wr_index,dev->Recv_rd_index,dev->usr_rd_num);
 if(dev->recv_flag){
  switch(expnum){
   case 0:
    wake_up_interruptible(&expuart1_queue_Rd); //
    break;
   case 1:
    wake_up_interruptible(&expuart2_queue_Rd); //
    break;
   case 2:
    wake_up_interruptible(&expuart3_queue_Rd); //
    break;
   case 3:
    wake_up_interruptible(&expuart4_queue_Rd); //
    break;
   default:
    break;
  }
 }
out:
 write_reg(gIR,0xf0);
}
                         
ssize_t uart3_read(struct file *flip, char *buf, size_t count, loff_t *offset)
{     
 int i;        
 int expnum;
 wait_queue_t wait;
 uart3_dev *dev = (uart3_dev *)flip->private_data;
         
 expnum = dev->no;
 dev->usr_rd_num = count;
 dev->recv_flag = uart3_chkbuf(dev->Recv_wr_index,dev->Recv_rd_index,dev->usr_rd_num);
 if(dev->recv_flag==0){
  init_waitqueue_entry(&wait, current); //
  switch(expnum){
   case 0:
    add_wait_queue(&expuart1_queue_Rd, &wait); //
    break;
   case 1:
    add_wait_queue(&expuart2_queue_Rd, &wait); //
    break;
   case 2:
    add_wait_queue(&expuart3_queue_Rd, &wait); //
    break;
   case 3:
    add_wait_queue(&expuart4_queue_Rd, &wait); //
    break;
   default:
    break;
  }  
  set_current_state(TASK_INTERRUPTIBLE);
  schedule_timeout(dev->time_out*HZ); 
  set_current_state(TASK_RUNNING);
  switch(expnum){
   case 0:
    remove_wait_queue(&expuart1_queue_Rd, &wait); //
    break;
   case 1:
    remove_wait_queue(&expuart2_queue_Rd, &wait); //
    break;
   case 2:
    remove_wait_queue(&expuart3_queue_Rd, &wait); //
    break;
   case 3:
    remove_wait_queue(&expuart4_queue_Rd, &wait); //
    break;
   default:
    break;
  }  
 }
 count = dev->Recv_wr_index - dev->Recv_rd_index;
 if(count<0){
  count += RX_BUFF_SIZE;
 }
 if(count > dev->usr_rd_num){
  count = dev->usr_rd_num; 
 }
 dev->usr_rd_num = 0;
 for(i=0; i<count; i++){
  buf[i] = dev->rxbuf[dev->Recv_rd_index];
  dev->Recv_rd_index += 1;
  if(dev->Recv_rd_index == RX_BUFF_SIZE){
   dev->Recv_rd_index = 0;
  }
 }
 return count;
}

static int uart3_write(struct file *flip, int *buf, size_t count, loff_t *offset)
{
 int expnum;
 wait_queue_t wait;
 uart3_dev *dev = (uart3_dev *)flip->private_data;
 
 if(count==0){
  return 0;
 }
 dev->tx_ctn = 0;
 expnum = dev->no;
 if((dev->tran_flag==1)&&(uart3tx_busy==NO)){
  goto copy_data;
 }
// printk("wait %d\n",expnum);
 dev->wait_flag = 1;
 init_waitqueue_entry(&wait, current); //
 switch(expnum){
  case 0:
   add_wait_queue(&expuart1_queue_Wr, &wait); //
   break;
  case 1:
   add_wait_queue(&expuart2_queue_Wr, &wait); //
   break;
  case 2:
   add_wait_queue(&expuart3_queue_Wr, &wait); //
   break;
  case 3:
   add_wait_queue(&expuart4_queue_Wr, &wait); //
   break;
  default:
   break;
 } 
 set_current_state(TASK_INTERRUPTIBLE);   
 schedule();
 set_current_state(TASK_RUNNING);
 switch(expnum){
  case 0:
   remove_wait_queue(&expuart1_queue_Wr, &wait); //
   break;
  case 1:
   remove_wait_queue(&expuart2_queue_Wr, &wait); //
   break;
  case 2:
   remove_wait_queue(&expuart3_queue_Wr, &wait); //
   break;
  case 3:
   remove_wait_queue(&expuart4_queue_Wr, &wait); //
   break;
  default:
   break;
 }
copy_data:
 if(count > TX_BUFF_SIZE){
  count = TX_BUFF_SIZE;
 }
 copy_to_user(dev->txbuf, buf, count);
 dev->tran_flag = 0;
 dev->usr_wr_num = count;
 uart3tx_busy = YES;
 write_reg(SIER(expnum),0x03);   // rx and tx interrupt enable
 return count;
}

static int uart3_open(struct inode *inode,struct file* flip)
{
 int result;
 int expnum;
 uart3_dev *dev;
 
 expnum = 248 - MAJOR(inode->i_rdev);
 dev =(uart3_dev *)(&exp_uart[expnum]);
 result = request_irq(nIRQ2, expuart_handler, SA_SHIRQ, dev->dev_name, dev);
 if(result == -1){
  printk("register the expand uart irq failed!\n");
  return -EIO;
 }
 
 dev->no = expnum;
 dev->Recv_wr_index = 0;
 dev->Recv_rd_index = 0;
 dev->usr_rd_num = 0;
 dev->recv_flag = 0;
 dev->time_out = 10;
 
 dev->tx_ctn = 0;
 dev->usr_wr_num = 0;
 dev->tran_flag = 1;
 
 MOD_INC_USE_COUNT;
 flip->private_data = dev;
 
 // default
 write_reg(SCTLR(expnum),B1200+0x08); // enable uart
 write_reg(SFOCR(expnum),0xff);  
 write_reg(SFOCR(expnum),0x0c);
 write_reg(SIER(expnum),0x01);   // rx interrupt enable, tx interrupt disable
 return 0;
}

static int uart3_close(struct inode* i,struct file* flip)
{
 uart3_dev *dev = (uart3_dev *)flip->private_data;
 
 free_irq(nIRQ2, dev);
 MOD_DEC_USE_COUNT;
 return 0;
}

static int uart3_ioctl(struct inode *inode, struct file *flip, unsigned int cmd, unsigned long arg)
{
 char temp;
 int  expnum;
 uart3_dev *dev = (uart3_dev *)flip->private_data;
 
 expnum = dev->no;
 
 switch(cmd)
 {
  case CMD_BANDRATE:  // set bandrate
   if(arg==115200)
    temp = B115200;
   else if(arg==57600)
    temp = B57600;
   else if(arg==38400)
    temp = B38400;
   else if(arg==19200)
    temp = B19200;
   else if(arg==9600)
    temp = B9600;
   else if(arg==4800)
    temp = B4800;
   else if(arg==2400)
    temp = B2400;
   else if(arg==1200)
    temp = B1200;
   else
    temp = B9600;
  // printk("set expuart %d bandrate:%d\n",expnum,arg);
   write_reg(SCTLR(expnum),temp+0x08); // enable uart
   break;
  case CMD_UARTLCR:  // set uart
   write_reg(SCONR(expnum),(char)arg);
   break;
  case CMD_RXTIMEOUT:
   dev->time_out = arg;
   break;
  case CMD_MODEM_SET:
   if((arg == 1)&&(expnum==2)){
    GPIO_DATAOUT5 |= 0x200;
    printk("high\n");
   }
   if((arg == 0)&&(expnum==2)){
    GPIO_DATAOUT5 &= 0xFFFFFDFF;
    printk("low\n");
   }
   break;
  default:
   break;
 }
 return 0;
}

struct file_operations uart3_fops =
{
 owner:  THIS_MODULE,
 open:  uart3_open,
 write:  uart3_write,
 read:  uart3_read,
 ioctl: uart3_ioctl,
 release: uart3_close,
};

static int __init uart3_init(void)
{
 int i;
 char temp;
 int result;
 int cfg;
 int time,time1;

 time1 = 1;
 cfg = GPIO_CFG0;
 cfg &= 0xFFFFFC00;
 cfg |= 0x3C2;   // GPIO.0=nIRQ2, GPIO.1=output, GPIO.3=TXD3,GPIO.4=RXD3
 GPIO_CFG0 = cfg;
 GPIO_DIR0 |= 0x60006;
 GPIO_DATAOUT0 |= 0x4;
 cfg = GPIO_XICFG;
 cfg &= 0xf0;
// cfg |= 0x8;    // low level sensitive
 cfg |= 0xa;    // negative edge triggered
 GPIO_XICFG = cfg;
 
 cfg = GPIO_CFG5;  // GPIO.14=output
 cfg &= 0x3F3FFFF;
 GPIO_CFG5 = cfg;
 GPIO_DIR5 = 0x2000200; 
 GPIO_DATAOUT5 |= 0x200;
  
 uart3_int(96);     // default 9600
 
 GPIO_DATAOUT0 |= 0x02;    // gpio.1=1
 ms_delay(10);
 GPIO_DATAOUT0 &= ~(0x02);   // gpio.1=0
 ms_delay(20);
 GPIO_DATAOUT0 |= 0x02;    // gpio.1=1
 write_reg(gIR,0xf0);   // enable all uart port interrupt

loop:
 uart3_int(96);     // default 9600
 write_reg(gMUCR,B115200);  // main uart bandrate 115200
 uart3_int(6);     // 230400bps@15MHz
 for(i=0;i<time1;i++){
  ms_delay(10);
 }
 time = TIME_OUT;
 UART_TX = gMUCR;
 do{
  temp = UART_LSR;
  time--;
  if(time == 0){
   time1++;
   goto loop;
  }
 }while((temp & LSR_RFDR)!=LSR_RFDR);
 temp = UART_RX;
 if(temp != B115200){
  time1++;
//  printk("temp = %02x\n",temp);
  goto loop;
 }
 
 for(i=0;i<EXPUART_NUM;i++){ 
  result = register_chrdev(exp_uart[i].major_num,exp_uart[i].dev_name,&uart3_fops);
  if(result == 0)
   printk("expand uart %d at 0xfff80300 (irq = 31) is a W90N745!\n",i+1);
 }
 uart3tx_busy = NO;
 return 0;
}

static void uart3_release(void)
{
 int i;
 
 for(i=0;i<EXPUART_NUM;i++){ 
  unregister_chrdev(exp_uart[i].major_num,exp_uart[i].dev_name);
 }
 printk("cancel the device expand uart1,2,3,4!\n");
 return;
}

module_init(uart3_init);
module_exit(uart3_release);
      

Read More...

星期五, 十一月 30, 2007

Locale 详解

locale 是国际化与本土化过程中的一个非常重要的概念,个人认为,对于中文用户来说,通常会涉及到的国际化或者本土化,大致包含三个方面:看中文,写中文,与 window中文系统的兼容和通信。从实际经验上看来,locale的设定与看中文关系不大,但是与写中文,及window分区的挂载方式有很密切的关 系。本人认为就像一个纯英文的Windows能够浏览中文,日文或者意大利文网页一样,你不需要设定locale就可以看中文。那么,为什么要设定 locale呢?什么时候会用到locale呢?  
一、为什么要设定locale 正如前面我所讲的,设定locale与你能否浏览中文的网页没有直接的关系,即便你把locale设置成en_US.ISO-8859-1这样一个标准的 英文locale你照样可以浏览中文的网页,只要你的系统里面有相应的字符集(这个都不一定需要)和合适的字体(如simsun),浏览器就可以把网页翻 译成中文给你看。具体的过程是网络把网页传送到你的机器上之后,浏览器会判断相应的编码的字符集,根据网页采用的字符集,去字体库里面找合适的字体,然后 由文字渲染工具把相应的文字在屏幕上显示出来。  

在下文本人会偶尔把字符集比喻成密码本,个人觉得对于一些东西比较容易理解,假如你不习惯的话,把全文copy到任何文本编辑器,用字符集替换密码本即可。  

那 有时候网页显示乱码或者都是方框是怎么回事呢?个人认为,显示乱码是因为设定的字符集不对(或者没有相应的字符集),例如网页是用UTF-8编码的,你非 要用GB2312去看,而系统根据GB2312去找字体,然后在屏幕上显示,当然是一堆的乱码,也就是说你用一个错误的密码本去翻译发给你的电报,当然内 容那叫一个乱;至于有些时候浏览的网页能显示一部分汉字,但有很多的地方是方框,能够显示汉字说明浏览器已经正确的判断出了网页的编码,并在字体库里面找 到了相应的文字,但是并不是每个字体库都包含某个字符集全部的字体的缘故,有些时候会显示不完全,找一个比较全的支持较多字符集的字体就可以了。  

既然我能够浏览中文网页,那为什么我还要设定locale呢?  

其 实你有没有想过这么一个问题,为什么gentoo官方论坛上中文论坛的网页是用UTF-8编码的(虽然大家一直强烈建议用GB2312编码),但是新浪网 就是用GB2312编码的呢?而Xorg的官方网页竟然是ISO-8859-15编码的,我没有设定这个locale怎么一样的能浏览呢?这个问题就像是 你有所有的密码本,不论某个网站是用什么字符集编码的,你都可以用你手里的密码本把他们翻译过来,但问题是虽然你能浏览中文网页,但是在整个操作系统里面 流动的还是英文字符。所以,就像你能听懂英语,也能听懂中文。 最根本的问题是:你不可以写中文。  

当你决定要写什么东西的时候,首先 要决定的一件事情是用那种语言,对于计算机来说就是你要是用哪一种字符集,你就必须告诉你的linux系统,你想用那一本密码本去写你想要写的东西。知道 为什么需要用GB2312字符集去浏览新浪了吧,因为新浪的网页是用GB2312写的。  

为了让你的Linux能够输入中文,就需要把 系统的locale设定成中文的(严格说来是locale中的语言类别LC_CTYPE ),例如zh_CN.GB2312、zh_CN.GB18030或者zh_CN.UTF-8。很多人都不明白这些古里古怪的表达方式。这个外星表达式规定 了什么东西呢?这个问题稍后详述,现在只需要知道,这是locale的表达方式就可以了。  

二、到底什么是locale? locale这个单词中文翻译成地区或者地域,其实这个单词包含的意义要宽泛很多。Locale是根据计算机用户所使用的语言,所在国家或者地区,以及当地的文化传统所定义的一个软件运行时的语言环境。  

这 个用户环境可以按照所涉及到的文化传统的各个方面分成几个大类,通常包括用户所使用的语言符号及其分类(LC_CTYPE),数字 (LC_NUMERIC),比较和排序习惯(LC_COLLATE),时间显示格式(LC_TIME),货币单位(LC_MONETARY),信息主要是 提示信息,错误信息, 状态信息, 标题, 标签, 按钮和菜单等(LC_MESSAGES),姓名书写方式(LC_NAME),地址书写方式(LC_ADDRESS),电话号码书写方式 (LC_TELEPHONE),度量衡表达方式(LC_MEASUREMENT),默认纸张尺寸大小(LC_PAPER)和locale对自身包含信息的 概述(LC_IDENTIFICATION)。  

所以说,locale就是某一个地域内的人们的语言习惯和文化传统和生活习惯。一个地 区的locale就是根据这几大类的习惯定义的,这些locale定义文件放在/usr/share/i18n/locales目录下面,例如 en_US, zh_CN and de_DE@euro都是locale的定义文件,这些文件都是用文本格式书写的,你可以用写字板打开,看看里边的内容,当然出了有限的注释以外,大部分 东西可能你都看不懂,因为是用的Unicode的字符索引方式。  

对于de_DE@euro的一点说明,@后边是修正项,也就是说你可 以看到两个德国的locale: /usr/share/i18n/locales/de_DE@euro /usr/share/i18n/locales/de_DE 打开这两个locale定义,你就会知道它们的差别在于de_DE@euro使用的是欧洲的排序、比较和缩进习惯,而de_DE用的是德国的标准习惯。   

上面我们说到了zh_CN.GB18030的前半部分,后半部分是什么呢?大部分Linux用户都知道是系统采用的字符集。  

三、 什么是字符集? 字符集就是字符,尤其是非英语字符在系统内的编码方式,也就是通常所说的内码,所有的字符集都放在/usr/share/i18n/charmaps,所 有的字符集也都是用Unicode编号索引的。Unicode用统一的编号来索引目前已知的全部的符号。而字符集则是这些符号的编码方式,或者说是在网络 传输,计算机内部通信的时候,对于不同字符的表达方式,Unicode是一个静态的概念,字符集是一个动态的概念,是每一个字符传递或传输的具体形式。就 像 Unicode编号U59D0是代表姐姐的“姐”字,但是具体的这个字是用两个字节表示,三个字节,还是四个字节表示,是字符集的问题。例如:UTF-8 字符集就是目前流行的对字符的编码方式,UTF-8用一个字节表示常用的拉丁字母,用两个字节表示常用的符号,包括常用的中文字符,用三个表示不常用的字 符,用四个字节表示其他的古灵精怪的字符。而GB2312字符集就是用两个字节表示所有的字符。需要提到一点的是Unicode除了用编号索引全部字符以 外,本身是用四个字节存储全部字符,这一点在谈到挂载windows分区的时候是非常重要的一个概念。所以说你也可以把Unicode看作是一种字符集 (我不知道它和UTF-32的关系,反正UTF-32就是用四个字节表示所有的字符的),但是这样表述符号是非常浪费资源的,因为在计算机世界绝大部分时 候用到的是一个字节就可以搞定的 26个字母而已。所以才会有UTF-8,UTF-16等等,要不然大同世界多好,省了这许多麻烦。
四、zh_CN.GB2312到底是在说什么? Locale 是软件在运行时的语言环境, 它包括语言(Language), 地域 (Territory) 和字符集(Codeset)。一个locale的书写格式为: 语言[_地域[.字符集]]. 所以说呢,locale总是和一定的字符集相联系的。下面举几个例子:  

1、我说中文,身处中华人民共和国,使用国标2312字符集来表达字符。 zh_CN.GB2312=中文_中华人民共和国+国标2312字符集。  

2、我说中文,身处中华人民共和国,使用国标18030字符集来表达字符。 zh_CN.GB18030=中文_中华人民共和国+国标18030字符集。  

3、我说中文,身处中华人民共和国台湾省,使用国标Big5字符集来表达字符。 zh_TW.BIG5=中文_台湾.大五码字符集  

4、我说英文,身处大不列颠,使用ISO-8859-1字符集来表达字符。 en_GB.ISO-8859-1=英文_大不列颠.ISO-8859-1字符集  

5、我说德语,身处德国,使用UTF-8字符集,习惯了欧洲风格。 de_DE.UTF-8@euro=德语_德国.UTF-8字符集@按照欧洲习惯加以修正  

注意不是de_DE@euro.UTF-8,所以完全的locale表达方式是 [语言[_地域][.字符集] [@修正值]  

生成的locale放在/usr/lib/locale/目录中,并且每个locale都对应一个文件夹,也就是说创建了de_DE@euro.UTF-8 locale之后,就生成/usr/lib/locale/de_DE@euro.UTF-8/目录,里面是具体的每个locale的内容。  

五、 怎样去自定义locale 在gentoo生成locale还是很容易的,首先要在USE里面加入userlocales支持,然后编辑locales.build文件,这个文件用 来指示glibc生成locale文件。 很多人不明白每一个条目是什么意思。 其实根据上面的说明现在应该很明确了。  

File: /etc/locales.build en_US/ISO-8859-1 en_US.UTF-8/UTF-8  

zh_CN/GB18030 zh_CN.GBK/GBK zh_CN.GB2312/GB2312 zh_CN.UTF-8/UTF-8  

上面是我的locales.build文件,依次的说明是这样的:  

en_US/ISO-8859-1:生成名为en_US的locale,采用ISO-8859-1字符集,并且把这个locale作为英文_美国locale类的默认值,其实它和en_US.ISO-8859-1/ISO-8859-1没有任何区别。  

en_US.UTF-8/UTF-8:生成名为en_US.UTF-8的locale,采用UTF-8字符集。  

zh_CN/GB18030:生成名为zh_CN的locale,采用GB18030字符集,并且把这个locale作为中文_中国locale类的默认值,其实它和zh_CN.GB18030/GB18030没有任何区别。  

zh_CN.GBK/GBK: 生成名为zh_CN.GBK的locale,采用GBK字符集。 zh_CN.GB2312/GB2312:生成名为zh_CN.GB2312的locale,采用GB2312字符集。 zh_CN.UTF-8/UTF-8:生成名为zh_CN.UTF-8的locale,采用UTF-8字符集。  

关于默认locale,默认locale可以简写成en_US或者zh_CN的形式,只是为了表达简单而已没有特别的意义。  

Gentoo 在locale定义的时候掩盖了一些东西,也就是locale的生成工具:localedef。 在编译完glibc之后你可以用这个localedef 再补充一些locale,就会更加理解locale了。具体的可以看 localedef 的manpage。  

$localedef -f 字符集 -i locale定义文件 生成的locale的名称 例如 $localedef -f UTF-8 -i zh_CN zh_CN.UTF-8  

上面的定义方法和在locales.build中设定zh_CN.UTF-8/UTF-8的结果是一样一样的。  

六、locale的五脏六腑  

刚 刚生成了几个locale,但是为了让它们生效,必须告诉Linux系统使用那(几)个locale。这就需要对locale的内部机制有一点点的了解。 在前面我已经提到过,locale把按照所涉及到的文化传统的各个方面分成12个大类,这12个大类分别是: 1、语言符号及其分类(LC_CTYPE) 2、数字(LC_NUMERIC) 3、比较和排序习惯(LC_COLLATE) 4、时间显示格式(LC_TIME) 5、货币单位(LC_MONETARY) 6、信息主要是提示信息,错误信息, 状态信息, 标题, 标签, 按钮和菜单等(LC_MESSAGES) 7、姓名书写方式(LC_NAME) 8、地址书写方式(LC_ADDRESS) 9、电话号码书写方式(LC_TELEPHONE) 10、度量衡表达方式(LC_MEASUREMENT) 11、默认纸张尺寸大小(LC_PAPER) 12、对locale自身包含信息的概述(LC_IDENTIFICATION)。  

其 中,与中文输入关系最密切的就是 LC_CTYPE, LC_CTYPE 规定了系统内有效的字符以及这些字符的分类,诸如什么是大写字母,小写字母,大小写转换,标点符号、可打印字符和其他的字符属性等方面。而locale定 义zh_CN中最最重要的一项就是定义了汉字(Class “hanzi”)这一个大类,当然也是用Unicode描述的,这就让中文字符在Linux系统中成为合法的有效字符,而且不论它们是用什么字符集编码 的。  

LC_CTYPE % This is a copy of the "i18n" LC_CTYPE with the following modifications: - Additional classes: hanzi  

copy "i18n"  

class "hanzi"; / % ..;/ ..;/ ;;;;;;;;/ ;;;;;;;;/ ;;;; END LC_CTYPE  

在en_US的locale定义中,并没有定义汉字,所以汉字不是有效字符。所以如果要输入中文必须使用支持中文的locale,也就是zh_XX,如zh_CN,zh_TW,zh_HK等等。  

另 外非常重要的一点就是这些分类是彼此独立的,也就是说LC_CTYPE,LC_COLLATE和 LC_MESSAGES等等分类彼此之间是独立的,可以根据用户的需要设定成不同的值。这一点对很多用户是有利的,甚至是必须的。例如,我就需要一个能够 输入中文的英文环境,所以我可以把LC_CTYPE设定成zh_CN.GB18030,而其他所有的项都是en_US.UTF-8。  

七、怎样设定locale呢?  

设 定locale就是设定12大类的locale分类属性,即 12个LC_*。除了这12个变量可以设定以外,为了简便起见,还有两个变量:LC_ALL和LANG。它们之间有一个优先级的关系: LC_ALL>LC_*>LANG 可以这么说,LC_ALL是最上级设定或者强制设定,而LANG是默认设定值。 1、如果你设定了LC_ALL=zh_CN.UTF-8,那么不管LC_*和LANG设定成什么值,它们都会被强制服从LC_ALL的设定,成为 zh_CN.UTF-8。 2、假如你设定了LANG=zh_CN.UTF-8,而其他的LC_*=en_US.UTF-8,并且没有设定LC_ALL的话,那么系统的locale 设定以LC_*=en_US.UTF-8。 3、假如你设定了LANG=zh_CN.UTF-8,而其他的LC_*,和LC_ALL均未设定的话,系统会将LC_*设定成默认值,也就是LANG的值 zh_CN.UTF-8 。 4、假如你设定了LANG=zh_CN.UTF-8,而其他的LC_CTYPE=en_US.UTF-8,其他的LC_*,和LC_ALL均未设定的话, 那么系统的locale设定将是:LC_CTYPE=en_US.UTF-8,其余的 LC_COLLATE,LC_MESSAGES等等均会采用默认值,也就是LANG的值,也就是LC_COLLATE=LC_MESSAGES=……= LC_PAPER=LANG=zh_CN.UTF-8。  

所以,locale是这样设定的: 1、如果你需要一个纯中文的系统的话,设定LC_ALL= zh_CN.XXXX,或者LANG= zh_CN.XXXX都可以,当然你可以两个都设定,但正如上面所讲,LC_ALL的值将覆盖所有其他的locale设定,不要作无用功。 2、如果你只想要一个可以输入中文的环境,而保持菜单、标题,系统信息等等为英文界面,那么只需要设定LC_CTYPE=zh_CN.XXXX,LANG = en_US.XXXX就可以了。这样LC_CTYPE=zh_CN.XXXX,而LC_COLLATE=LC_MESSAGES=……= LC_PAPER=LANG=en_US.XXXX。 3、假如你高兴的话,可以把12个LC_*一一设定成你需要的值,打造一个古灵精怪的系统: LC_CTYPE=zh_CN.GBK/GBK(使用中文编码内码GBK字符集); LC_NUMERIC=en_GB.ISO-8859-1(使用大不列颠的数字系统) LC_MEASUREMEN=de_DE@euro.ISO-8859-15(德国的度量衡使用ISO-8859-15字符集) 罗马的地址书写方式,美国的纸张设定……。估计没人这么干吧。 4、假如你什么也不做的话,也就是LC_ALL,LANG和LC_*均不指定特定值的话,系统将采用POSIX作为lcoale,也就是C locale。

Read More...

星期四, 十一月 29, 2007

LQ035Q7DH01参数

为方便客户调试SHARP LQ035Q7DH01 LCD,现在我们将该款LCD主要的几个参数公布如下:

/* Sharp LQ035 portrait mode ADTFT display parameters */
const LCD_PARAM_T sharp_lq035 =
{
  21,     /* Horizontal back porch */
  11,     /* Horizontal front porch */
  13,     /* HSYNC pulse width */
  240,     /* Pixels per line */
  3,     /* Vertical back porch */
  4,     /* Vertical front porch */
  2,     /* VSYNC pulse width */
  320,     /* Lines per panel */
  0,     /* Do not invert output enable */
  1,     /* Invert panel clock */
  1,     /* Invert HSYNC */
  0,     /* Do not invert VSYNC */
  1,     /* AC bias frequency (not used) */
  16,     /* Bits per pixel */

Read More...

星期一, 十一月 26, 2007

CS8900 base address的确定方法『转』

开始研究tftp的实现方法,框架基本上是(从底层向上):媒介->IP->UDP->TFTP。其中,CS8900A就可以完成 PHY层和MAC层的所有工作,在媒介的上部应该还有一个LLC(logical link control),IP层也不仅仅包含IP协议,另外还需要有arp协议来确定host的mac地址。这个整体的框架后面详细介绍,下面先简略介绍 cs8900a的base address的确定方法和探测程序。
cs8900a实际上已经比较老了,性能和价格上都比不过dm9000,Linux 2.6的内核默认的是支持dm9000的以太网控制器。但是EDUKIT-III上仍然使用的是cs8900a,所以还是专心看cs8900a。要注意, 一是以学习的态度去研究,二是总结阅读datasheet的方法。
首先看看cs8900a到底是什么。

·Single-Chip IEEE 802.3 Ethernet Controller with Direct ISA-Bus Interface
·Maximum Current Consumption = 55 mA (5V Supply)
·3 V or 5 V Operation
·Industrial Temperature Range
· Comprehensive Suite of Software Drivers Available
·Efficient PacketPage Architecture Operates in I/O and Memory Space, and as DMA Slave
·Full Duplex Operation
·On-Chip RAM Buffers Transmit and ReceiveFrames
·10BASE-T Port with Analog Filters, Provides:
- Automatic Polarity Detection and Correction
·AUI Port for 10BASE2, 10BASE5 and 10BASE-F
·Programmable Transmit Features:
- Automatic Re-transmission on Collision
- Automatic Padding and CRC Generation
·Programmable Receive Features:
- Stream Transfer; for Reduced CPU Overhead
- Auto-Switch Between DMA and On-Chip Memory
- Early Interrupts for Frame Pre-Processing
- Automatic Rejection of Erroneous Packets
·EEPROM Support for Jumperless Configuration
·Boot PROM Support for Diskless Systems
·Boundary Scan and Loopback Test
·LED Drivers for Link Status and LAN Activity
·Standby and Suspend Sleep Modes

可以看出,cs8900a最重要的部分就是:
·ISA总线接口。这部分可以使外部的glue logic最小,简化编程,方便设计。
·集成10BASE-T transmit and receive filters。这个加上专用变压器和RJ45对应的网线,就可以完成PHY层的功能。所以,硬件设计时不需要考虑PHY芯片了。at91rm9200集 成了以太网控制器,但是只是实现了MAC层的功能,所以需要扩展一个PHY层芯片完成网络接口的设计。
·特殊的PacketPage架构。这实际上是cs8900a的特色,本来IO访问模式和memory访问模式是不同的,但是cs8900a屏蔽了这种差异,提供了一个统一的接口。这样在程序设计上,就可以使用统一的内存空间,来完成对cs8900a的操作。
·EEPROM的支持。
现在就PacketPage架构和两种访问模式展开分析。

The CS8900A architecture is based on a unique, highly-efficient method of accessing internal registers and buffer memory known as PacketPage. PacketPage provides a unified way of controlling the CS8900A in Memory or
I/O space that minimizes CPU overhead and simplifies software. It provides a flexible set of performance features and configuration options, allowing designers to develop Ethernet circuits that meet their particular system requirements.

cs8900a有两种访问模式:IO模式和MEM模式。在IO模式下,cs8900a占用host最小的空间,仅仅16个字节(8个16bit的IO ports)。对地址空间紧张的系统来说,是一个合适的选择。在MEM模式下,cs8900a占用4K的空间,这是软件可以直接访问cs8900a的内部 寄存器,而且这4K的空间还分成了6个组成部分,具体可以参考datasheet的4.1.1部分。
cs8900a默认的是IO模式。在系统设计时,最好两种模式都要预留出来。原因有很多,首先,大多数情况下,MEM模式性能更高。因为ISA内存操作比 IO操作需要更少的时钟周期。所以最好有mem模式。其次呢,如果内存空间不可用,或者特殊操作时,IO模式是唯一的选择,而且如果使用了EEPROM, 板子发生故障时,EEPROM通常是空白的,为了对EEPROM编程,cs8900a也必须工作在IO模式。综上两种分析,系统设计还是要设计两种访问方 式为好。在ISA总线接口中,I/O空间和内存空间是独立寻址的,因此它们有各自不同的读写信号。但ARM体系中,I/O空间和内存空间是统一寻址。问题 就出来了,如果两种访问模式同时存在,那么如何区分是那种访问模式呢?
这个在硬件设计中是很普遍的问题。电路设计的时候用高位地址线(ADDR24)经过组合逻辑的译码来区分I/O空间和内存空间的读写。cs8900a只需 要20根地址现,而s3c2410的地址线是32条。那么可以让a[19:0]对应cs8900a的sa[19:0],拿出一条地址线A24来区分两种模 式。我画了原理图(注意,这仅仅是组合逻辑译码的一种,可以有其他的方式,这个就有硬件设计来决定了),如下:
500)this.width=500;" border="0" height="384" width="500">
文件: 1.rar
大小: 25KB
下载: 下载
通过上述的原理图,很容易明白,A24地址线为低时,为MEM模式。A24为高时,为IO模式。另外,cs8900a可以接在不同的bank。比如, EDUKIT接在nGCS3,基址是0x18000000,那么很明显,MEM模式的基地址是0x18000000,而IO模式的基地址是 0x18000000+(1<<24),也就是0x19000000。但是IO模式的基地址还没有最终结果呢。看datasheet,发现 packetpage偏移0x0020的地方为IO base address。在复位以后,如果没有EEPROM,那么该寄存器的值就是默认的0x0300。如果采用IO方式,那么初始时对应的该寄存器始终为 0x0300,前面得到的0x19000000再加上这个0x300才能找到最终8个16bitsIO ports的首地址。以后所有的操作都是通过这8个IO ports来进行的,包括访问MEM模式的那些寄存器。也就是MEM要访问那些内部寄存器,只是需要直接寻址就可以了,而IO模式则要先找到IO ports,然后通过这些IO ports来寻找到内部寄存器。显然,在效率上又差了一层。
下面看看EDUKIT-III的不同。EDUKIT-III采用了核心板+底板的设计方法。核心板SoC的A24最终经过电平转换芯片 74LVCH62245成为AB24,然后经过CPLD处理逻辑关系得到相应的IO和MEM控制信号。在CPLD中的逻辑处理跟上面原理图的处理是相同 的,不过实验箱需要处理的逻辑太多,用CPLD要好。这个也是看了好长时间的电路图才推断出来的。
由此可见base address的计算方法如下(假定选择nGCSn,对应bank的首地址为nGCS_ADDR)
MEM模式: base address = nGCS_ADDR
IO模式: base address = nGCS_ADDR + (1 << 24) + 0x0300.
当然,还有的默认前提就是使用了A24这条地址线来进行区分。当然,如果你不使用A24,只采用IO模式,不支持MEM模式,那么你的基地址完全可以是 nGCS_ADDR+0x0300。需要注意的是,在弄清楚原理的情况下,一定要看硬件设计如何,这样才能确定基地址。

备注:关于IO mode和memory mode的比较
I/O mode is 99.6% as fast as Memory mode. Also, since the CS8900A defaults to I/O and you don't need glue logic in most systems in I/O mode, Cirrus recommends I/O mode.

Read More...

星期六, 十一月 24, 2007

S3C2410启动信息注释(vivi部分)『转』

uclinux的启动主要分为两个阶段:
第一部分bootloader启动阶段
第二部分linux 内核初始化和启动阶段
     第一节:start_kernel
     第二节:用户模式( user_mode )开始,start_kernel结束
     第三节:加载linux内核完毕,转入cpu_idle进程

Specification of the machine

- CPU: S3C2410
- MEM: 64MB (bank 0)
- ROM: 64MB (Inte Starata Flash Memory)
- got UART ports

64MB RAM (DRAM)

0x3400 0000 +----------------------+
                  |VIVI RAM (size: 1M)    |
0x33F0 0000 +----------------------+ VIVI_RAM_BASE
                  |HEAP AREA(size: 1M)   |
0x33E0 0000 +----------------------+ HEAP_BASE
                  |MMU TABLE(size: 16k)  |
0x33DF C000 +----------------------+ MMU_TABLE_BASE
                 
|                               |
           mtd partition table (size: 16k)| MTD_PART_SIZE -----\
0x33DF 8000 +----------------------+                               |
          vivi parameter table(size: 16k)| PARAMETER_TLB_SIZE  | vivi pri data
0x33DF 4000 |----------------------+                               |
        
linux command line (size: 16k)  | LINUX_CMD_SIZE  -----/
                  |                               |

0x33DF 0000 +----------------------+ VIVI_PRIV_RAM_BASE
                  |                              |
                  |   free memory            |
                  |                              |
0x3010 8000 +----------------------+
                  |   kernel (size: 1MB)     |
0x3000 8000 +----------------------+
                  |                              |
                  |   free memory            |
                  |                              |
0x3000 0100 +----------------------+
           kernel param (size 256Byte)  |
0x3000 0000 +----------------------+ DRAM_BASE


64MB ROM (NAND Flash)

* 0x0400 0000 +-----------------------------+
*                   |                                         |
*                   | jffs2 (size: 61M)                   | user
*                   |                                         |
* 0x0030 0000 +-----------------------------+
*                   | root  (size: 2048k)                 |
* 0x0010 0000 +-----------------------------+
*                   | kernel (size: 832k)                |
* 0x0003 0000 +-----------------------------+
*                   | param (size: 64k)                   |
* 0x0002 0000 +-----------------------------+
*                   | vivi  (size: 128k)                  |
* 0x0000 0000 +-----------------------------+

[root@localhost root]# minicom
Welcome to minicom 2.00.0


OPTIONS: History Buffer, F-key Macros, Search History Buffer, I18nCompiled on Jan 25 2003, 00:15:18.Press CTRL-A Z for help on special keys


minicom信息


VIVI version 0.1.4 (root@BC) (gcc version 2.95.2 20000516 (release) [Rebel.com]5


Bootloader头信息,版本等,这个因不同的bootloader的设计而有所不同,由此你能看出bootloader的版本信息,有很多使用的是通用的bootloader,如u-boot,redboot等。

链接 Boot Loader  VIVI

源代码: vivi/lib/version.c vivi/main.c


#define VIVI_RELEASE "0.1.4"
#define VIVI_COMPILE_BY "root"
#define VIVI_COMPILE_HOST "localhost.localdomain"
#define VIVI_COMPILER "gcc version 2.95.2 20000516 (release)[Rebel.com]"
#define UTS_VERSION "#0.1.4 六 6月 5 05:27:07 CST 2004"

const char *vivi_banner = "VIVI version " VIVI_RELEASE " (" VIVI_COMPILE_BY "@" VIVI_COMPILE_HOST ") (" VIVI_COMPILER ") " UTS_VERSION "\r\n";putstr(vivi_banner);


MMU table base address = 0x33DFC000
Succeed memory mapping.


链接 MMU

    内存映射(memory map)就是指在整个4GB物理地址空间中有哪些地址范围被分配用来寻址系统的RAM单元。比如,在SA-1100CPU中,从0xC000,0000开 始的512M地址空间被用作系统的RAM地址空间,而在Samsung S3C44B0X CPU中,从0x0c00,0000到0x1000,0000之间的64M地址空间被用作系统的RAM地址空间。虽然 CPU 通常预留出一大段足够的地址空间给系统 RAM,但是在搭建具体的嵌入式系统时却不一定会实现CPU预留的全部RAM地址空间。也就是说,具体的嵌入式系统往往只把CPU预留的全部RAM地址空 间中的一部分映射到RAM单元上,而让剩下的那部分预留RAM地址空间处于未使用(unused)状态。由于上述这个事实,因此Boot Loader的stage2必须在它想干点什么之前——比如,将存储在flash上的内核映像读到RAM空间中——检测整个系统的内存映射情况,也即它必 须知道CPU预留的全部RAM地址空间中的哪些被真正映射到RAM地址单元,哪些是处于unused状态的。
    VIVI中用CONFIG_BOOTUP_MEMTEST中的memtest完成基本的RAM检测,针对
Samsung S3C2410 CPU从0x3000,0000到0x3400,0000之间的64M地址空间被用作系统的RAM地址空间。

源代码:vivi/arch/s3c2410/mmu.c

#define MMU_TABLE_BASE (HEAP_BASE - MMU_TABLE_SIZE)
#define HEAP_BASE (VIVI_RAM_BASE - HEAP_SIZE)
#define VIVI_RAM_BASE (DRAM_BASE + DRAM_SIZE - VIVI_RAM_SIZE)
#define DRAM_BASE DRAM_BASE0
#define DRAM_BASE0       0x30000000      /* base address of dram bank 0 */
#define DRAM_SIZE        SZ_64M
#define SZ_8M            0x00800000
#define VIVI_RAM_SIZE    SZ_1M
#define HEAP_SIZE        SZ_1M
#define SZ_1M            0x00100000
#define MMU_TABLE_SIZE   SZ_16K
#define SZ_16K           0x00004000static
unsigned long *mmu_tlb_base = (unsigned long *) MMU_TABLE_BASE;
putstr_hex("MMU table base address = 0x", (unsigned long)mmu_tlb_base);

源代码:vivi/main.c

mem_map_init();
mmu_init();
putstr("Succeed memory mapping.\r\n");



NAND device: Manufacture ID: 0xec, Chip ID: 0x76 (Samsung K9D1208V0M)

链接:闪存芯片

NAND Flash:1989年,东芝公司发明。是以块和页为单位来读写的,不能随机访问某个指定的点。因而相对来说读取速度较慢,而擦除和写入的速度则比较快。一般适用在大容量的多媒体应用中,如:CF,SM。
NOR  Flash:Intel于1988年发明.随机读取的速度比较快,随机按字节写,写入和擦除速度很低。一般适合应用于数据/程序的存贮应用中,如:手机,机顶盒。NOR还可以片内执行(execute-in-place)XIP。

简单的说,NAND类似于硬盘,NOR类似于内存

源代码:vivi/drivers/mtd/nand/smc_core.c


struct nand_flash_dev {
    char * name;
    int manufacture_id;
    int model_id;
    int chipshift;
    char page256;
    char pageadrlen;
    unsigned long erasesize;
};
static struct nand_flash_dev nand_flash_ids[] = {
  {"Toshiba TC5816BDC",     NAND_MFR_TOSHIBA, 0x64, 21, 1, 2, 0x1000},    // 2Mb 5V
  {"Toshiba TC58V16BDC",    NAND_MFR_TOSHIBA, 0xea, 21, 1, 2, 0x1000},    // 2Mb 3.3V
  {"Toshiba TC5832DC",      NAND_MFR_TOSHIBA, 0x6b, 22, 0, 2, 0x2000},    // 4Mb 5V
  {"Toshiba TC58V32DC",     NAND_MFR_TOSHIBA, 0xe5, 22, 0, 2, 0x2000},    // 4Mb 3.3V
  {"Toshiba TC58V64AFT/DC", NAND_MFR_TOSHIBA, 0xe6, 23, 0, 2, 0x2000},    // 8Mb 3.3V
  {"Toshiba TH58V128DC",    NAND_MFR_TOSHIBA, 0x73, 24, 0, 2, 0x4000},    // 16Mb
  {"Toshiba TC58256FT/DC",  NAND_MFR_TOSHIBA, 0x75, 25, 0, 2, 0x4000},    // 32Mb
  {"Toshiba TH58512FT",     NAND_MFR_TOSHIBA, 0x76, 26, 0, 3, 0x4000},    // 64Mb
  {"Toshiba TH58NS100/DC",  NAND_MFR_TOSHIBA, 0x79, 27, 0, 3, 0x4000},    // 128Mb
  {"Samsung KM29N16000",    NAND_MFR_SAMSUNG, 0x64, 21, 1, 2, 0x1000},    // 2Mb 5V
  {"Samsung KM29W16000",    NAND_MFR_SAMSUNG, 0xea, 21, 1, 2, 0x1000},    // 2Mb 3.3V
  {"Samsung unknown 4Mb",   NAND_MFR_SAMSUNG, 0x6b, 22, 0, 2, 0x2000},    // 4Mb 5V
  {"Samsung KM29W32000",    NAND_MFR_SAMSUNG, 0xe3, 22, 0, 2, 0x2000},    // 4Mb 3.3V
  {"Samsung unknown 4Mb",   NAND_MFR_SAMSUNG, 0xe5, 22, 0, 2, 0x2000},    // 4Mb 3.3V
  {"Samsung KM29U64000",    NAND_MFR_SAMSUNG, 0xe6, 23, 0, 2, 0x2000},    // 8Mb 3.3V
  {"Samsung KM29U128T",     NAND_MFR_SAMSUNG, 0x73, 24, 0, 2, 0x4000},    // 16Mb
  {"Samsung KM29U256T",     NAND_MFR_SAMSUNG, 0x75, 25, 0, 2, 0x4000},    // 32Mb
  {"Samsung K9D1208V0M",    NAND_MFR_SAMSUNG, 0x76, 26, 0, 3, 0x4000},    // 64Mb
  {"Samsung K9D1G08V0M",    NAND_MFR_SAMSUNG, 0x79, 27, 0, 3, 0x4000},    // 128Mb
  {NULL,}
};
mtd->name = nand_flash_ids[i].name;
printk("NAND device: Manufacture ID:" \
                " 0x%02x, Chip ID: 0x%02x (%s)\n",
                nand_maf_id, nand_dev_id, mtd->name);


Could not found stored vivi parameters. Use default vivi parameters.

vivi参数

vivi_parameter_t default_vivi_parameters[] = {
    { "mach_type",            MACH_TYPE,    NULL },
    { "media_type",            MT_S3C2410,    NULL },
    { "boot_mem_base",        0x30000000,    NULL },
    { "baudrate",            UART_BAUD_RATE,    NULL },
    { "xmodem_one_nak",        0,        NULL },
    { "xmodem_initial_timeout",    300000,        NULL },
    { "xmodem_timeout",        1000000,    NULL },
    { "ymodem_initial_timeout",    1500000,    NULL },
    { "boot_delay",            0x1000000,    NULL }
};
#define MACH_TYPE        193
#define MT_S3C2410       MT_SMC_S3C2410
#define UART_BAUD_RATE   115200

源代码:vivi/lib/priv_data.c

int init_priv_data(void)
{
    int ret_def;
#ifdef CONFIG_PARSE_PRIV_DATA //#define CONFIG_PARSE_PRIV_DATA 1
    int ret_saved;
#endif
    ret_def = get_default_priv_data();
#ifdef CONFIG_PARSE_PRIV_DATA
    ret_saved = load_saved_priv_data();
    if (ret_def && ret_saved) {
        printk("Could not found vivi parameters.\n");
        return -1;
    } else if (ret_saved && !ret_def) {
        printk("Could not found stored vivi parameters.");
        printk(" Use default vivi parameters.\n");
    } else {
        printk("Found saved vivi parameters.\n");
    }
#else
    if (ret_def) {
        printk("Could not found vivi parameters\n");
        return -1;
    } else {
        printk("Found default vivi parameters\n");
    }
#endif

#ifdef CONFIG_DEBUG_VIVI_PRIV
    display_param_tlb();
    display_mtd_partition();
#endif
    return 0;



Press Return to start the LINUX now, any other key for vivi

按回车进入linux,其它键进vivi。当然,从vivi中boot也可以。

源代码:vivi/main.c

void boot_or_vivi(void)
{
    char c;
    int ret;
    ulong boot_delay;

    boot_delay = get_param_value("boot_delay", &ret);
    if (ret) boot_delay = DEFAULT_BOOT_DELAY;
    /* If a value of boot_delay is zero,
     * unconditionally call vivi shell */
    if (boot_delay == 0) vivi_shell();


    /*
     * wait for a keystroke (or a button press if you want.)
     */
    printk("Press Return to start the LINUX now, any other key for vivi\n");
    c = awaitkey(boot_delay, NULL);
    if (((c != '\r') && (c != '\n') && (c != '\0'))) {
        printk("type \"help\" for help.\n");
        vivi_shell();
    } // 有问题,'\r' '\n'都是回车,'\0'是什么?
    run_autoboot();

    return;
}
【注:这里还有个小问题。就是在这里添加的时候,\n\r要合起来用,不能只用\n。原因如下:
    计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。
    于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车”,告诉打字机把打印头定位在左边界;另一个叫做“换行”,告诉打字机把纸向下移一行。
    这就是“换行”和“回车”的来历,从它们的英语名字上也可以看出一二。
    后来,计算机发明了,这两个概念也就被般到了计算机上。那时,存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以。于是,就出现了分歧。Unix 系统里,每行结尾只有“<换行>”,即“\n”;Windows系统里面,每行结尾是“<换行><回车>”,即“\ n\r”;Mac系统里,每行结尾是“<回车>”。一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。
    这几个地方我都遇到过,不过一直没有搞清楚。现在才算是找到根源了。】



Copy linux kernel from 0x00030000 to 0x30008000, size = 0x00100000 ... done

    启 动linux kernel,kernel映像必须被放到MTD设备的一个分区中,from、size分别表示linux kernel起始地址和kernel的大小。为什么要指定kernel大小呢?因为kernel首先要被copy到boot_mem_base + 0x8000的地方,然后在boot_mem_base + 0x100开始的地方设置内核启动参数。要拷贝 kernel,当然需要知道kernel的大小啦,这个大小不一定非要和kernel实际大小一样,但是必须大于等于kernel的大小。(单位字节)
  
    media_type是指定的媒介类型,因为boot命令对不同媒介的处理方式是不同的,例如如果kernel在 SDRAM中,那么boot执行的过程中就可以跳过拷贝kernel映像到SDRAM中这一步骤了
   
Boot命令识别的媒介类型有以下三种:
ram 表示从RAM中启动linux kernel,linux kernel必须要放在RAM中,我的S3C2410平台就是如此。
nor 表示从NOR Flash中启动linux kernel,linux kernel必须已经被烧写到了NOR Flash中
smc 表示从NAND Flash中启动linux kernel,linux kernel必须已经被烧写到了NAND Flash中 


源代码:vivi/lib/boot-kernel.c

    int boot_kernel(ulong from, size_t size, int media_type)
{
    int ret;
    ulong boot_mem_base;    /* base address of bootable memory */
    ulong to;

    boot_mem_base = get_param_value("boot_mem_base", &ret);
    if (ret) {
        printk("Can't get base address of bootable memory\n");
        printk("Get default DRAM address. (0x%08lx\n", DRAM_BASE);
        boot_mem_base = DRAM_BASE;
    }   //boot_mem_base = 0x30000000

    /* copy kerne image */
    to = boot_mem_base + LINUX_KERNEL_OFFSET;
        // #define LINUX_KERNEL_OFFSET    0x8000
        // to = 0x30008000
    printk("Copy linux kernel from 0x%08lx to 0x%08lx, size = 0x%08lx ... ",from, to, size);
    ret = copy_kernel_img(to, (char *)from, size, media_type);
        // 将内核镜像从NAND FLASH拷入RAM
        //
media_type=MT_SMC_S3C2410
    if (ret) {
        printk("failed\n");
        return -1;
    } else {
        printk("done\n");
    }

    return 0;
}

  
void command_boot(int argc, const char **argv)
{
    int media_type = 0;
    ulong from = 0;
    size_t size = 0;
    mtd_partition_t *kernel_part;
    int ret;
   
    media_type = get_param_value("media_type", &ret);
            if (ret) {
                printk("Can't get default 'media_type'\n");
                return;
            }
    kernel_part = get_mtd_partition("kernel");
            if (kernel_part == NULL) {
                printk("Can't find default 'kernel' partition\n");
                return;
            }
    from = kernel_part->offset;
    size = kernel_part->size;
    boot_kernel(from, size, media_type);
}

user_command_t boot_cmd = {
    "boot",
    command_boot,
    NULL,
    "boot [{cmds}] \t\t\t-- Booting linux kernel"
};

zImage magic = 0x016f2818

判断内核文件是否为压缩镜像,而当前文件是压缩镜像


源文件: vivi/lib/boot-kernel.c

#define LINUX_ZIMAGE_MAGIC    0x016f2818

if (*(ulong *)(to + 9*4) != LINUX_ZIMAGE_MAGIC) {
       printk("Warning: this binary is not compressed linux kernel image\n");
       printk("zImage magic = 0x%08lx\n", *(ulong *)(to + 9*4));
    } else {
        printk("zImage magic = 0x%08lx\n", *(ulong *)(to + 9*4));
    }

Setup linux parameters at 0x30000100
linux command line is: "noinitrd root=/dev/bon/3 init=/linuxrc console=ttyS0"


设置linux参数和控制行

static void setup_linux_param(ulong param_base)
{
/*
linux parameters*/
    struct param_struct *params = (struct param_struct *)param_base;
    char *linux_cmd;
    printk("Setup linux parameters at 0x%08lx\n", param_base);
    memset(params, 0, sizeof(struct param_struct));
    params->u1.s.page_size = LINUX_PAGE_SIZE;
    params->u1.s.nr_pages = (DRAM_SIZE >> LINUX_PAGE_SHIFT);

/* set linux command line */
    linux_cmd = get_linux_cmd_line();//
linux_cmd = 0x33DF8008
    if (linux_cmd == NULL) {
        printk("Wrong magic: could not found linux command line\n");
    } else {
        memcpy(params->commandline, linux_cmd, strlen(linux_cmd) + 1);
        printk("linux command line is: \"%s\"\n", linux_cmd);
    }
}

//
#define boot_mem_base 0x30000000
//#define LINUX_PARAM_OFFSET 0x100
setup_linux_param(boot_mem_base + LINUX_PARAM_OFFSET);


MACH_TYPE = 193

/* Get machine type */
    mach_type = get_param_value("mach_type", &ret);
    printk("MACH_TYPE = %d\n", mach_type);

NOW, Booting Linux......

激动人心的消息,终于可以引导linux了。。。

源代码: vivi/lib/boot-kernel.c

printk("NOW, Booting Linux......\n");
    call_linux(0, mach_type, to);

#elif defined(CONFIG_ARCH_S3C2410)
void  call_linux(long a0, long a1, long a2)
{
    cache_clean_invalidate();
    tlb_invalidate();

__asm__(
    "mov    r0, %0\n"
    "mov    r1, %1\n"
    "mov    r2, %2\n"
    "mov    ip, #0\n"
    "mcr    p15, 0, ip, c13, c0, 0\n"    /* zero PID */
    "mcr    p15, 0, ip, c7, c7, 0\n"    /* invalidate I,D caches */
    "mcr    p15, 0, ip, c7, c10, 4\n"    /* drain write buffer */
    "mcr    p15, 0, ip, c8, c7, 0\n"    /* invalidate I,D TLBs */
    "mrc    p15, 0, ip, c1, c0, 0\n"    /* get control register */
    "bic    ip, ip, #0x0001\n"        /* disable MMU */
    "mcr    p15, 0, ip, c1, c0, 0\n"    /* write control register */
    "mov    pc, r2\n"
    "nop\n"
    "nop\n"
    : /* no outpus */
    : "r" (a0), "r" (a1), "r" (a2)
    );
//全是汇编,有空再研究

Boot Loader 调用 Linux 内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到 MEM_START+0x8000 地址处。在跳转时,下列条件要满足: 

1. CPU 寄存器的设置: 
R0=0;
R1=机器类型 ID;关于 Machine Type Number,可以参见 linux/arch/arm/tools/mach-types。 
R2=启动参数标记列表在 RAM 中起始基地址; 

2. CPU 模式:
必须禁止中断(IRQs和FIQs);
CPU 必须 SVC 模式;

3. Cache 和 MMU 的设置:
MMU 必须关闭;
指令 Cache 可以打开也可以关闭;
数据 Cache 必须关闭;

Read More...