Notes on network drivers

A lot of people have been asking about how to get their network drivers to work. It's our hope that you guys don't get stuck in a rut wrangling with the network driver for too long, because the primary goal of this project is to explore what you can do with multicore and synchronization. To remedy this, one of the course staff decided to start from scratch and make a network driver so that we can report to everyone what needs to be done to get this working.

I created network.h with the following function definitions:

// Initializes the network driver, allocating the space for the ring buffer.
void network_init();

// Starts receiving packets!
void network_start_receive();

// If opt != 0, enables interrupts when a new packet arrives.
// If opt == 0, disables interrupts.
void network_set_interrupts(int opt);

// Continually polls for data on the ring buffer. Loops forever!
void network_poll();

// Called when a network interrupt occurs.
void network_trap();

I created network.c, with implementations for these functions. More on that later.

In kernel.c, I made core 0 do the following if I wanted to use interrupts:

network_init();
network_set_interrupts(1);
network_start_receive();
// Do something useful down here; core 0 will get interrupted if a packet arrives.

Or instead the following if I wanted to use polling:

network_init();
network_set_interrupts(0);
network_start_receive();
network_poll();

In addition, if I wanted to use interrupts, I had to edit the function named interrupt_handler in kernel.c; right now this function only handles keyboard and timer interrupts. I added the capability to handle network interrupts as well.

A few notes on the functions in network.c and what they need to do, generally:

network_init()

The first order of business of this function is to find out which of the 16 possible devices is the network device. For this first portion, I would suggest looking at how keyboard.c does it, and emulating its code.

After you've done that, you can allocate the ring buffer for receiving packets. This ring buffer is basically an array of struct dma_ring_slot. If I decided I would make my ring buffer have 8 slots in it, I would do this:

// Somewhere at the top of network.c, where it's easily visible:
#define RING_SIZE 8

// Somewhere inside network_init():
struct dma_ring_slot* ring = (struct dma_ring_slot*) malloc(sizeof(struct dma_ring_slot) * RING_SIZE);

Remember that you must set the rx_base variable of your dev_net struct to be the physical address of the start of this array.

Furthermore, for every ring slot inside the ring buffer, you must allocate a suitably-sized buffer, store its physical address in the dma_base variable, and store the length of the buffer in the dma_len variable. This might look something like this, if I decided my buffer would be 1024 bytes long (a bad choice, but this is only an example!!!)

// Somewhere at the top of network.c:
#define BUFFER_SIZE 1024

// Somewhere inside network_init():
for (int i = 0; i < RING_SIZE; ++i) {
    void* space = malloc(BUFFER_SIZE);
    ring[i].dma_base = /* you figure this part out! */;
    ring[i].dma_len = /* you figure this part out! */;
}

network_start_receive()

This is nothing special. It simply turns the network card on, and lets it start receiving packets.

network_set_interrupts()

This function is also not terribly interesting.

One thing to note is that if you want to enable interrupts on the device, you must also enable them for your current core. For inspiration on how to do this, look to keyboard.c again for an example.

You should not need to do anything involving the #defined variables named STATUS_IE, STATUS_EXL, or STATUS_IM in sim.h. In fact, your kernel code should not even be #include-ing sim.h, as that file contains things that only the simulator should need to know about!

network_trap()

This function (or any function of your choice, really, depending on how you modify kernel.c) gets called in response to a network interrupt. Presumably, you handle a packet in here.

network_poll()

This function is a simple while loop that continually checks whether the ring buffer is non-empty (look to the documentation in machine.h if you don't know how to check this condition), and if it is non-empty, it handles the packets.

In actuality I just made network_trap() and network_poll() call a handle_packet() function that simply prints the string "I got a packet!", then increments rx_tail without actually doing anything to that packet. Obviously you'll want your packet handler to be a tad bit smarter...

Obviously, if you're dead set on either using interrupts or polling, you're totally free to only implement one of network_{trap,poll}. I suggest an interface like this for those who want to experiment with either using polling or interrupts to see which approach is superior.

Checklist

Can't get your network driver to work? Did you...