CS8900 U-boot 网卡驱动分析

来源:互联网 发布:无线网络优化薪资前景 编辑:程序博客网 时间:2024/06/05 08:21

【概述】
    本文描述了CS8900网卡在u-boot环境下的相关操作,包括初始化,数据发送/接收等过程的解析等。

【环境描述】

硬件平台:EBD2410开发板(S3C2410+CS8900A)
U-boot版本:V1.1.4

【其他描述】
在include/configs/smdk2410.h中,定义如下:
#define CONFIG_DRIVER_CS8900    1
#define CS8900_BASE    0x19000300
#define CS8900_BUS16    1

u-boot中对网卡驱动需要实现如下函数:
int eth_init (bd_t * bd);
int eth_rx (void);
eth_send (volatile void *packet, int length);
void eth_halt (void);

在此主要分析drivers/cs8900.c这个文件。

参考文档:
    CS8900 Datasheet 《CS8900A.pdf

【地址映射相关】
    CS8900默认工作于IO空间访问方式,其IO空间的首地址默认为0x300,而在u-boot中也是通过I/O空间对芯片进行访问,因此我们可以宏定义中,有#define CS8900_BASE 0x19000300

【通过I/O空间访问CS8900的内部寄存器】
    方法很简单,通过往PacketPage Pointer Port中写入寄存器的地址,其中最高位表示是否工作于自增模式;然后访问PacketPage Data Ports 0 and 1来进行读或写,相关代码如下所示:
    
static unsigned short get_reg (int regno)
{
    CS8900_PPTR = regno;
    return (unsigned short) CS8900_PDATA;
}


static void put_reg (int regno, unsigned short val)
{
    CS8900_PPTR = regno;
    CS8900_PDATA = val;
}
其中:
    
#define CS8900_PPTR   *(volatile CS8900_REG *)(CS8900_BASE+0x05*CS8900_OFF)
#define CS8900_PDATA  *(volatile CS8900_REG *)(CS8900_BASE+0x06*CS8900_OFF)

 

#ifdef CS8900_BUS16
  #define CS8900_OFF 0x02
......

 


 

【软件复位CS8900
    通过往SelfCTL寄存器的RESET位置一进行软复位,复位后CS8900A将进行内部一系列操作,此时不要对其进行任何访问操作,典型的时间为10ms,一段时间后,软件通过读取Self Status Register的INITD位来确定芯片是否复位操作完成。参考如下函数:
static void eth_reset (void)
{
    int tmo;
    unsigned short us;

    /* reset NIC */
    put_reg (PP_SelfCTL, get_reg (PP_SelfCTL) | PP_SelfCTL_Reset);

    /* wait for 200ms */
    udelay (200000);
    /* Wait until the chip is reset */

    tmo = get_timer (0) + 1 * CFG_HZ;
    while ((((us = get_reg_init_bus (PP_SelfSTAT)) & PP_SelfSTAT_InitD) == 0)
      && tmo < get_timer (0))
    /*NOP*/;
}

 


【对CS8900A的初始化】
    通过eth_init()函数进行,这个也是u-boot网络驱动中要求实现的函数:

int eth_init (bd_t * bd)
{

    /* verify chip id */
    if (get_reg_init_bus (PP_ChipID) != 0x630e) {
    printf ("CS8900 Ethernet chip not found?!/n");
    return 0;
    }

    eth_reset ();
    /* set the ethernet address */
    put_reg (PP_IA + 0, bd->bi_enetaddr[0] | (bd->bi_enetaddr[1] << 8));
    put_reg (PP_IA + 2, bd->bi_enetaddr[2] | (bd->bi_enetaddr[3] << 8));
    put_reg (PP_IA + 4, bd->bi_enetaddr[4] | (bd->bi_enetaddr[5] << 8));
    
    eth_reginit ();
    return 0;
}
    首先验证芯片ID,随后通过软件复位芯片(参看前面),随后初始化其MAC地址,最后完成对相关寄存器的初始化。

其中:
#define PP_IA        0x0158  /* Individual address (MAC) */

eth_reginit()初始化了哪些寄存器呢?我们看看:
static void eth_reginit (void)
{
    /* receive only error free packets addressed to this card */
    put_reg (PP_RxCTL, PP_RxCTL_IA | PP_RxCTL_Broadcast | PP_RxCTL_RxOK);
    /* do not generate any interrupts on receive operations */
    put_reg (PP_RxCFG, 0);
    /* do not generate any interrupts on transmit operations */
    put_reg (PP_TxCFG, 0);
    /* do not generate any interrupts on buffer operations */
    put_reg (PP_BufCFG, 0);
    /* enable transmitter/receiver mode */
    put_reg (PP_LineCTL, PP_LineCTL_Rx | PP_LineCTL_Tx);
}

 

PP_RxCTL(Receiver Control):用来设置接收包的类型。此刻我们设置她:
    a:仅能接收正确的包(CRC正确,且包大小大于64小于1518字节)
    b:目标地址匹配模式为IndibidualA模式(即不是IAHashA模式)
    c:能接收广播地址

 

PP_RxCFG(Receiver Configuration):用来设置接收到的帧如何传递给主机,以及能产生何种类型的中断。此刻我们设置她:
    a:正常接收模式(不使用DMA及Stream模式)
    b:接收到的数据中不包括CRC
    c:不产生任何中断

PP_TxCFG(Transmit Configuration):用来设置产生中断的情况。此刻我们设置她:
    不产生任何中断

PP_BufCFG(Buffer Configuration):设置有关跟buffer有关的中断类型。此刻我们设置她:
    不产生任何中断

PP_LineCTL(Line Control):设置相关物理层与MAC层的相关配置。此刻我们设置她:
    允许发送和接收状态

【数据包发送】
    数据包发送的过程如下:
    1:主机向TxCMD寄存器中写数,通知即将发送数据
    2:主机向TxLen寄存器中写数,告知要发送的数据长度
    3:主机查询Bus Status寄存器中的Rdy4TX NOW位,查看CS8900是否准备好
    4:若CS8900A已经准备好,主机开始不断向RTDATA寄存器写入数据长度的数据。
    5:主机通过查询Transimitter Event寄存器中的TxOK位,可知数据包是否发送完毕。

    函数体如下:
    
extern int eth_send (volatile void *packet, int length)
{
    volatile unsigned short *addr;
    int tmo;
    unsigned short s;

    retry:
    /* initiate a transmit sequence */
    CS8900_TxCMD = PP_TxCmd_TxStart_Full;
    CS8900_TxLEN = length;

    /* Test to see if the chip has allocated memory for the packet */
    if ((get_reg (PP_BusSTAT) & PP_BusSTAT_TxRDY) == 0) {
    /* Oops... this should not happen! */
    #ifdef DEBUG
    printf ("cs: unable to send packet; retrying.../n");
    #endif
    for (tmo = get_timer (0) + 5 * CFG_HZ; get_timer (0) < tmo;)
        /*NOP*/;
        eth_reset ();    //若没有准备好就进行软重启,有必要吗?
        eth_reginit ();
        goto retry;
    }

    /* Write the contents of the packet */
    /* assume even number of bytes */    
    for (addr = packet; length > 0; length -= 2)
        CS8900_RTDATA = *addr++;

    /* wait for transfer to succeed */
    tmo = get_timer (0) + 5 * CFG_HZ;
    while ((s = get_reg (PP_TER) & ~0x1F) == 0) {
        if (get_timer (0) >= tmo)
        break;
    }

    /* nothing */ ;
    if ((s & (PP_TER_CRS | PP_TER_TxOK)) != PP_TER_TxOK) {
        #ifdef DEBUG
        printf ("/ntransmission error %#x/n", s);
        #endif
    }

    return 0;
}

 

【数据包接收】
    网络数据包的接收处理过程应该是中断模式的,不过在u-boot中的这个驱动没有这样设计,而是做成了一个函数,当需要接收数据的时候调用这个函,若此时有网络数据则接收,没有则直接返回而已。
    数据接收的过程如下:
    1:查看Receiver Event中数据位RXOK,看是置为。若没有置为则返回0,表示此时没有网络数据要接收。
    2:连续两次读取RTDATA地址,获得接收状态以及数据长度。
    3:根据获取的长度,连续读取RTDATA地址,以获得接收到的数据。
    函数体如下所示:
    
extern int eth_rx (void)
{
    int i;
    unsigned short rxlen;
    unsigned short *addr;
    unsigned short status;

    status = get_reg (PP_RER);

    if ((status & PP_RER_RxOK) == 0)
    return 0;

    status = CS8900_RTDATA; /* stat */
    rxlen = CS8900_RTDATA; /* len */

    #ifdef DEBUG
    if (rxlen > PKTSIZE_ALIGN + PKTALIGN)
        printf ("packet too big!/n");
    #endif
    for (addr = (unsigned short *) NetRxPackets[0], i = rxlen >> 1; i > 0;
    i--)
        *addr++ = CS8900_RTDATA;
    if (rxlen & 1)
    *addr++ = CS8900_RTDATA;

    /* Pass the packet up to the protocol layers. */
    NetReceive (NetRxPackets[0], rxlen);

    return rxlen;
}

 

【Halt CS8900
此处实现的函数如下:
void eth_halt (void)
{
    /* disable transmitter/receiver mode */
    put_reg (PP_LineCTL, 0);

    /* "shutdown" to show ChipID or kernel wouldn't find he cs8900 ... */
    get_reg_init_bus (PP_ChipID);
}
原来比较简单,仅仅是禁止一起设置允许接收和发送的标志位而已。

 



【后续问题】
问题1:
    关于映射地址0x19000000的确定,以及通过MEM以及IO空间分别访问CS8900的方法。
    可能需要参考:
    1:ISA总线规范
    2:硬件原理图中的硬件连接
    3:S3C2410 Datasheet中memory region的划分
    4:AT91RM9200 Datasheet中对RAM/ROM设备的访问方法(2410的datasheet中貌似没有提及)

【疑问】
问题1:
    对某些寄存器的读访问采用的是get_reg_init_bus()函数,而其他则采用get_reg()函数,而这两个函数大体一致,但get_reg_init_bus()函数在前面加入了如下:
    
static unsigned short get_reg_init_bus (int regno)
{
    /* force 16 bit busmode */
    volatile unsigned char c;

    c = CS8900_BUS16_0;
    c = CS8900_BUS16_1;
    c = CS8900_BUS16_0;
    c = CS8900_BUS16_1;
    c = CS8900_BUS16_0;

    CS8900_PPTR = regno;
    return (unsigned short) CS8900_PDATA;
}

 

    前面一连串的c=XXX不知有何用,被Eric注释掉后,也能正常工作

问题2:
    在eth_init()函数中用以识别芯片型号的代码:
    
    if (get_reg_init_bus (PP_ChipID) != 0x630e) {
        printf ("CS8900 Ethernet chip not found?!/n");
        return 0;
    }

    其值0x630e在datasheet中无法找到,见Datasheet P41

 

问题3:<已解答>
    Address Filter Register有两个:
    1: Logical Address Filter (hash table)
    2: Individual Address(IEEE address)
    其中第二个需要设置,第一个干啥用的?

    <解答>
    参考Receive Control寄存器中关于目的地址匹配模式的设置,其匹配模式有两种,一种为IAHashA,另一种为IndividualA,两种匹配模式分别对应于这两个寄存器。

原创粉丝点击