MIT 6.828 操作系统课程系列7 Networking#

中断和驱动#

看手册第5章

当设备需要和操作系统交互时,会产生一个中断,是trap的一种。在devintr()中处理。

驱动的执行可分”上/下”两部分。
上半部分由read/write之类的syscall发起,请求设备做一些io操作。然后等待设备结果。
设备完成操作触发一个中断,开始下半部分,即中断处理。

plic https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc

设备模拟#

https://www.qemu.org/docs/master/system/target-riscv.html
lab0中已有一些讨论。我们模拟的cpu是64-bit RISC-V。用qemu-system-riscv64命令启动。

列出machine类型

qemu-system-riscv64 -machine help
Supported machines are:
none                 empty machine
sifive_e             RISC-V Board compatible with SiFive E SDK
sifive_u             RISC-V Board compatible with SiFive U SDK
spike                RISC-V Spike Board (default)
spike_v1.10          RISC-V Spike Board (Privileged ISA v1.10)
spike_v1.9.1         RISC-V Spike Board (Privileged ISA v1.9.1)
virt                 RISC-V VirtIO board

我们用的是virt,也就是Generic Virtual Platform
https://www.qemu.org/docs/master/system/riscv/virt.html

qemu-system-riscv64 -device help可列出所有模拟设备。

可以看到name "e1000", bus PCI, alias "e1000-82540em", desc "Intel Gigabit Ethernet"

uart我不知道是哪个设备。页没有找到qemu的uart相关文档。可能只能从它代码里找信息。
qemu/qemu
qemu/qemu

可以看到这两个代码开头都有Copyright (c) 2017 SiFive, Inc.

memlayout.h中前半部分都是硬件相关的定义。

Advanced Core Local Interruptor(ACLINT)#

riscv/riscv-aclint

ACLINT是virt平台的一部分,定义了一批memory mapped设备。为每个cpu提供inter-processor中断(IPI)和timer功能。
前身是SiFive E31CLINTSiFive E31是riscv的一个实现。qemu的virt的相关代码应该也是SiFive贡献的。
CLINT提供了统一的IPI和timer的寄存器映射,没有提供supervisor-level IPI。
ACLINT在CLINT基础上变得更模块化。
ACLINT向回兼容CLINT。xv6只用到CLINT。直接看SiFive E31的文档。
https://static.dev.sifive.com/E31-RISCVCoreIP.pdf

见SiFive文档第8章
cpu0的mtimecmp映射0x2004000,每个占8字节。
mtime寄存器映射0x200BFF8。
所以代码有

// core local interruptor (CLINT), which contains the timer.
#define CLINT 0x2000000L
#define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 8*(hartid))
#define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot.

CLINT_MTIME可获取当前的tick数。CLINT_MTIMECMP(hartid)用来设置某个cpu的timer中断触发时间。

Platform-Level Interrupt Controller(PLIC)#

riscv/riscv-plic-spec
https://wiki.osdev.org/PLIC

  • PLIC的base映射在0x0c000000

  • 每个中断源都对应一个id。id为0时为无中断。id同时可以表示优先级,越小优先级越高。

  • 中断源从0x0c000000开始排,每4字节一个。最多可有1024个中断源。0x0c000000是第一个也就是0号,是特殊的,不实际使用。

  • 0x0c001000开始是Interrupt Pending。到0x0c001080一共128字节,1024bit正好对应1024个中断源。

  • 0x0c002000开始是一系列context,每个占0x80字节,1024bit。正好对应1024个中断源。每个bit配置对应中断源是否在此context开启。
    而一个hart分machine模式和supervisor模式两种配置。所以一个hart占0x80*2=0x100字节,2个context。

  • 一直到0x0c200000开始Priority threshold的配置,前4字节为threshold,5-8为claim/complete。
    之后为reserved,一整个占0x1000。同样分两种模式,一个hart占0x2000。

  • threshold作用是,只有当中断的优先级大于配置的threshold,plic才会实际触发这个中断。

中断数据流#

interrupt source发signal到gateway排队,gateway挨个处理,请求plic core
plic core进行interrupt notification通知目标,目标进行claim以获取中断源(哪个中断)。
处理完以后目标要通知plic已经完成。然后gateway开始请求处理下一个中断。

console流程#


// qemu puts UART registers here in physical memory.
#define UART0 0x10000000L
#define UART0_IRQ 10 // qemu的virt.h定义

// core local interruptor (CLINT), which contains the timer.
#define CLINT 0x2000000L
#define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 8*(hartid))
#define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot.

// qemu puts platform-level interrupt controller (PLIC) here.
#define PLIC 0x0c000000L
#define PLIC_PRIORITY (PLIC + 0x0)
#define PLIC_PENDING (PLIC + 0x1000)
#define PLIC_MENABLE(hart) (PLIC + 0x2000 + (hart)*0x100)
#define PLIC_SENABLE(hart) (PLIC + 0x2080 + (hart)*0x100)
#define PLIC_MPRIORITY(hart) (PLIC + 0x200000 + (hart)*0x2000)
#define PLIC_SPRIORITY(hart) (PLIC + 0x201000 + (hart)*0x2000)
#define PLIC_MCLAIM(hart) (PLIC + 0x200004 + (hart)*0x2000)
#define PLIC_SCLAIM(hart) (PLIC + 0x201004 + (hart)*0x2000)

plicinit(void)
{
    // set desired IRQ priorities non-zero (otherwise disabled).
    // 设置全局UART0中断源
    *(uint32*)(PLIC + UART0_IRQ*4) = 1;
}

// 每个hart进行配置
plicinithart(void)
{
    int hart = cpuid();
  
    // set enable bits for this hart's S-mode
    // 只用s模式。开启此cpu的uart中断。
    *(uint32*)PLIC_SENABLE(hart) = (1 << UART0_IRQ);
  
    // 只用s模式。threshold=0
    // set this hart's S-mode priority threshold to 0.
    *(uint32*)PLIC_SPRIORITY(hart) = 0;
}

// console初始化
#define CONSOLE 1
consoleinit
    uartinit
        // 基本就是对uart硬件进行各种配置
        // 见http://byterunner.com/16550.html

    // 设置回调
    devsw[CONSOLE].read = consoleread;
    devsw[CONSOLE].write = consolewrite;

// uart数据
struct spinlock uart_tx_lock;
#define UART_TX_BUF_SIZE 32
char uart_tx_buf[UART_TX_BUF_SIZE];
uint64 uart_tx_w;
uint64 uart_tx_r;

// console的数据
struct {
    struct spinlock lock;
  
    // input
    #define INPUT_BUF_SIZE 128
    char buf[INPUT_BUF_SIZE];
    uint r;  // Read index
    uint w;  // Write index
    uint e;  // Edit index
} cons;

// 都配置好后。系统运行sh。当按下键盘,触发中断。
usertrap
    devintr
        uint64 scause = r_scause();

        // 查riscv-privileged 4.1.8
        // 64bit的第一位为1表示是Interrupt
        // Exception Code 9,为Supervisor external interrupt
        if((scause & 0x8000000000000000L) &&
            (scause & 0xff) == 9){
            // this is a supervisor external interrupt, via PLIC.

            // irq indicates which device interrupted.
            // claim获取中断编号
            int irq = plic_claim();
                int hart = cpuid();
                int irq = *(uint32*)PLIC_SCLAIM(hart);

            if(irq == UART0_IRQ){
                uartintr();
                    // 进入uart驱动流程
                    // 当uart有输入,或者uart准备好接收更多的数据。都会进中断。
                    // 然后这里既要尝试读。又要尝试写。

                    // read and process incoming characters.
                    // 不断尝试从uart读一个字符,并抛给console。
                    while(1){
                        int c = uartgetc();
                            if(ReadReg(LSR) & 0x01){
                                #define Reg(reg) ((volatile unsigned char *)(UART0 + reg))
                                #define ReadReg(reg) (*(Reg(reg))) // 解地址

                                #define RHR 0
                                #define LSR 5                 // line status register
                                // 见http://byterunner.com/16550.html
                                // LSR为1说明有输入

                                // 此时读RHR得到键盘输入的字符
                                // input data is ready.
                                return ReadReg(RHR);
                            } else {
                                return -1; // 无输入
                            }


                        if(c == -1)
                            break; // 无输入

                        consoleintr(c);              // 处理得到的输入字符c
                            acquire(&cons.lock);     // 获取锁

                            // case字符
                            switch(c){
                                case C('P'):  // 特殊功能。打印进程列表
                                    procdump();
                                    break;
                                case C('U'):  // 特殊功能。命令行删除一行
                                    while(cons.e != cons.w &&
                                        // 更新buf。发数据给硬件。
                                        cons.buf[(cons.e-1) % INPUT_BUF_SIZE] != '\n'){
                                        cons.e--;
                                        consputc(BACKSPACE);
                                            // console发给uart一个字符

                                            if(c == BACKSPACE){ // 特殊的BACKSPACE。删除一个字符。
                                                // 这里为什么要删了做空格再删?
                                                uartputc_sync('\b');
                                                uartputc_sync(' ');
                                                uartputc_sync('\b');
                                                    push_off(); // 关中断

                                                    if(panicked){
                                                        for(;;)
                                                          ;
                                                    }

                                                    #define LSR_TX_IDLE (1<<5)
                                                    // LSR的bit5
                                                    // 0 = transmit holding register is full. 16550 will not accept any data for transmission.
                                                    // 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character.

                                                    // 如果设备不能接收数据了,就一直等。
                                                    // wait for Transmit Holding Empty to be set in LSR.
                                                    while((ReadReg(LSR) & LSR_TX_IDLE) == 0)
                                                    ;
                                                    WriteReg(THR, c); // 写THR就是给串口喂数据

                                                    pop_off(); // 开中断
                                            } else {
                                                uartputc_sync(c);
                                            }

                                    }
                                    break;
                                case C('H'): // Backspace
                                case '\x7f': // Delete key
                                    if(cons.e != cons.w){
                                        cons.e--;
                                        consputc(BACKSPACE); // 删除一个字符
                                    }
                                    break;
                                default:
                                    if(c != 0 && cons.e-cons.r < INPUT_BUF_SIZE){ // 不能超出buf
                                        c = (c == '\r') ? '\n' : c;

                                        // echo back to the user.
                                        // console发字符c发给uart
                                        consputc(c);

                                        // store for consumption by consoleread().
                                        // 更新buf
                                        cons.buf[cons.e++ % INPUT_BUF_SIZE] = c;

                                        if(c == '\n' || c == C('D') || cons.e-cons.r == INPUT_BUF_SIZE){
                                            // wake up consoleread() if a whole line (or end-of-file)
                                            // has arrived.
                                            cons.w = cons.e;
                                            wakeup(&cons.r);
                                        }
                                    }
                                    break;
                                }

                            release(&cons.lock); // 释放console锁

                    }

                    // 上面是尝试读uart
                    // 下面尝试写

                    // send buffered characters.
                    acquire(&uart_tx_lock);
                    uartstart();
                        // 不断尝试发数据给uart
                        while(1){
                            // uart_tx_w是buf的目前写入index,需要把buf的数据都发给uart
                            // uart_tx_r是uart目前读到的位置。
                            // 如果uart_tx_r追上uart_tx_w,说明uart已经读完了。否则要发数据非uart。
                            if(uart_tx_w == uart_tx_r){
                                // transmit buffer is empty.
                                return;
                            }
                            
                            // 如果设备不能接收数据了,可直接结束。等它能接收了,会再起中断的。
                            if((ReadReg(LSR) & LSR_TX_IDLE) == 0){
                                // the UART transmit holding register is full,
                                // so we cannot give it another byte.
                                // it will interrupt when it's ready for a new byte.
                                return;
                            }
                            
                            int c = uart_tx_buf[uart_tx_r % UART_TX_BUF_SIZE];
                            uart_tx_r += 1;
                            
                            // maybe uartputc() is waiting for space in the buffer.
                            wakeup(&uart_tx_r);
                            
                            // 发字符给uart
                            WriteReg(THR, c);
                          }

                    release(&uart_tx_lock);

            } else if(irq){
                printf("unexpected interrupt irq=%d\n", irq);
            }

            // the PLIC allows each device to raise at most one
            // interrupt at a time; tell the PLIC the device is
            // now allowed to interrupt again.
            if(irq)
                plic_complete(irq);
                    int hart = cpuid();

                    // 写入claim地址表示完成
                    *(uint32*)PLIC_SCLAIM(hart) = irq;

            return 1;
        }

printf流程#


#define CONSOLE 1

// init.c
main
    mknod("console", CONSOLE, 0); // init最开始就会用mknod创建一个device文件
        create(path, T_DEVICE, major, minor)

printf(const char *fmt, ...) // user空间
    va_start(ap, fmt);
    vprintf(1, fmt, ap); // 默认写到fd1
        putc(fd, c);     // 最终就是调putc写字符
            write(fd, &c, 1); // syscall
                sys_write     // kernel
                    filewrite(f, p, n)
                        if(f->type == FD_DEVICE) // 开始已经create为device类型。会走到这里回调
                            if(f->major < 0 || f->major >= NDEV || !devsw[f->major].write)
                                return -1;
                            ret = devsw[f->major].write(1, addr, n);
                                // 走consoleinit设置的回调consolewrite

                                consolewrite(int user_src, uint64 src, int n)
                                    int i;
                                    for(i = 0; i < n; i++){
                                        char c;
                                        if(either_copyin(&c, user_src, src+i, 1) == -1) // 从user取数据
                                            break;

                                        // 数据放到uart的buf并尝试写入uart
                                        uartputc(c);
                                            acquire(&uart_tx_lock);

                                            if(panicked){
                                                for(;;)
                                                  ;
                                            }
                                            while(uart_tx_w == uart_tx_r + UART_TX_BUF_SIZE){
                                                // buffer is full.
                                                // wait for uartstart() to open up space in the buffer.
                                                sleep(&uart_tx_r, &uart_tx_lock);
                                            }
                                            uart_tx_buf[uart_tx_w % UART_TX_BUF_SIZE] = c;
                                            uart_tx_w += 1;
                                            uartstart();
                                            release(&uart_tx_lock);
                                    }

                            return ret;

E1000流程#

这个lab要做一个网卡驱动。设备叫做E1000,是qemu的模拟设备,连接到qemu的模拟lan网络。
xv6会得到ip地址10.0.2.15。host的地址为10.0.2.2。

make server会起一个python的简单udp server。
此时另起模拟器运行nettests,会测试网络功能。现在要调通网络功能。


// nettests.c

uint16 dport = NET_TESTS_PORT;

ping(uint16 sport, uint16 dport, int attempts)
    int fd;
    char *obuf = "a message from xv6!";
    uint32 dst;

    // qemu写死的10.0.2.2。作为host的ip。
    dst = (10 << 24) | (0 << 16) | (2 << 8) | (2 << 0);

    fd = connect(dst, sport, dport) // syscall
        sys_connect
            // 获取ip和两个端口
            argint(0, (int*)&raddr);
            argint(1, (int*)&lport);
            argint(2, (int*)&rport);

            sockalloc(&f, raddr, lport, rport)
                // 各种分配和填数据

            fd=fdalloc(f)
                // 得到socket fd

            return fd

    write(fd, obuf, strlen(obuf) // syscall
        sys_write
            filewrite(struct file *f, uint64 addr, int n)
                if(f->type == FD_SOCK) // 走socket类型
                    ret = sockwrite(f->sock, addr, n);
                        m = mbufalloc(MBUF_DEFAULT_HEADROOM);

                        // copyin要发送的数据到kernel本地buf
                        copyin(pr->pagetable, mbufput(m, n), addr, n)

                        // 发送udp
                        net_tx_udp(m, si->raddr, si->lport, si->rport);
                            net_tx_ip(m, IPPROTO_UDP, dip);
                                net_tx_eth(m, ETHTYPE_IP);
                                    e1000_transmit(m)
                                        // 到这就需要我们写代码把m中的数据发给网卡


接下来需要学习E1000手册,查各种资料。

E1000手册
https://pdos.csail.mit.edu/6.828/2022/readings/8254x_GBe_SDM.pdf

PCI概念
https://en.wikipedia.org/wiki/Peripheral_Component_Interconnect

  • Peripheral Component Interconnect(PCI)

  • Peripheral Component Interconnect eXtended(PCI-X)

  • Peripheral Component Interconnect Express(PCIe)

  • 使用Direct Memory Access(DMA)技术直接和内存交互,不用映射寄存器。

  • Ethernet以太网,一种有线网络技术。协议为IEEE802.3,去IEEE网站注册下载2022版本。有7000页pdf。

packet的接收#

总的来说需要识别线路上的包,做地址过滤,把包存到FIFO,把数据转到host的接收buffer,更新receive descriptor

packet地址过滤#

过滤模式

  1. Exact Unicast/Multicast
    完全匹配

  2. Promiscuous Unicast
    接收所有unicast

  3. Multicast

  4. Promiscuous Multicast
    接收所有multicast

  5. VLAN
    接收所有vlan包

正常情况只接收”好的”包,即没有CRC/symbol/时序/长度等错误。

Receive Data Storage#

硬件支持7中buffer长度。
256B 512B 1024B 2048B 4096B 8192B 16384B

Receive Descriptor Format#

receive descriptor是一个数据结构,包含接收buffer的地址和其他一些包信息。

e1000_dev.h中的rx_desc

packet的发送#

  • 协议栈收到一块待发送数据

  • 协议栈根据MTU大小等信息算出发送完这块数据需要的包数量

  • 对于每个包

  • 准备Ethernet,IP,TCP/UDP头。

  • 协议栈对接设备驱动,请求发送包数据。

  • 驱动拿到frame然后请求设备

  • 设备通过DMA读数据

  • 设备完成后通过中断通知驱动,驱动再往上回复。

Transmit Descriptors#

描述发送数据的结构体。
e1000_dev.h中的tx_desc

PCI配置/网卡初始化#


// pci.c

pci_init()
{
    // we'll place the e1000 registers at this address.
    // vm.c maps this range.
    // e1000的内存映射。我们可以自己决定。
    uint64 e1000_regs = 0x40000000L;

    // qemu -machine virt puts PCIe config space here.
    // vm.c maps this range.

    // 见https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c
    // VIRT_PCIE_ECAM映射在0x30000000
    // vm初始化时会做映射
    // https://en.wikipedia.org/wiki/PCI_configuration_space

    // ECAM是pcie的配置方法
    // Enhanced Configuration Access Mechanism
    uint32  *ecam = (uint32 *) 0x30000000L;

    // 配置参考这个文档
    // https://wiki.qemu.org/images/f/f6/PCIvsPCIe.pdf
    // https://en.wikipedia.org/wiki/PCI_configuration_space
    // 也可直接看pcie文档。我找了pcie5.0文档。
    // 但是xv6代码实际走的PCI的CAM而不是PCIE的ECAM?
    // 而qemu中都显示的pcie。先不纠结。

    // https://wiki.osdev.org/PCI
  
    // look at each possible PCI device on bus 0.
    // 遍历device
    // dev占5bit,最多32个设备。
    // func占3bit,最多8个function。
    for(int dev = 0; dev < 32; dev++){
        int bus = 0;
        int func = 0;
        int offset = 0;
        uint32 off = (bus << 16) | (dev << 11) | (func << 8) | (offset);
        volatile uint32 *base = ecam + off;
        uint32 id = base[0];
        
        // 100e:8086 is an e1000
        // 设备型号的对应见网卡手册5.2

        // 互联网可查PCI设备
        // https://devicehunt.com/view/type/pci/vendor/8086/device/100E
        if(id == 0x100e8086){
            // command and status register.
            // bit 0 : I/O access enable
            // bit 1 : memory access enable
            // bit 2 : enable mastering

            // 网卡手册4.1
            // 写 Command Register。3个bit打开
            base[1] = 7;
            __sync_synchronize();

            // 6个Base Address
            // Base Address Registers(BAR)
            for(int i = 0; i < 6; i++){
                uint32 old = base[4+i];

                // writing all 1's to the BAR causes it to be
                // replaced with its size.
                base[4+i] = 0xffffffff;
                __sync_synchronize();

                base[4+i] = old;
            }

            // tell the e1000 to reveal its registers at
            // physical address 0x40000000.
            base[4+0] = e1000_regs;

            e1000_init((uint32*)e1000_regs);
                int i;

                initlock(&e1000_lock, "e1000");

                regs = xregs;

                // Reset the device
                // 13.4.20 Interrupt Mask Set/Read Register 关中断
                regs[E1000_IMS] = 0; // disable interrupts

                // 13.4.1 Device Control Register 主要配置
                regs[E1000_CTL] |= E1000_CTL_RST; // |= 0x00400000 22bit置1 SDP0_IODIR?

                // 再次关。没懂
                regs[E1000_IMS] = 0; // redisable interrupts
                __sync_synchronize();

                // tx_ring的定义。就是一个buffer的数组
                // #define TX_RING_SIZE 16
                // static struct tx_desc tx_ring[TX_RING_SIZE] __attribute__((aligned(16)));
                // static struct mbuf *tx_mbufs[TX_RING_SIZE];

                // 设置发送buffer
                // [E1000 14.5] Transmit initialization
                memset(tx_ring, 0, sizeof(tx_ring));
                for (i = 0; i < TX_RING_SIZE; i++) {
                    tx_ring[i].status = E1000_TXD_STAT_DD; // 3.3.3.2 初始化为发送完成状态
                    tx_mbufs[i] = 0;
                }

                // 13.4.36 Transmit Descriptor Base Address Low
                // 设置发送buffer
                regs[E1000_TDBAL] = (uint64) tx_ring;

                if(sizeof(tx_ring) % 128 != 0)
                    panic("e1000");

                // 3.4 Transmit Descriptor Ring Structure
                // 13.4.38 Transmit Descriptor Length
                // tx_ring的字节数
                regs[E1000_TDLEN] = sizeof(tx_ring);

                // 13.4.39 Transmit Descriptor Head
                // 硬件控制。tx ring中第一个在处理的buffer的offset

                // 13.4.40 Transmit Descriptor Tail
                // 软件层写入值,配置要发送的新数据。
                regs[E1000_TDH] = regs[E1000_TDT] = 0;

                // 设置接收buffer
                // [E1000 14.4] Receive initialization
                memset(rx_ring, 0, sizeof(rx_ring));
                for (i = 0; i < RX_RING_SIZE; i++) {
                    rx_mbufs[i] = mbufalloc(0);
                    if (!rx_mbufs[i])
                        panic("e1000");
                    rx_ring[i].addr = (uint64) rx_mbufs[i]->head;
                }

                // 13.4.25 Receive Descriptor Base Address Low
                // 设置接收buffer
                regs[E1000_RDBAL] = (uint64) rx_ring;

                if(sizeof(rx_ring) % 128 != 0)
                    panic("e1000");

                // 13.4.28 Receive Descriptor Head
                regs[E1000_RDH] = 0;

                // 13.4.29 Receive Descriptor Tail
                regs[E1000_RDT] = RX_RING_SIZE - 1;

                // 13.4.27 Receive Descriptor Length
                regs[E1000_RDLEN] = sizeof(rx_ring);

                // filter by qemu's MAC address, 52:54:00:12:34:56
                // 13.5.2 Receive Address Low
                // 13.5.3 Receive Address High
                regs[E1000_RA] = 0x12005452;
                regs[E1000_RA+1] = 0x5634 | (1<<31);

                // multicast table
                // 13.5.1 Multicast Table Array
                for (int i = 0; i < 4096/32; i++)
                    regs[E1000_MTA + i] = 0;

                // transmitter control bits.
                // 13.4.33 Transmit Control Register
                // 发送配置
                regs[E1000_TCTL] = E1000_TCTL_EN |  // enable
                E1000_TCTL_PSP |                  // pad short packets
                (0x10 << E1000_TCTL_CT_SHIFT) |   // collision stuff
                (0x40 << E1000_TCTL_COLD_SHIFT);

                // 13.4.34 Transmit IPG Register
                regs[E1000_TIPG] = 10 | (8<<10) | (6<<20); // inter-pkt gap

                // receiver control bits.
                // 13.4.22 Receive Control Register
                // 接收配置
                regs[E1000_RCTL] = E1000_RCTL_EN | // enable receiver
                E1000_RCTL_BAM |                 // enable broadcast
                E1000_RCTL_SZ_2048 |             // 2048-byte rx buffers
                E1000_RCTL_SECRC;                // strip CRC

                // ask e1000 for receive interrupts.
                // 13.4.30 Receive Delay Timer Register
                regs[E1000_RDTR] = 0; // interrupt after every received packet (no timer)

                // 13.4.31 Receive Interrupt Absolute Delay Timer
                regs[E1000_RADV] = 0; // interrupt after every packet (no timer)

                // 13.4.20 Interrupt Mask Set/Read Register
                // RXT0 
                // Sets mask for Receiver Timer Interrupt
                regs[E1000_IMS] = (1 << 7); // RXDW -- Receiver Descriptor Write Back

        }
    }
}

  • 看网卡手册3.4的发送ring 有一个buffer数组也就是ring buffer映射给硬件。head指向硬件正在处理的第一个buffer,tail指向硬件正在处理的最后一个buffer之后。

  • e1000_transmit中 直接读tail寄存器的值,就是ring buffer可用的index。如果有残余数据,free掉。
    把新的buffer和其他参数填好。tail寄存器+1。
    有错的话返回-1。

  • e1000来数据后触发中断,走e1000_recv。
    检查rx_ring,用net_rx()把数据传给协议栈。
    需要申请新的mbuf代替原buf,这样来新数据的话有地方存。
    rdt指向的是可取数据的前一格。rdh指向的是最后收到的数据之后一格。每次收一条数据,网卡把rdh+1。
    如果两者相等,buffer满,网卡不再收数据,造成丢数据。直到应用程序从rdt+1处取走一条数据,并把rdt+1,即空出一个buffer。

  • 具体参照hints

  • 总体看
    cpu0对pci初始化,初始化网卡硬件。
    然后每个cpu都能跑设备驱动。
    应用程序从某个进程开始,调syscall进内核态,走协议栈逻辑,产生协议数据,发给网卡硬件,网卡发到外部网络。
    网卡收到外部数据,反向走,产生中断,取出数据,发给协议栈解析,最后返到应用程序。

  • 网卡产生的中断发到哪个hart?
    看plic文档第3节
    The PLIC hardware only supports multicasting of interrupts, such that all enabled targets will receive interrupt notifications for a given active interrupt.
    只支持multicast,会通知所有打开该中断的hart。然后hart按规则必须进行claim,得到中断的具体信息。
    claim是原子操作,不会出现重复claim同一个中断,谁先claim到,谁就来处理。
    见plic文档第7节
    还可以通过配置项做各种策略。
    这样看来所有hart一样配置的话,默认可认为是随机挑选hart来执行设备中断。

  • 并行环境下,能正确地返到对应地应用程序。见sockrecvudp()等。

  • 并行环境下要考虑多个进程同时跑驱动流程。需要加锁。

  • 用户态或kernel态都可能发生中断。

  • 外部的多条数据发到网卡,填入多个buffer,可能只产生一个中断。

  • 多进程收发测试
    ring buffer有限,如果驱动收数据不及时,可造成网卡直接丢弃数据,测试失败。
    目前的理解本质上要么叫对方重发,要么直接加大buffer完事。