Lwip是一个非常优秀的开源协议栈,本人成功地把它移植到两款ARM7芯片上,移植过程中使用了众多大虾的成果,在此感谢他们的无私奉献!俗话说滴水之恩当涌泉相报,我反CS8900A的驱动程序张贴出来,希望对大家有利用价值。
网上有不少CS8900A的驱动程序,但大多是使之工作于Memory模式,Memory模式占用较多的地址口,适用于有EMI接口的芯片,而对于没有
EMI接口的芯片(在低端嵌入式应用场合比较常见),IO模式因占用较少的地址线而得到广泛的应用。本驱动程序工作于16位IO模式,并在
Lwip1.1.1上能够很好地工作,底层操作系统采用uCOS-II。
/** @file:cs8900if0.c
*
* Ethernet network driver for CS8900A
*/
/*
*
* This is a device driver for the Crystal Semiconductor CS8900
* chip in combination with the lwIP stack.
*
* The Swedish Institute of Computer Science and Adam Dunkels
* are specifically granted permission to redistribute this
* source code under any conditions they seem fit.
*
* A quick function roadmap:
*
* cs8900a0_*() are low level, cs8900a hardware specific functions.
* These are declared static in the device driver source and
* SHOULD NOT need to be called from outside this source.
*
* cs8900if0_*() are the lwIP network interface functions.
*
* cs8900if0_rxIsr() is an early interrupt service routine (ISR).
* It merely post a semaphore to indicate the cs8900 needs servicing.
*
* cs8900if0_rxThread() is the actual interrupt event service routine.
* It must be called whenever the cs8900 signal a interrupt semaphore.
* It MAY be polled safely (so, you do NOT NEED interrupt support.)
* 把所有的接收工作都放在这个函数中是为了尽量使ISR简捷。
*
* cs8900a0_init() sets up the cs8900, using its register set. When
* using the driver on your particular hardware platform, make sure
* the register setups match.
* Function is called from cs8900if_init().
*
* cs8900a0_input() transfers a received packet from the chip.
* Function is called from cs8900if_input().
*
* cs8900a0_output() transfers a packet to the chip for transmission.
* Function is called from cs8900if_output().
*
* cs8900if0_init() initializes the lwIP network interface, and
* calls cs8900_init() to initialize the hardware.
* Function is called from lwIP.
*
* cs8900if0_input() calls cs8900_input() to get a received packet
* and then forwards the packet to protocol(s) handler(s).
* Function is called from cs8900_service().
*
* cs8900if0_output() resolves the hardware address, then
* calls cs8900_output() to transfer the packet.
* Function is called from lwIP.
*
* Future development:
*
* Split the generic Ethernet functionality (a lot of the
* cs8900if_*() functions) and the actual cs8900a dependencies.
*
* Enhance the interrupt handler to service the Ethernet
* chip (to decrease latency); support early packet
* inspection (during reception) to early drop unwanted
* packets, minimize chip buffer use and maximize throughput.
*
* Statistics gathering, currently under development.
* SNMP support, currently under development.
*
*/
/*
* 今天终于找出了取数据(DataAbort)出错的问题,原因是CS8900A的外部中断引起的。先用轮询法读取数据。
2007.2.24 千杯不醉
*/
/*
* cs8900a驱动程序,工作于16位I/O模式,采用中断方式收发数据
*
* INT1--------P0.3
* INT2--------P0.7
* IOR---------P1.19
* IOW---------P1.23
* A3-A1-------P1.18-P1.16
* SEL0--------P1.21 :active low
* SEL1--------P1.22
* D15-D10-----P0.30-P0.25
* D9-D0-------P0.23-P0.14
*
* 千杯不醉 2007.1.20
*
* 注意:本驱动中所有涉及到IO1SET = SEL1; //禁止CS8900A1
* IO1CLR = SEL0; //使能CS8900A0
* 的语句是因为双网卡的缘故.
*/
#include "lwip/debug.h"
#include "lwip/opt.h"
#include "lwip/def.h"
#include "lwip/mem.h"
#include "lwip/pbuf.h"
#include "lwip/stats.h"
#include "lwip/sys.h"
#include "netif/etharp.h"
#include "arch/sys_arch.h"
#include "netif/cs8900if.h"
/* Define those to better describe your network interface. */
#define IFNAME0 'e'
#define IFNAME1 '1'
//定义MAC地址
#define ETHADDR0 0x00
#define ETHADDR1 0x01
#define ETHADDR2 0x02
#define ETHADDR3 0x03
#define ETHADDR4 0x04
#define ETHADDR5 0x06
//cs8900a的配置信息,寄存器地址和写入值成对出现
static TInitSeq InitSeq[] =
{
{PP_IA, ETHADDR0 | (ETHADDR1 << 8)}, // set MAC Address
{PP_IA + 2, ETHADDR2 | (ETHADDR3 << 8)},
{PP_IA + 4, ETHADDR4 | (ETHADDR5 << 8)},
{PP_TestCTL, 0x0099}, //Test
Control:DisableLT XXX OBS
/*
* enable:receiver,transmitter
*/
{PP_LineCTL, 0x0013U | 0x0080U/*SerTxOn*/ | 0x0040U/*SerRxOn*/},
/*
* accept valid unicast or broadcast frames
*/
{PP_RxCTL, 0x0005U | 0x0800U | 0x0400U | 0x0100U},
{PP_RxCFG, 0x0003U | 0x0100U}, // enable receive interrupt
{PP_TxCFG, 0x0007U | 0}, // disable transmit interrupt (is default)
{PP_CS8900_ISAINT, 0x0000U}, // use interrupt number 0
/* generate interrupt event on:
- the RxMISS counter reaches 0x200, or
- a received frame is lost
*/
{PP_BufCFG, 0x000bU},
{PP_BusCTL, 0x0017U | 0x8000U}// enable interrupt generation
};
static struct netif *cs8900if0;
//向cs8900a内部寄存器地址写入一个16位半字,访问方式为小端模式
static void cs8900a0_write(u16_t addr, u16_t data)
{
u32_t addr32 = 0,data32 = 0;
addr32 = (u32_t)addr;
data32 = (u32_t)data;
/* CS8900的片先低有效 */
IO1SET = SEL1; //禁止CS8900A1
IO1CLR = SEL0; //使能CS8900A0
IO0DIR |= D15_10 | D9_0; // Data port to output
IO1SET = IOR | IOW | ((addr32<<15) & A3_1); // Put address on bus
IO1CLR = (~(addr32<<15)) & A3_1;
IO0SET = ((data32<<14) & D9_0) | ((data32<<15) & D15_10); //put 16bit data on data bus
IO0CLR = ((~(data32<<14)) & D9_0) | ((~(data32<<15)) & D15_10);
IO1CLR = IOW;
IO1SET = IOW;
}
// Reads a word in little-endian byte order from a specified port-address
static u16_t cs8900a0_read(u16_t addr)
{
u32_t value;
u16_t tmp;
u32_t addr32;
addr32 = (u32_t)addr;
/* CS8900的片先低有效 */
IO1SET = SEL1; //禁止CS8900A1
IO1CLR = SEL0; //使能CS8900A0
IO0DIR &= ~(D15_10 | D9_0); // data port to input
IO1SET = IOR | IOW | ((addr32<<15) & A3_1); // Put address on bus
IO1CLR = (~(addr32<<15)) & A3_1;
IO1CLR = IOR; // IOR-signal low
value = IO0PIN;
IO1SET = IOR;
tmp = (u16_t)(((value & D9_0)>>14) | ((value & D15_10)>>15));
return tmp;
}
/**----------------------------------------*
* 说明: SKIP_1:when set,this bit causes the last committed revieved frame to be
* deleted from the receive buffer.并且一置位就立即执行(Act at once)。
**---------------------------------------*/
static void cs8900a0_skip_frame(void)
{
//read RxStatus and RxLen
cs8900a0_read(RX_FRAME_PORT);
cs8900a0_read(RX_FRAME_PORT);
// skip received frame
cs8900a0_write(ADD_PORT,PP_RxCFG);
cs8900a0_write(DATA_PORT,cs8900a0_read(DATA_PORT) | 0x0040U/*Skip_1*/ );
}
static sys_sem_t rxSem0 = NULL;
/* 我建立了一个接收进程来处理网络数据包的接收,也可以用轮询的方式,前者更符合lwip的设计原则,并且
工作效率更高。此进程的优先级应该更高,不至于使数据丢失,但系统繁忙时可能需要更多的内存。
*
*/
void cs8900if0_rxThread(void *pdata)
{
u16_t irq_status = 0x0000U;
pdata = pdata;
/* Block for ever. */
rxSem0 = sys_sem_new(0);
while(1)
{
//接收到信号量
sys_sem_wait(rxSem0);
//读取8900的中断状态寄存器判断中断来源
irq_status = cs8900a0_read(ISQ_PORT);
/* ISQ interrupt event, and allowed to service in this loop? */
while (irq_status != 0x0000U)
{
/* investigate event */
if ((irq_status & 0x003fU) == 0x0004U/*Receiver Event*/)
{
/* correctly received frame, either broadcast or individual address */
/* TODO: think where these checks should appear: here or in cs8900_input() */
if ((irq_status & 0x0100U/*RxOK*/) &&
(irq_status & 0x0c00U/*Broadcast | Individual*/))
{
/* read the frame from the cs8900a */
cs8900if0_input(cs8900if0);
}
else cs8900a0_skip_frame();
}
/* read ISQ register */
irq_status = cs8900a0_read(ISQ_PORT);
}
}
}
/**
* cs8900a的接收中断服务程序
*
* 接收到数据包时触发,用信号量通知接收子进程把数据提交给上层。
*
*
*
* 千杯不醉 2007.1.30
*/
void cs8900if0_rxIsr(void)
{
//#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
// OS_CPU_SR cpu_sr = 0;
//#endif
// OS_ENTER_CRITICAL();
if((EXTINT & 0x02) == 0x02)
{
sys_sem_signal(rxSem0);
}
EXTINT |= 0x02; //清除中断源,电平模式下当电平无效时执行
VICVectAddr = 0; // 通知中断控制器中断结束
// OS_EXIT_CRITICAL();
}
// cs8900_init()
//
// initializes the CS8900A chip
static struct netif *
cs8900a0_init(struct netif *netif)
{
u8_t i = 0;
/* maximum transfer unit */
//以太网最大传输单元是1500B
netif->mtu = 1500;
/* broadcast capability */
netif->flags = NETIF_FLAG_BROADCAST;
/* hardware address length */
netif->hwaddr_len = 6;
/* make up an MAC address. */
netif->hwaddr[0] = (u8_t)ETHADDR0;
netif->hwaddr[1] = (u8_t)ETHADDR1;
netif->hwaddr[2] = (u8_t)ETHADDR2;
netif->hwaddr[3] = (u8_t)ETHADDR3;
netif->hwaddr[4] = (u8_t)ETHADDR4;
netif->hwaddr[5] = (u8_t)ETHADDR5;
/** 初始化cs8900a引脚配置和中断控制器
*******INT1***INT2****
*
* INT1--------P0.3
* INT2--------P0.7
*
*********GPIO*********
* IOR---------P1.19
* IOW---------P1.23
* A3-A1-------P1.18-P1.16
* SEL0--------P1.21 :active low
* SEL1--------P1.22
* D15-D10-----P0.30-P0.25
* D9-D0-------P0.23-P0.14
**************************
*/
PINSEL1 |= 0x00000000;
PINSEL2 |= 0x04;
//配置相关引脚为输出
IO0DIR |= D15_10 | D9_0;
IO1DIR |= IOR | IOW | SEL0 | SEL1 | A3_1;
IO1SET = SEL0 | SEL1 | IOR | IOW;
IO1SET = SEL1; //禁止CS8900A0
IO1CLR = SEL0; //使能CS8900A1
/**
* Reset the CS8900A chip-wide using a soft reset
*
* @note You MUST wait 30 ms before accessing the CS8900
* after calling this function.
*/
cs8900a0_write(ADD_PORT, PP_SelfCTL);
cs8900a0_write(DATA_PORT, POWER_ON_RESET);
/* 电平的跳变使CS8900A进入16位模式。After a hardware or a software reset,the CS8900A will be in 8-bit mode.Provide a HIGH
* to LOW and then LOW to HIGH transition on the /SBHE signal before any 16-bit IO or Memory access is done
* to the CS8900A.
*/
IO1SET = SEL0;
IO1CLR = SEL0;
IO1SET = SEL0;
IO1CLR = SEL0;
// Wait until chip-reset is done
cs8900a0_write(ADD_PORT, PP_SelfST);
// INITD bit still clear?
while ((cs8900a0_read(DATA_PORT) & INIT_DONE) == 0) ;
// Configure the CS8900A
for (i = 0; i < sizeof(InitSeq) / sizeof (TInitSeq); ++i)
{
cs8900a0_write(ADD_PORT, InitSeq[i].addr);
cs8900a0_write(DATA_PORT, InitSeq[i].data);
}
//返回此网络接口给一个全局变量,接收线程通过此网络接口传递数据
return netif;
}
/*-----------------------------------------------------------------------------------*/
/*
* cs8900a0_output():
* @return error code
* - ERR_OK: packet transferred to hardware
* - ERR_CONN: no link or link failure
* - ERR_IF: could not transfer to link (hardware buffer full?)
*
*
* Should do the actual transmission of the packet. The packet is
* contained in the pbuf that is passed to the function. This pbuf
* might be chained.
*
* 我想在数据接收和发送的过程中应该用信号量来保证数据的完整性。
*/
/*-----------------------------------------------------------------------------------*/
static err_t
cs8900a0_output(struct netif *netif, struct pbuf *p)
{
struct pbuf *q;
int tries = 0;
err_t result;
// exit if link has failed
cs8900a0_write(ADD_PORT,PP_LineST);
if ((cs8900a0_read(DATA_PORT) & 0x0080U/*LinkOK*/) == 0) return ERR_CONN; // no Ethernet link
result = ERR_OK;
// issue 'transmit' command to CS8900
cs8900a0_write(TX_CMD_PORT, TX_START_ALL_BYTES);
/* send length (in bytes) of packet to send, but at least minimum frame length */
cs8900a0_write(TX_LEN_PORT, (p->tot_len < MIN_PACKET_SIZE? MIN_PACKET_SIZE: p->tot_len));
cs8900a0_write(ADD_PORT,PP_BusST);
// not ready for transmission and still within 50 retries?
//如果当前缓冲区满不能发送,在50次后清空接收数据
while (((cs8900a0_read(DATA_PORT) & 0x0100U/*Rdy4TxNOW*/) == 0) && (tries++ < 50))
{
// throw away the last committed received frame
cs8900a0_skip_frame();
cs8900a0_write(ADD_PORT,PP_BusST);
}
//drop the padding word
#if ETH_PAD_SIZE
pbuf_header(p,-ETH_PAD_SIZE);
#endif
// ready to transmit?如果准备好则发送数据
if ((cs8900a0_read(DATA_PORT) & 0x0100U/*Rdy4TxNOW*/) != 0)
{
unsigned long sent_bytes = 0;
/* q traverses through linked list of pbuf's
* This list MUST consist of a single packet ONLY */
for (q = p; q != NULL; q = q->next)
{
u16_t i;
u16_t *ptr = (u16_t *)q->payload;
/* Send the data from the pbuf to the interface, one pbuf at a
* time. The size of the data in each pbuf is kept in the ->len
* variable.
可能有多个缓冲区中的数据要发送,我们争取全部把它们发送出去
*/
for (i = 0; i < q->len; i += 2)
{
/** TODO: this routine assumes 16-bit boundary pbufs... */
cs8900a0_write(TX_FRAME_PORT,*ptr++);
sent_bytes += 2;
}
}
/* provide any additional padding to comply with minimum Ethernet
* frame length (RFC10242)如果数据过短就补充0,达到以太网最小的数据包 */
while (sent_bytes < MIN_PACKET_SIZE)
{
cs8900a0_write(TX_FRAME_PORT,0x0000);
sent_bytes += 2;
}
}
else
{
// { not ready to transmit!? }
/* return not connected */
result = ERR_IF;
}
//reclaim the padding word
#if ETH_PAD_SIZE
pbuf_header(p,-ETH_PAD_SIZE);
#endif
#if LINK_STATS
lwip_stats.link.xmit++;
#endif /* LINK_STATS */
return result;
}
/*-----------------------------------------------------------------------------------*/
/*
* cs8900a0_input():
*
* Should allocate a pbuf and transfer the bytes of the incoming
* packet from the interface into the pbuf.
*
* Move a received packet from the cs8900 into a new pbuf.
*
* Must be called after reading an ISQ event containing the
* "Receiver Event" register, before reading new ISQ events.
*
* This function copies a frame from the CS8900A.
* It is designed failsafe:
* - It does not assume a frame is actually present.
* - It checks for non-zero length
* - It does not overflow the frame buffer
*/
/*-----------------------------------------------------------------------------------*/
static struct pbuf *
cs8900a0_input(struct netif *netif)
{
struct pbuf *p = NULL, *q = NULL;
u16_t len = 0;
u16_t i;
u16_t *ptr = NULL;
// read RxStatus.由于在cs8900if0_rxThread()已经对接收到的数据进行判断,故discard it以提高效率
cs8900a0_read(RX_FRAME_PORT);
// read RxLength
len = cs8900a0_read(RX_FRAME_PORT);
LWIP_DEBUGF(NETIF_DEBUG, ("cs8900_input: packet len %u\n", len));
// positive length?
if (len > 0)
{
// allocate a pbuf chain with total length 'len'
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
if (p != NULL)
{
#if ETH_PAD_SIZE
pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif
for (q = p; q != 0; q = q->next)
{
LWIP_DEBUGF(NETIF_DEBUG, ("cs8900_input: pbuf @%p
tot_len %u len %u\n", q, q->tot_len, q->len));
ptr = q->payload;
// TODO: CHECK: what if q->len is odd? we don't use the last byte?
for (i = 0; i < (q->len + 1) / 2; i++)
{
*ptr = cs8900a0_read(RX_FRAME_PORT);
ptr++;
}
}
#if ETH_PAD_SIZE
pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif
}
// could not allocate a pbuf
else
{
// skip received frame
// TODO: maybe do not skip the frame at this point in time?
cs8900a0_skip_frame();
len = 0;
#if LINK_STATS
lwip_stats.link.memerr++;
lwip_stats.link.drop++;
#endif /* LINK_STATS */
}
}
// length was zero
else
{
}
return p;
}
/**
* cs8900if0_input():
*
* Read a received packet from the CS8900.
*
* This function should be called when a packet is received by the CS8900
* and is fully available to read. It moves the received packet to a pbuf
* which is forwarded to the IP network layer or ARP module. It transmits
* a resulting ARP reply or queued packet.
*
* @param netif The lwIP network interface to read from.
*
* @internal Uses cs8900_input() to move the packet from the CS8900 to a
* newly allocated pbuf.
*
*
* This function should be called when a packet is ready to be read
* from the interface. It uses the function low_level_input() that
* should handle the actual reception of bytes from the network
* interface.
*
*/
/*-----------------------------------------------------------------------------------*/
void
cs8900if0_input(struct netif *netif)
{
struct eth_hdr *ethhdr;
struct pbuf *p;
/* move received packet into a new pbuf */
p = cs8900a0_input(netif);
/* no packet could be read */
if (p == NULL) {
/* silently ignore this */
return;
}
/* points to packet payload, which starts with an Ethernet header */
ethhdr = p->payload;
#if LINK_STATS
lwip_stats.link.recv++;
#endif /* LINK_STATS */
switch (htons(ethhdr->type)) {
/* IP packet? */
case ETHTYPE_IP:
/* update ARP table */
etharp_ip_input(netif, p);
/* skip Ethernet header */
pbuf_header(p, -sizeof(struct eth_hdr));
/* pass to network layer */
netif->input(p, netif);
break;
case ETHTYPE_ARP:
/* pass p to ARP module */
etharp_arp_input(netif, (struct eth_addr *)&(netif->hwaddr[0]), p);
break;
default:
pbuf_free(p);
p = NULL;
break;
}
}
/**
* cs8900if0_output():
*
* Writing an IP packet (to be transmitted) to the CS8900.
*
* Before writing a frame to the CS8900, the ARP module is asked to resolve the
* Ethernet MAC address. The ARP module might undertake actions to resolve the
* address first, and queue this packet for later transmission.
*
* @param netif The lwIP network interface data structure belonging to this device.
* @param p pbuf to be transmitted (or the first pbuf of a chained list of pbufs).
* @param ipaddr destination IP address.
*
* @return ERR_OK if the packet was sent or queued. There is no way to
* find out if a packet really makes it onto the network link.
*
* @internal It uses the function cs8900_input() that should handle the actual
* reception of bytes from the network interface.
*
* This function is called by the TCP/IP stack when an IP packet
* should be sent. It calls the function called low_level_output() to
* do the actual transmission of the packet.
*
*/
err_t
cs8900if0_output(struct netif *netif, struct pbuf *p,
struct ip_addr *ipaddr)
{
/* resolve hardware address, then send (or queue) packet */
return etharp_output(netif, ipaddr, p);
}
/**
* arp_timer.
*/
static void arp_timer(void *arg)
{
etharp_tmr();
sys_timeout(ARP_TMR_INTERVAL, (sys_timeout_handler)arp_timer, NULL);
}
//void cs8900a0_send_test(void);
/**
* cs8900if0_init():
* Initialize the CS8900 Ethernet MAC/PHY and its device driver.
*
* @param netif The lwIP network interface data structure belonging to this device.
* MAY be NULL as we do not support multiple devices yet.
*
* Should be called at the beginning of the program to set up the
* network interface. It calls the function low_level_init() to do the
* actual setup of the hardware.
*
*/
err_t
cs8900if0_init(struct netif *netif)
{
struct cs8900if *cs8900if;
//协议栈有自己的内存管理机制
cs8900if = mem_malloc(sizeof(struct cs8900if));
if (cs8900if == NULL)
{
LWIP_DEBUGF(NETIF_DEBUG, ("cs8900if0_init: out of memory\n"));
return ERR_MEM;
}
// initialize cs8900 specific interface state data pointer
netif->state = cs8900if;
netif->name[0] = IFNAME0;
netif->name[1] = IFNAME1;
netif->output = cs8900if0_output;
netif->linkoutput = cs8900a0_output;
//initialize cs8900 specific interface state field
cs8900if->state = NETIFUSED;
// intialize the cs8900a chip
cs8900if0 = cs8900a0_init(netif);
etharp_init();
sys_timeout(ARP_TMR_INTERVAL, arp_timer, NULL);
//cs8900a0_send_test();
return ERR_OK;
}
/*-----------------------------------------------------------------------------------*/
/**
* 这是一个不依赖于Lwip的驱动测试程序,发送一个ARP请求包。
*
*/
#if 0
static const u8_t arpdata[60] =
{
0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x01,0x02,0x03,0x04,0x06,0x08,0x06,0x00,0x01,
0x08,0x00,0x06,0x04,0x00,0x01,0x00,0x01,0x02,0x03,0x04,0x06,0xd2,0x1d,0x68,0x08,
0x00,0x00,0x00,0x00,0x00,0x00,0xd2,0x1d,0x68,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
void cs8900a0_send_test(void)
{
u16_t u;
// Transmit command
cs8900a0_write(TX_CMD_PORT, TX_START_ALL_BYTES);
cs8900a0_write(TX_LEN_PORT, 60);
// Maximum number of retries
u = 8;
for (;;)
{
// Check for avaliable buffer space
cs8900a0_write(ADD_PORT, PP_BusST);
if (cs8900a0_read(DATA_PORT) & READY_FOR_TX_NOW)
break;
if (u -- == 0)
return;
// No space avaliable, skip a received frame and try again
cs8900a0_skip_frame();
}
// Send uip_len bytes of header
for (u = 0; u < 60; u += 2)
{
cs8900a0_write(TX_FRAME_PORT,*(u16_t *)&arpdata[u]);
}
}
#endif