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:
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! */; }
This is nothing special. It simply turns the network card on, and lets it start receiving packets.
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
#define
d 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!
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.
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.
Can't get your network driver to work? Did you...
rx_base
to the physical address of the start of the ring buffer?rx_capacity
to an appropriate value?dma_ring_slot
s?dma_base
to the physical address of the start of the designated space for each dma_ring_slot
?dma_len
to an appropriate value for each dma_ring_slot
?keyboard.c
)?interrupt_handler
to handle network interrupts?