powerpc e500 内核中断系统有两部分组成一个是e500 的 内核,一个是中断异常控制器programmable interrupt controller (PIC) interrupt protocol
e500 内核有些特殊之处是:在e500 内核进入中断和异常处理程 序时不能关闭mmu 也就是说e500内核 所看到的是虚拟地址。E500 内核的解决办法是利用IVPR 和 IVOR 寄存器来共同决定中断程序的入 口地址,IVPR 保存中断入口程序的0~15 位 IVOR 保存16~27 位 28~31 位为0 ;(每一个IVOR 对应 一种 中断, 如:IVOR4 对应着外部中断)。此外PIC 控制器还有 Interrupt Source Configuration Registers 用来区分是具体的中断如外部中断一有EIVPR1 和 EIDR1 共同决定
0--------------------------------------------------------------------------------- à31
MSK A RSV1 P S PRIORITY VECTIOR
0 1 2~7 8 9 12~`15 16~`31
其中vector 位为硬件中断号(不同于datasheet 种 的中断号)。其他个相关位见datasheet
当e500 int 信号有效是期铜外部中断处理,外部中断处理根据具体的硬件中断号执行 具体本中断的处理程序。俺也不知道处于啥原因linux 又弄了个软中断号这样你在申请中断(request_irq 是应该使用的是软中断号)。为此系统必须建立软中断号与硬中断号之间的联系。建立联系 之后你就可以通过硬件中断号向系统要软件中断号了然后request_irq 了。参见irq_of_parse_and_map函数或者干脆就直接调用irq_create_mapping( 不 知道会不会有问题) !
与之相关的中断调用如下 (arch/powerpc/kernel/head_fsl_booke.S)
#define SET_IVOR(vector_number, vector_label) /
li r26,vector_label@l; /
mtspr SPRN_IVOR##vector_number,r26; /
sync
SET_IVOR(0, CriticalInput);
SET_IVOR(1, MachineCheck);
SET_IVOR(2, DataStorage);
SET_IVOR(3, InstructionStorage);
SET_IVOR(4, ExternalInput);
SET_IVOR(5, Alignment);
SET_IVOR(6, Program);
SET_IVOR(7, FloatingPointUnavailable);
SET_IVOR(8, SystemCall);
SET_IVOR(9, AuxillaryProcessorUnavailable);
SET_IVOR(10, Decrementer);
SET_IVOR(11, FixedIntervalTimer);
SET_IVOR(12, WatchdogTimer);
SET_IVOR(13, DataTLBError);
SET_IVOR(14, InstructionTLBError);
SET_IVOR(15, DebugCrit);
以外部中断处理为例: SET_IVOR(4, ExternalInput);
当外部中断发生时根据 IVOR4 和 IVPR 中的地址会导致调用 ExternalInput
( arch/powerpc/kernel/head_fs_booke.S ) - à
/* External Input Interrupt */
EXCEPTION(0x0500, ExternalInput, do_IRQ, EXC_XFER_LITE)
#define EXCEPTION(n, label, hdlr, xfer) /
START_EXCEPTION(label); /
NORMAL_EXCEPTION_PROLOG; /
addi r3,r1,STACK_FRAME_OVERHEAD; /
xfer(n, hdlr)
#define EXC_XFER_LITE(n, hdlr) /
EXC_XFER_TEMPLATE(hdlr, n+1, MSR_KERNEL, NOCOPY, transfer_to_handler, /
ret_from_except)
#define EXC_XFER_TEMPLATE(hdlr, trap, msr, copyee, tfer, ret) /
li r10,trap; /
stw r10,_TRAP(r11); /
lis r10,msr@h; /
ori r10,r10,msr@l; /
copyee(r10, r9); /
bl tfer; /
.long hdlr; /
.long ret
首先, trap
加 载到寄存器 r10
中。 在接下来的一行中,那个值存储在由 TRAP(r11)
给 出的地址中。TRAP(r11)
以 及接下来两行去做一些硬件相关的位操作。然后,我们调用 tfer
函 数( transfer_to_handler
函数),它会处理更多内部事务并将控制转交给 hdlr
( do_IRQ )。注意, transfer_to_handler
通 过链接寄存器加载处理程序的地址,因此您看到的是 .long do_IRQ ,而不是 bl
do_IRQ 。
这个宏完成一些必要的设置后导致 do_IRQ 的调用!
(transfer_to_handler 参见arch/powerpc/kernel/entry_32.S )
void do_IRQ(struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
unsigned int irq;
irq_enter();
check_stack_overflow();
irq = ppc_md.get_irq();/*这个东西返回传说中的软件中断号,可见申请中断要用 软件中断号而你在寄存器EIPVRn里读出来的是硬件中断号,呵呵,终于给irq_create_mapping 找个说法了*/
if (irq != NO_IRQ && irq != NO_IRQ_IGNORE)
handle_one_irq(irq);
else if (irq != NO_IRQ_IGNORE)
/* That's not SMP safe ... but who cares ? */
ppc_spurious_interrupts++;
irq_exit();
set_irq_regs(old_regs);
#ifdef CONFIG_PPC_ISERIES
if (firmware_has_feature(FW_FEATURE_ISERIES) &&
get_lppaca()->int_dword.fields.decr_int) {
get_lppaca()->int_dword.fields.decr_int = 0;
/* Signal a fake decrementer interrupt */
timer_interrupt(regs);
}
#endif
}
在uboot阶段,没有挂载中断,接收通过轮询来实现的,所以发送和接收这两个过程跟Linux内核中有区别。
在发送阶段,网口将被启动,发送函数首先找到一个可用的Buffer Descriptor,将上层软件组好的包的地址赋给该BD的指针,置相应的标志位和长度,然后通知DMA来搬运。搬运结束后,发送函数会清除相应的BD标识位。DMA将数据从内存搬运到Tx FIFO后, MAC会给其加上数据链路层的首部后通过GMII口发送到PHY层。
在接收阶段,硬件会检测TSECn_RX_DV和TSECn_COL信号,并会检查有效的preamble,若找到,则检查MAC地址,校验等等,若都合格,则剥掉链路层的包头后,塞给Rx FIFO,DMA会将其搬到现在一个有效的Rx BD中,我们的接收程序会轮询该Buffer Descriptor,直到它有数据时,便将数据提交到上层,然后清除BD的一些状态位。
static int tsec_send(struct eth_device *dev, volatile void *packet, int length)
{
int i;
int result = 0;
struct tsec_private *priv = (struct tsec_private *)dev->priv;
volatile tsec_t *regs = priv->regs;
/*找一块空的Buffer Descriptor*/
for (i = 0; rtx.txbd[txIdx].status & TXBD_READY; i++) {
if (i >= TOUT_LOOP) {
printf("%s: tsec: tx buffers full/n", dev->name);
return result;
}
}
rtx.txbd[txIdx].bufPtr = (uint) packet;
rtx.txbd[txIdx].length = length;
rtx.txbd[txIdx].status |=
(TXBD_READY | TXBD_LAST | TXBD_CRC | TXBD_INTERRUPT);
/* 通过设置寄存器让DMA来从BD中搬运到FIFO中 */
regs->tstat = TSTAT_CLEAR_THALT;
/* 等到BD搬运完成,清除标志位*/
for (i = 0; rtx.txbd[txIdx].status & TXBD_READY; i++) {
if (i >= TOUT_LOOP) {
printf("%s: tsec: tx error/n", dev->name);
return result;
}
}
txIdx = (txIdx + 1) % TX_BUF_CNT;
result = rtx.txbd[txIdx].status & TXBD_STATS;
return result;
}
static int tsec_recv(struct eth_device *dev)
{
int length;
struct tsec_private *priv = (struct tsec_private *)dev->priv;
volatile tsec_t *regs = priv->regs;
while (!(rtx.rxbd[rxIdx].status & RXBD_EMPTY)) {
length = rtx.rxbd[rxIdx].length;
/* 有数据来时,检测BD的status,如果没有报错,就扔给上层协议栈 */
if (!(rtx.rxbd[rxIdx].status & RXBD_STATS)) {
NetReceive(NetRxPackets[rxIdx], length - 4);
} else {
printf("Got error %x/n",
(rtx.rxbd[rxIdx].status & RXBD_STATS));
}
rtx.rxbd[rxIdx].length = 0;
/* 如果是最后一个BD就设置W位 */
rtx.rxbd[rxIdx].status =
RXBD_EMPTY | (((rxIdx + 1) == PKTBUFSRX) ? RXBD_WRAP : 0);
rxIdx = (rxIdx + 1) % PKTBUFSRX;
}
if (regs->ievent & IEVENT_BSY) {
regs->ievent = IEVENT_BSY;
regs->rstat = RSTAT_CLEAR_RHALT;
}
return -1;
}
Uboot下的网口驱动就这么多内容。
Linux 网络驱动设备模型
Linux网络设备模型如下图,从上到下可以划分为4层,分别是网络协议接口层,网络设备接口层,设备驱动功能层和网络设备媒介层。
网络协议接口层向网络层协议提供统一的数据包收发接口,通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接收数据。
网络设备结构层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device。
设备驱动功能层各函数是网络设备结构层net_device数据结构的具体成员,通过hard_start_xmit()函数发送,通过中断触发接收函数。
网络设备媒介层就是完成数据包发送和接收的物理实体。
套接字缓冲区
套接字缓冲区 sk_buff的结构体非常重要,用于在Linux网络子系统中的各层之间传递数据,是Linux网络子系统数据传递的“中枢神经”。
当发送数据时,Linux内核的网络处理模块必须建立一个包含要传输的数据包sk_buff,然后将sk_buff递交给下层,各层在sk_buff中添加不同的协议头直至交给网络设备发送。同样,当网络设备从网络媒介上接收数据包后,它必须将接收到的数据转化为sk_buff数据结构并传递给上层,各层剥去相应的协议头,直至交给用户。
Skb有四个指针,head和end分别指向数据缓冲区的启始地址和结尾地址,而data和tail分别指向有效数据的开始地址和结尾地址。
Skb的操作有:alloc_skb()分配一个套接字缓冲区和一个数据缓冲区;Kree_skb进行套接字缓冲区的释放;skb_push()将data指针上移,主要用于添加协议头部;skb_pull将data指针下移,用于剥去头部。