一.丢失引导:
华恒论坛在新版本的ppcboot中的说明如下:
近来不断有客户反映,s3c2410的一些开发板有时会莫名的丢失引导。一开始我们以为是客户不熟悉操作,不小心误操作的结果,但经过一段时间的多方检测,发现这里不仅仅是误操作这么简单;我们通过对这方面的问题收集,并结合客户反馈的若干想法和建议,我们总结后发现这不单单是s3c2410相关开发板的问题,还包括ppc8250、xscale425、s3c2440、mxl9328、arm9200等,都有类似问题出现过。通过对这类板卡的分析发现这类板卡都有一个共性,就是他们的rom存储都是采用的intel e28f128 这款flash;再者了解到e28f128这款flash的控制时序相对比较简单很可能也是导致这个问题发生重要原因。

更改后的ppcboot.bin在当前的使用上做如下调整
原先的引导、内核、文件系统的起始地址分别为1000000、1040000、1140000,现在分为0、40000、140000
……

以一下所提到的所有问题已经得到了彻底的解决,尤其让我们高兴的是早期对intel的这款e28f128 flash 始终支持的不是很好(这块flash往往擦写几十次可能就坏了,这实际上是这块flash的操作时序太简单所致,这也是采用这款flash的所有商家的共同问题。),我们通过多方努力,今天终于做到了性能上的突破。

现有的HHARM2410使用的是3 Volt Intel StrataFlash Memory device, 该Flash Memory提供安全模式(Security Mode),可以保护Flash中的数据防止被意外删除或重写。但是现在的 ppcboot for HHARM2410 版本并没有实现此保护功能,造成的后果是某些重要代码烧写到Flash中之后,如果后续程序跑飞,很可能会冲掉之前烧入的重要代码。这类错误很难发现而且后果通常比较严重。
针对以上问题,对现有的ppcboot for HHARM2410代码进行改进,使之可以实现保护Flash中的数据防止被意外删除或重写的功能就显得十分必要。
……


那块板的ppcboot烧写基地址是0x1000000,那么就是没有写保护的旧版了……

但不太清楚到底什么情况下可以改写flash的东西(NOR flash是可以随机读写的,但擦除只能以block为单位进行——这是否意味着,以正常方式对某个地址进行写操作,并不存在判断该地址数据是否为0xff的过程,只要有为"1"的bit就可能可以改变对应字节的内容呢?)

在丢失引导之前曾经执行过loadb 8000(之前设置了base 30000000,然而这个设置居然是对loadb无效的,而loadb命令在下载前也不会提示,所以下载完看到统计信息才知道地址错了),不知道是不是就是这个指令冲掉了引导……

而更神奇的是,后来我把ppcboot烧回去了,发现不但ppcboot没有了,kernel、initrd、jffs2等所有分区都被破坏了(某些字节被改写),难道是丢失引导后ppcboot跑飞,到处乱写了东西?


二.JTAG:
核心板的JTAG connector是10pin的那种,而且size特别小,由于找不到这种插头,最后同事硬是用两个差不多孔距的插头插了上去,把那几根线引到了另一个大的20pin connector上。DB25-JTAG用的是同时有Wiggler、STD、S3C2410三种接口的那个转换板。一开始接的是Wiggler,调试代理能认到CPU,H-JTAG也能,但就是sjf2410死活认不出来。开始怀疑是延串口长线、giveio甚至是系统的问题(调试用机,啥问题都有可能出现),但用一个测试程序去测,giveio是正常的,而且换到linux下甚至到另外一台win2k下,sjf2410都依然固执地不认cpu。后来再认真看看sjf2410的文档才醒悟过来可能是db25那边的Pin Assignment的问题。另外,后来才看到那个转接板配套的ARM9开发板的文档里其实已经指出,使用sjf2410时要把20针排线接到S3C2410的接口上……其实还是自己以前对JTAG的认识不够全面造成的问题,还好最终能够及时发现。


三.SJF2410 & 28F128J3A:
解决JTAG的问题后,SJF2410在dos下总算能认cpu了(可是Linux下面依然认不出来。。。难道华恒的程序改了Pin Assignment?或者系统本身的问题?),然而在检测Flash型号的时候,ID认出来0x89,但Device类型却认不出来。对照strata32.c和28F128J3A的datasheet,读取Identifier Codes的方法应该是先向任意地址写0x90(见Datasheet的Table 4. Command set definitions),然后读地址00000,可得到ID号0x89,而地址00001则读到Device Code,128Mbit对应的是0x18(见Table 5. Identifier Codes)。Table 5中还提到,那两个地址的最低位是A1,A0在x16模式(即16位模式)下被忽略了。Strata的代码原来是这么写的:
int Strata_CheckDevice(int targetAddr) 
{
    //_RESET();
    _WR(targetAddr, 0x00900090);
    return _RD(targetAddr+0x4);
}

试着把0x4改成0x2,果然可以读出来了,接着就自动开始了烧写。然而烧完后用内存读写功能查看,发现烧进去的东西每隔两个字节就是两个0xFF,即11 22 FF FF 55 66 FF FF 99 AA这样。再仔细阅读一下Strata.c,才发现原来这个程序对应的硬件配置是16x2,即用2片flash接成32bit的形式,一片是低16bit一片是高16bit,sjf2410也是以32bit的宽度进行读写。例如上面的_WR(targetAddr, 0x00900090);实际上就是向targetAddr开始的4个字节写入数据,也就是分别向两片flash写入16bit数0x0090(其实这从代码的注释中也可以看出来)。targetAddr仍然是按字节编码的,对于16x2的配置,每次写入32bit,地址就要+4,因此用这样的代码对16x1的配置进行烧写,就会每次只烧入了低16位,高16位没烧,而地址的增量仍然是4,于是就会出现每2字节间隔2个ff的现象。
也就是说,16x2的地址分布是这样的(假设基地址是00000000):

---mem-------flash--
0000 0000 - 0:000000
0000 0001 - 0:000001
0000 0002 - 1:000000
0000 0003 - 1:000001
0000 0004 - 0:000002
0000 0005 - 0:000003
0000 0006 - 1:000002
0000 0007 - 1:000003
....

由于flash是16位,所以其地址线的A0被忽略,也就是说000000和000001实际上都是同一个地址,左移一位后都是00000。
而16x1的地址分布是:

---mem-------flash--
0000 0000 - 0:000000
0000 0001 - 0:000001
0000 0002 - 0:000002
0000 0003 - 0:000003
0000 0004 - 0:000004
0000 0005 - 0:000005
0000 0006 - 0:000006
0000 0007 - 0:000007
....

所以,把烧写部分的代码改写成这样就行了:
    for (i=0; i<targetSize; i+=2) 
    {
        Strata_ProgFlash(i+targetAddress+targetOffset, (*((U16 *)(srcAddr+i)))&0xffff);        
        //if((i%0x100)==0xfc)
        if((i&0xff)==0xfe)
            printf("[%x]",i+2);
    }

读数也一样,根据datasheet,读Device Code的地址是0x00001,加上忽略掉的A0,就是0x00001 << 1。而在16x2的配置下,根据上面的地址应设表,0x0000 0002和0x0000 0003实际上对应高16bit的0x00000地址(忽略A0),0x0000 0004和0x0000 0005对应的才是低16bit flash的0x00001地址(忽略A0)。因此16x2读Device Code是_RD(targetAddr+0x4),16x1应该改成_RD(targetAddr+0x2)。

至于擦除,由于是按block进行的,根据28F128J3A的datasheet,它分为128个block,每个block为1Mbit,即128kb(0x20000字节),所以擦除部分的代码应该为:
    for(i=0;i<targetSize;i+=0x20000)
    {
//Strata_ClearBlockLock(targetAddress+targetOffset+i); 
      Strata_EraseSector(targetAddress+targetOffset+i);
    }

当然,Strata_EraseSector也要进行修改,不然擦除的时候就可能跳不出里面那个while循环:
void Strata_EraseSector(int targetAddress) 
{
    unsigned long ReadStatus;
    unsigned long bSR5;     // Erase and Clear Lock-bits Status, lower 16bit, 8MB Intel Strate Flash ROM
    unsigned long bSR5_2;   // Erase and Clear Lock-bits Status, higher 16bit, 8MB Intel Strate Flash ROM
    unsigned long bSR7;     // Write State Machine Status, lower 16bit, 8MB Intel Strate Flash ROM
    unsigned long bSR7_2;   // Write State Machine Status, higher 16bit, 8MB Intel Strate Flash ROM
    //_RESET();
//  _WR(targetAddress, 0x00200020);
//  _WR(targetAddress, 0x00d000d0);
    _WR(targetAddress, 0x0020); // Block Erase, First Bus Cycle, targetAddress is the address withint the block
    _WR(targetAddress, 0x00d0); // Block Erase, Second Bus Cycle, targetAddress is the address withint the block
    
    //_RESET();
    _WR(targetAddress, 0x0070); // Read Status Register, First Bus Cycle, targetAddress is any valid address within the device
    ReadStatus=_RD(targetAddress);  // Read Status Register, Second Bus Cycle, targetAddress is any valid address within the device
    bSR7=ReadStatus & (1<<7);       // lower 16-bit 8MB Strata
    //bSR7_2=ReadStatus & (1<<(7+16));// higher 16-bit 8MB Strata
    //while(!bSR7 || !bSR7_2) 
    while(!bSR7) 
    {
        _WR(targetAddress, 0x0070);
        ReadStatus=_RD(targetAddress);
        bSR7=ReadStatus & (1<<7);
        //bSR7_2=ReadStatus & (1<<(7+16));
      //printf("wait\n");
    }

    _WR(targetAddress, 0x0070); // When the block erase is complete, status register bit SR.5 should be checked. 
                    // If a block erase error is detected, the status register should be cleared before
                    // system software attempts correct actions.
    ReadStatus=_RD(targetAddress);  
    bSR5=ReadStatus & (1<<5);           // lower 16-bit 8MB Strata 
    //bSR5_2=ReadStatus & (1<<(5+16));    // higher 16-bit 8MB Strata 
    //if (bSR5==0 && bSR5_2==0) 
    if (bSR5==0) 
    {
        printf("Block @%xh Erase O.K. \n",targetAddress);
    } 
    else 
    {
        //printf("Error in Block Erasure!!\n");
        _WR(targetAddress, 0x0050); // Clear Status Register
        error_erase=1;                  // But not major, is it casual ?
    }

    _RESET();   // write 0xffh(_RESET()) after the last opoeration to reset the device to read array mode.
}


另外,烧写flash可能初始化时需要对cpu的某些寄存器进行设置。在FlashPGM里,虽然可以识别cpu,但烧写总是不顺利,应该是cpu寄存器没有设置好的原因。由于时间关系,没有对这个问题进行深入研究……

由于sjf2410开放源代码,因此很容易就可以将其修改得适应自己的硬件设置,也可以很容易地增加功能。例如我在内存读写那里加入了一个df指令,用来把flash的内容dump出来(当然,如果能进linux,直接把mtd设备内容dump出来保存到u盘上是最快的做法)。
--- sjf2410_ori/mem_rdwr.c      2002-11-15 08:29:02.000000000 +0800
+++ sjf2410/mem_rdwr.c  2006-09-18 19:34:04.000000000 +0800
@@ -19,7 +19,7 @@
 {
     int i,j;
     char command[128];
-    U32 addr;
+    U32 addr, len;
     U8 dataByte;
     U16 dataHW;
     U32 dataWord;
@@ -49,6 +49,7 @@
            printf("| wb <hex_addr> <hex_data>: write, byte data                 |\n");
            printf("| wh <hex_addr> <hex_data>: write, half-word data            |\n");
            printf("| ww <hex_addr> <hex_data>: write, word data                 |\n");
+        printf("| df <hex_addr> <len>     : dump to file                     |\n");
            printf("+-------------------------- NOTE ----------------------------+\n");
            printf("| 1. nGCS6,7 SDRAM read/write isn't supported now.           |\n");
            printf("| 2. example: >bs 2 16 1                                     |\n");
@@ -76,10 +77,47 @@
            break;

        case 'd':
+        if(command[1] == 'f'){
+          FILE *fp;
+          char filename[FILENAME_MAX];
+          scanf("%x %x",&addr, &len);
+          addr=addr&0xfffffffe; //ignore A[0]
+          bank=S2410_Addr2Bank(addr);
+          i = addr + len;
+          j = 0;
+          printf("Please wait...\n");
+          sprintf(filename,"dump_%X_%X",addr,len);
+          fp = fopen(filename,"wb");
+          if(fp==NULL){
+            printf("Failed to open %s for writing!\n",filename);
+            break;
+          }
+          while(addr < i){
+            if (j != ((addr>>8)&1)){
+              j = ((addr>>8)&1);
+              printf("[%8x]",addr);
+              fflush(fp);
+            }
+            //error=MRW_RdByte(addr++,&dataByte);
+            error=MRW_RdHW(addr,&dataHW);
+            addr+=2;
+            if(error!=0)
+              printf("?? ");
+            else{
+              //printf("%04x ",dataHW);
+              fputc(dataHW&0xff,fp);
+              fputc((dataHW>>8)&0xff,fp);
+            }
+            //printf(".");
+          }
+          printf("\nDumped to %s\n",filename);
+          fclose(fp);
+          break;
+        }
            scanf("%x",&addr);
            addr=addr&0xfffffff0;
            bank=S2410_Addr2Bank(addr);
-           for(i=0;i<4;i++)
+        for(i=0;i<8;i++)
            {
                printf("%8x:",addr);
                for(j=0;j<16;j++)

其实我还打算加入烧写后自动为block加上写保护的功能的,不过由于现在不能碰那块板,于是只好放弃……反正新版本的ppcboot也可以方便地进行写保护嘛……


四.PPCBoot & Terminal software:
前面说了,ppcboot可以用loadb命令,通过串口以kermit协议下载.bin文件(但要注意那该死的base设置无效的bug)。由于windows自带的超级终端显示缓存的内容经常会乱掉,所以一直用的是Tera Term。但通过Tera Term把kernel下载到sdram中运行却怎么也跑不起来。经过观察,发现Tera term通过kermit传文件居然会丢包,于是后来只好改回超级终端,虽然传输速率比Tera Term低,但是却不会发生丢包的现象。

现在,至少有3种烧写flash的方法了:jtag、ppcboot、进入linux后写mtd设备
* jtag仅在丢失引导后重新烧写ppcboot使用,因为比较慢。
* 能够进入ppcboot后,就可以通过loadb从串口接收kernel和initrd(因为底板没有网口,不然就用tftp了),分别把它们放到30008 000和3080 0000的sdram空间(sdram接的片选是nGSC6,因此地址空间为0x3000 0000~0x37FF FFFF,具
体说明可以在华恒的手册上查到),然后bootm或者go 30008000就可以启动kernel进入系统。
* 进入系统后,如果kernel正常,可以通过u盘把jffs2和cramfs两个分区的img文件字节烧入相应flash分区。过程很简单:先umount掉/dev/mtdblock/4和/dev/mtdblock/5的挂载,然后把img文件复制到tmpfs中,再dd if=xxx.img of=/dev/mtd/4这样的命令烧写,烧写完后就可以重新挂载了。不过这里有个小问题,那块板最后两个分区默认下分别是jffs2和cramfs,如果用其它分区格式烧写,再挂载的时候,系统仍然会把它们识别成jffs2和cramfs(跟fstab无关的),必须用-t参数手动指定分区格式才能正常挂载。还有,mtdblock/4之前的分区都是不可写的,这意味着不能在linux下面通过写这几个分区来烧写ppcboot、kernel和ramdisk,应该能通过修改kernel去除这一限制(flash分区表是写在kernel里的,那么应该也可以通过kernel来让mtd分区只读)。

现在不知道是ppcboot还是kernel的问题,在默认情况下插入u盘会提示"usb.c: USB device not accepting new address",而启动后马上进入ppcboot,然后cp 1040000 30008000 e0000,cp 1140000 30800000 210000把kernel和ramdisk装入内存(ramdisk一定也要在内存中。另外,好玩的是,reset键复位并不会清除sdram的内容,所以cp之前可以先md一下看看内存中之前复制过去的ramdisk是否还健在——kernel基本上是不在的了,进入系统后30008000的内容就变了),接着go 30008000或者bootm进入系统才可以正常使用u盘。

另外,ppcboot在fc4下编译会出错,在华恒的论坛上,有人指出
在ppcboot-2.0.0/tools/gdb/astest.c
找到下面代码

printf("\tcooked_size=%ld, raw_size=%ld, output_offset=%ld\n",
(long)sect->_cooked_size , (long)sect->_raw_size,
(long)sect->output_offset);
改成
printf("\tcooked_size=%ld, raw_size=%ld, output_offset=%ld\n",
(long)sect->size, (long)sect->rawsize,
(long)sect->output_offset);

看了一下/usr/includ/bfd.h,应该就是2.6的typedef struct bfd_section定义改变了的原因。


五.Kernel:
kernel方面没进行太多的研究,所以笔记不多:
* USB Support那里的Maximum port(s) of RootHub要改成1,不然kernel会像疯子一样不停地抱怨连不上其它的port。
* General Setup的 Compressed boot loader in ROM/flash,选上之后似乎ppcboot就boot不了编译出来的kernel了(未确认,似乎而已,很可能那次boot不了是因为其它原因,只有等以后有机会并且可以用tftp传kernel的时候再进一步确认了。)
* 音频方面,Kernel那里只选上了OSS sound modules,声音驱动实际上是进入系统后用insmod /usr/2410audio.o另外加载的。其实很多设备的驱动都是进入系统后才挂的,可以参考华恒的手册。
* flash分区表在/drivers/mtd/maps/s3c2410_llg.c文件,可能可以通过修改这个文件来设置分区是否只读。