您的位置:首页 > 理论基础 > 计算机网络

RTEMS 4.9.5:QEMU MINI2440 BSP 中的网络驱动开发(下)

2011-03-26 12:08 295 查看
(原创文章,转载请注明出处,谢谢。)
驱动编译运行,呵呵,跑起来了,欣喜之余,要看看还有什么问题没有解决,还有什么吸取的经验。首先:
1.DM9000的特性没有完全支持;
* 没有操作DM9000的eeprom部分;
* 没有调试dm9000的phy部分;
* dm9000支持发送两个队列,我们只使用了一个;
* dm9000发送采取的是查询策略,而非中断策略。效率低下。

2.QEMU 仿真 DM9000 有些问题,尚未确认。
3.延时函数。
我们一个问题一个问题说:
DM9000的EEPROM的支持问题,由于不影响大局可以暂时放一样,但是听rickleaf和我交流时,所这一块仿真时有问题。即在QEMU MINI2440 上不能正常工作,目前还不能确认是QEMU的问题还是驱动本省的问题,这个问题姑且放一放。
DM9000的内部的PHY和DM9000的MAC层已经无缝的结合在一起,两个自动协商,不要对其做匹配,即MAC工作在100Mbps下,phy肯定也工作在100Mbps下,MAC工作在全双工下,phy也工作在全双工下。这不用烦心。我实际的读过phy的寄存器,不是很成功,可以考虑是QEMU的仿真问题。
DM9000支持的两个发送队列的问题,我写得驱动只用了一个,没有使用第二个。这个问题,动手做过实验。发现程序并不稳定,我参考了Linux的驱动,在QEMU上仿真时,一段时候后就完蛋了。由于没有实际的开发板验证,实在是不知道是代码自身的问题,还是QEMU的问题。以下是改成双缓冲的 发送代码,在 dm9000_softc_t中增加了两个字段,txPktCnt,用于表示当前队列中发送包的数量。txPktLen表示队列中第二包的长度。
/* Send packet */
void dm9000_sendpacket(dm9000_softc_t *sc, struct mbuf *m)
{
unsigned int length = 0;
rtems_interval tmo; /* Move data to DM9000 TX RAM */
DM9000_outb(DM9000_MWCMD, DM9000_IO);/* Prepare for TX-data */
length = (sc->outmbuf)(m); m_freem(m); /* Move data to DM9000 TX RAM */
DM9000_outb(DM9000_MWCMD, DM9000_IO); /* Prepare for TX-data */
/* push the data to the TX-fifo */
(sc->outblk)(packet, length);
sc->txPktCnt++;
if (sc->txPktCnt == 1)
{
/* Set TX length to DM9000 */
DM9000_iow(DM9000_TXPLL, length & 0xff);
DM9000_iow(DM9000_TXPLH, (length >> 8) & 0xff);
/* Issue TX polling command */
DM9000_iow(DM9000_TCR, TCR_TXREQ);/* Cleared after TX complete */
}
else
{
sc->txPktLen = length; /* wait for end of transmission */
tmo = rtems_clock_get_ticks_since_boot() + 5 * rtems_clock_get_ticks_per_second();
while ( !(DM9000_ior(DM9000_NSR) & (NSR_TX1END | NSR_TX2END)) ||
!(DM9000_ior(DM9000_ISR) & IMR_PTM) )
{
if (rtems_clock_get_ticks_since_boot() >= tmo)
{
printf("transmission timeout/n");
DM9000_iow(DM9000_ISR, IMR_PTM);
return;
}
/*rtems_task_wake_after(0); *//*yeild CPU to other task*/
}
DM9000_iow(DM9000_ISR, IMR_PTM);
/* Clear Tx bit in ISR */
sc->txPktCnt --;
if (sc->txPktCnt > 0)
{
/* Set TX length to DM9000 */
DM9000_iow(DM9000_TXPLL, (sc->txPktLen) & 0xff);
DM9000_iow(DM9000_TXPLH, ((sc->txPktLen) >> 8) & 0xff);
/* Issue TX polling command */
DM9000_iow(DM9000_TCR, TCR_TXREQ);
/* Cleared after TX complete */
}
}
DM9000_DBG("transmit done/n/n");
}

关于发送采用中断模式也是有同样的问题,我也做了实验,在QEMU上运行时也有不稳定的问题,但代码层面我的确找不到问题了。请高手们多多指正。我改来改去,发现查询方式最稳定,呵呵,那就用查询方式发布吧。
/* * Driver transmit daemon */
void dm9000_txDaemon (void *arg)
{
dm9000_softc_t *sc = (dm9000_softc_t *)arg;
struct ifnet *ifp = &sc->arpcom.ac_if;
struct mbuf *m; rtems_event_set events;
for (;;)
{
/* turn on TX interrupt, then wait for one */
rtems_bsdnet_event_receive(
START_TRANSMIT_EVENT,
RTEMS_EVENT_ANY | RTEMS_WAIT,
RTEMS_NO_TIMEOUT,
&events);
/* Get the next mbuf chain to transmit. */
IF_DEQUEUE(&ifp->if_snd, m);
if (!m)
{
ifp->if_flags &= ~IFF_OACTIVE;
continue;
}
dm9000_sendpacket(sc, m);
}
}

/* Send packet */
void dm9000_sendpacket(dm9000_softc_t *sc, struct mbuf *m)
{
unsigned int length = 0;
/* Move data to DM9000 TX RAM */
DM9000_outb(DM9000_MWCMD, DM9000_IO);
/* Prepare for TX-data */
length = (sc->outmbuf)(m);
m_freem(m);
/* Set TX length to DM9000 */
DM9000_iow(DM9000_TXPLL, length & 0xff);
DM9000_iow(DM9000_TXPLH, (length >> 8) & 0xff);
/* Issue TX polling command */
DM9000_iow(DM9000_TCR, TCR_TXREQ);
/* Cleared after TX complete */
}

/* interrupt handler */
rtems_isr dm9000_isr (rtems_vector_number v)
{
int status;
#if !defined(CONFIG_NET_POLL_CONTROLLER)
rEINTMASK |= 0x80;
#endif
DM9000_iow(DM9000_IMR, IMR_PAR);
status = DM9000_ior(DM9000_ISR);
DM9000_iow(DM9000_ISR, status);
if ((status & ISR_PRS))
{
rtems_event_send (softc.rxDaemonTid, START_RECEIVE_EVENT);
}
if ((status & ISR_PTS))
{
int txstat;
txstat = DM9000_ior(DM9000_NSR);
if (txstat & (NSR_TX1END | NSR_TX2END))
rtems_event_send (softc.txDaemonTid, START_TRANSMIT_EVENT);
}
DM9000_iow(DM9000_IMR, IMR_PAR | IMR_PRM | IMR_PTM);
#if !defined(CONFIG_NET_POLL_CONTROLLER)
rEINTPEND = 0x80;
ClearPending(BIT_EINT4_7);
rEINTMASK &= ~0x80;
#endif
}
最有关于一个udelay函数的问题,
udelay函数RTEMS没有提供该函数的实现,需要自己撰写。我就偷懒写了个for 循环的延迟。这样做是不对的。由于没有实际的硬件,QEMU执行ARM的代码完全不像实际那样。所以靠指令实现精确的1us的延迟,是不太可能。实际中使用应采用定时器精确测量多少条循环指令可以实现1us的延迟。这样才能做出较好的udelay来。

下面和大家深入地讨论一些问题:
查看代码,发现 mbuf 的相关宏 MGETHDR、MGET 使用 splimp()、splimp、splx()三个函数保护其临界区域。然而,在 rtems_bsdnet_internal.h 中这三个的宏定义为:
#define splnet() 0
#define splimp() 0
#define splx(_s) do { (_s) = 0; } while(0)

都是为空的。并且网络中的许多操作都是依靠这几个锁来实现临界区域的保护。这三个宏这么定义,显然什么保护都没有。照理说,不保护,那么在多任务情况下,那是得不到正确结果的,然而我们实际使用中却能得到正确结果,这又是为什么呢?
阅读RTEMS官方的文档 networking.pdf 可以找到答案。他们提供了一个 rtems_bsdnet_semaphore_obtain() 和 rtems_bsdnet_semaphore_release() 进行这个级别的互斥。用于解决网络内部的数据结构一致性的问题。

这里还有最后问题:为什么要这样设计呢?这样设计的优缺点在哪里?
这个问题,我想了很久,这个答案可能会仁者见仁智者见智,姑且抛砖引玉吧。这么设计首先不像uC/OS-II这样的操作系统也不像Linux这样的操作系统,动不动就关中断,我们都知道 RTOS 的最重要的一个性能指标就是实时性。其中对中断的响应延迟时间是个非常重要的指标。RTEMS这样的设计,最明显的就是不用关中断,即使关了中断,时间也非常的短,直接的好处就是响应中断延迟小。自然实时性就会好。

这么设计的优点就是实时性好。除去这个优点,我们看看缺点在哪里。首先,网络的接收、发送、协议栈的运转是三个任务。他们之间不能同时运行。这会对网络的性能有比较大的影响,尤其是我们的系统中有1个以上的网络驱动,假设有两个网口,那么就有5个任务。5个任务抢占调度,在一个网口有大数据量发送时,必然会影响第二个网口的性能。当然这也不是一成不变的,如果MAC支持DMA链,可以在DMA链上多放一些缓冲区,可以对网络瞬时来的数据做一定的缓冲。对性能也会有比较大的改善。但仅对于DM9000这种没有DMA的芯片来说,对性能多多少少还是有些影响吧,特别是对突发访问有影响。(我手头没有条件做测试,不然肯定会做个实验,在实际板子上验证我的思路。)

讲到这里,我想起来了,看到Linux里DM9000发送时,使用两个发送缓冲区,发送采用中断触发。看看他的临界区域的保护,两个开关中断的自旋锁之间竟然放着数据包的拷贝过程(蓝色字体标识的)。这个在实时操作系统中是难以想象的,关中断时间长不说,占用总线资源时间也长。这对RTOS来说是个噩梦。所以我考虑来考虑去还是用了查询方法,也就是基于实时性的考虑,虽然那样写在实际中DM9000的工作效率并不高。但可以使用一个队列元素,采用中断触发,作为一些性能改善。如果一些朋友有更好的建议,更好的解释和方法,恳请一定赐教!!!谢谢。

static int
dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
unsigned long flags;
board_info_t *db = netdev_priv(dev);

dm9000_dbg(db, 3, "%s:/n", __func__);

if (db->tx_pkt_cnt > 1)
return NETDEV_TX_BUSY;

spin_lock_irqsave(&db->lock, flags);

/* Move data to DM9000 TX RAM */
writeb(DM9000_MWCMD, db->io_addr);

(db->outblk)(db->io_data, skb->data, skb->len);
dev->stats.tx_bytes += skb->len;

db->tx_pkt_cnt++;
/* TX control: First packet immediately send, second packet queue */
if (db->tx_pkt_cnt == 1) {
/* Set TX length to DM9000 */
iow(db, DM9000_TXPLL, skb->len);
iow(db, DM9000_TXPLH, skb->len >> 8);

/* Issue TX polling command */
iow(db, DM9000_TCR, TCR_TXREQ); /* Cleared after TX complete */

dev->trans_start = jiffies; /* save the time stamp */
} else {
/* Second packet */
db->queue_pkt_len = skb->len;
netif_stop_queue(dev);
}

spin_unlock_irqrestore(&db->lock, flags);

/* free this SKB */
dev_kfree_skb(skb);

return 0;
}

(原创文章,转载请注明出处,谢谢。)
本文出自 “夜来听风雨” 博客,请务必保留此出处http://coolbacon.blog.51cto.com/7777407/1279990
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: