1 of 18

ECE 391 / DISCUSSION 6

memio and timer

2 of 18

​

  • MP3 Team Form due today
  • Exam 1 Conflict form due today
  • MP2cp4 due Friday (remember style points)
  • Late Waiver
  • Exam Review session Sat (2pm-4pm)

GRAINGER ENGINEERING

Announcements

ELECTRICAL & COMPUTER ENGINEERING

3 of 18

struct io and struct iointf review

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

03

Three fields of shared state, one table of function pointers

The object

struct io {

const struct iointf * intf;

unsigned int blksz;

unsigned int refcnt;

};

intf

the dispatch table (vtable)

blksz

every length is a multiple of this

refcnt

lifetime, not ownership

The interface

struct iointf {

const char * implname;

void (*reclaim)(struct io*);

long (*read) (struct io*, void*, long);

long (*write) (struct io*, const void*, long);

long (*fetch) (struct io*, ull pos, void*, long);

long (*store) (struct io*, ull pos, const void*, long);

int (*ioctl) (struct io*, int op, void * arg);

};

A NULL slot means "not supported" and the io.c wrapper returns -ENOTSUP.

4 of 18

The Operation Set

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

04

Two families of data transfer, plus one escape hatch

Operation

Meaning

Position

memio

ioread / iowrite

sequential; advances as you go

implicit

borrowed from seekio

iofetch / iostore

random access at an explicit pos

you pass it

implemented natively

ioctl

out-of-band queries and settings

n/a

GETEND, plus GETPOS / SETPOS

ioaddref / iodropref

reference counting and teardown

n/a

reclaim frees the object

The wrapper validates so the driver does not have to

long iofetch(struct io * io, unsigned long long pos, void * buf, long buflen) {

if (io->intf->fetch == NULL) return -ENOTSUP;

if (buflen < 0) return -EINVAL;

if (buflen != 0 && buflen < io->blksz) return -EINVAL;

if (pos % io->blksz != 0 || buflen % io->blksz != 0) return -EINVAL;

return io->intf->fetch(io, pos, buf, buflen); // your code

5 of 18

How You Implement One

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

05

Embed the base structure first, then cast back

Every implementation starts the same way

struct memio {

struct seekio base; // MUST be first

unsigned long long end;

void * buf;

void (*reclfn)(void*,size_t);

};

​

long memio_fetch(struct io * io, ...) {

struct memio * const mio = (struct memio*)io;

Two ways to initialize

ioinit(&uart->io, &uart_intf,

/* blksz */ 1, /* refcnt */ 0);

​

seekio_init(&mio->base, &memio_intf,

/* blksz */ 1, /* refcnt */ 1);

6 of 18

Four Drivers, One Interface

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

07

The same seven function pointers, filled in four different ways

Driver

Backed by

blksz

read / write

fetch / store

uart

16550 MMIO registers + a ring buffer

1

native

—

viorng

a VirtIO entropy queue

1

read only

—

vioblk

a VirtIO queue over ngfs.raw

512

from seekio

native

memio

a buffer that is already in RAM

1

from seekio

native

What is new

• No MMIO, no feature negotiation

• No interrupt, no ISR, never blocks

• fetch = one bounds check + one memcpy()

What is not new

• The interface and the refcount discipline

• The block-size rule and the error codes

• The seekio wrapper , all as in vioblk

memio is vioblk with the hardware deleted. Same shape, none of the latency.

7 of 18

An In-Memory Block Device

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

10

The whole driver is four fields and five functions

struct memio {

struct seekio base; // struct io + pos; must be first

unsigned long long end; // size in bytes == the answer to IOC_GETEND

void * buf; // borrowed pointer to the actual bytes

void (*reclfn)(void*,size_t); // optional: how to release buf

};

It borrows the buffer, it does not own it

reclfn == NULL

the buffer outlives the io; a blob in the image

nothing to free

reclfn != NULL

the buffer was allocated for this io; kmalloc, a page

called once, on the last iodropref()

create_memio() never copies your bytes. It stores the pointer you gave it.

8 of 18

The Blob: Grafting Bytes Into the Kernel

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

11

Two Makefile lines turn any file into a linkable section

BLOB_OBJCOPY_FLAGS = \

--add-section .data.blob=blob.raw \

--set-section-flags .data.blob=alloc,contents,load

​

blob.o:

echo .end | $(AS) $(ASFLAGS) -o blob.o # an empty object file

[ ! -f blob.raw ] || $(OBJCOPY) $(BLOB_OBJCOPY_FLAGS) $@

What each step does

1

Assemble nothing

echo .end (valid object file with no code and no data)

2

Graft the file in

--add-section copies blob.raw in verbatim as .data.blob

3

Mark it real

alloc,contents,load — the linker places it, the loader loads it

4

Stay optional

the [ ! -f ] guard builds fine with no blob.raw present

blob.raw can be any file — an ELF executable, a file system image, anything.

9 of 18

The Linker Script Hands You the Bounds

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

12

Two symbols in, one memio out

kernel.ld

.data : {

PROVIDE(_kimg_data_start = .);

PROVIDE(_kimg_blob_start = .);

*(*.data.blob)

PROVIDE(_kimg_blob_end = .);

*(.sdata .sdata.*)

...

} :data

main.c

extern char _kimg_blob_start[],

_kimg_blob_end[];

​

size_t size = _kimg_blob_end

- _kimg_blob_start;

​

struct io * mio =

create_memio(_kimg_blob_start, size, NULL);

Declare them as arrays

char _kimg_blob_start[] is the symbol’s address

char * _kimg_blob_start (WRONG)si a pointer read FROM it

The wrong one loads your blob’s first eight bytes and uses

them as an address.

Note the section: .data

• memio_store() writes through mio->buf

• .rodata → a read-only device

​

10 of 18

Backing Device and why Memio?

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

14

vioblk

a real block device

fetch / store → VirtIO descriptors → MMIO

blocks the thread; woken by an interrupt

bytes live in ngfs.raw, a disk image

memio

fetch / store → memcpy()

never blocks, never takes an interrupt

bytes live in the kernel image

struct io * iofetch() · iostore() · ioctl(io, IOC_GETEND, &end)

File system (MP3)

mount_ngfs(mpname, bkgio)

ELF loader (MP3)

elf_load(io, &ep)

Disk

RAM

11 of 18

struct seekio: Where the Position Lives

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

17

Three structures, one address

struct seekio {

struct io base;

unsigned long long pos;

};

struct memio {

struct seekio base;

unsigned long long end;

void * buf;

void (*reclfn)(void*,size_t);

};

struct io *

struct seekio *

struct memio *

all three are

the same address

struct io * seekio_init(struct seekio * sio,

const struct iointf * intf,

unsigned int blksz, unsigned int refcnt)

{

sio->pos = 0;

return ioinit(&sio->base, intf, blksz, refcnt);

}

The device stays stateless; seekio does all the sequential bookkeeping.

12 of 18

seekio_read: A Fetch at the Current Position

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

18

Ask where the end is, clamp, fetch, advance

long seekio_read(struct io * io, void * buf, long bufsz) {

struct seekio * const sio = (struct seekio*)io;

​

if (io->intf->fetch == NULL) return -ENOTSUP;

​

result = ioctl(io, IOC_GETEND, &end); // -> memio_ioctl()

if (result < 0) return result;

​

if (sio->pos == end) return 0; // EOF, not an error

if (sio->pos > end) return -EINVAL;

​

if (end - sio->pos < bufsz) // clamp near the end

bufsz = end - sio->pos;

​

retlen = io->intf->fetch(&sio->base, sio->pos, buf, bufsz);

if (retlen <= 0) return retlen;

​

assert (retlen == bufsz); // no partial fetches

sio->pos += retlen;

return retlen;

}

return 0 means EOF

pos == end. Per io.h: no more data, ever.

A short read is success

512 asked, 100 left → returns 100.

pos > end is reachable

IOC_SETPOS seeks past the end happily.

fetch is all-or-nothing

The assert forbids partial transfers.

13 of 18

memio_intf: The Dispatch Table

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

22

Seven slots: two borrowed, three written, two bookkeeping

static const struct iointf memio_intf = {

.implname = "memio",

.reclaim = &memio_reclaim,

.read = &seekio_read,

.write = &seekio_write,

.fetch = &memio_fetch,

.store = &memio_store,

.ioctl = &memio_ioctl

};

Bookkeeping

implname, reclaim

A name and the teardown hook

Borrowed

read, write

Generic seekio code — free with fetch/store

Yours

fetch, store, ioctl

~40 lines — the only device-specific code

14 of 18

create_memio

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

23

Allocate, fill in three fields, delegate the initialization

struct io * create_memio (

void * buf, size_t size,

void(*reclfn)(void*,size_t))

{

struct memio * mio;

​

assert (buf != NULL || size == 0);

mio = kcalloc(1, sizeof(*mio));

mio->buf = buf;

mio->end = size;

mio->reclfn = reclfn;

​

return seekio_init (

&mio->base, &memio_intf,

/* blksz */ 1, /* refcnt */ 1);

}

assert (buf != NULL || size == 0)

NULL only for an empty object

kcalloc, not kmalloc

Zeroes the struct — seekio_init only sets pos

end = size

Length and IOC_GETEND answer, in one field

blksz 1

memcpy() needs no alignment. vioblk: 512.

refcnt 1

The caller leaves holding it. Devices start at 0.

seekio_init() returns &mio->base, which is the address of the whole struct.

15 of 18

memio_reclaim

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

24

Called by iodropref(), never by you

void memio_reclaim(struct io * io) {

struct memio * const mio = (struct memio*)io;

​

if (mio->reclfn != NULL)

mio->reclfn(mio->buf, mio->end);

​

kfree(mio);

}

Two objects, two owners

struct memio

allocated by create_memio()

always kfree()d here

the buffer

handed in by the caller

only released via reclfn

Never call memio_reclaim() yourself — call iodropref()

and let the reference count decide.

reclfn == NULL means "this buffer is not mine to free".

16 of 18

memio_fetch and memio_store

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

25

The same function twice, with the memcpy arguments swapped

long memio_fetch(struct io * io, unsigned long long pos,

void * buf, long len) {

struct memio * const mio = (struct memio*)io;

​

assert (io != NULL);

assert (pos % io->blksz == 0); // ensured by iofetch()

assert (len % io->blksz == 0); // ensured by iofetch()

assert (0 <= len); // ensured by iofetch()

​

if (mio->end < pos || mio->end - pos < len)

return -EINVAL;

​

memcpy(buf, mio->buf + pos, len);

return len;

}

long memio_store(struct io * io, unsigned long long pos,

const void * buf, long len) {

struct memio * const mio = (struct memio*)io;

​

assert (io != NULL);

assert (pos % io->blksz == 0); // ensured by iostore()

assert (len % io->blksz == 0); // ensured by iostore()

assert (0 <= len); // ensured by iostore()

​

if (mio->end < pos || mio->end - pos < len)

return -EINVAL;

​

memcpy(mio->buf + pos, buf, len);

return len;

}

Why the bounds check is written that way

if (mio->end < pos || mio->end - pos < len) // safe

if (pos + len > mio->end) // wraps

• pos is unsigned long long — pos + len can wrap

• end < pos first proves pos <= end

• so the subtraction cannot underflow

17 of 18

memio_ioctl

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

26

Answer what only you can answer; delegate the rest

int memio_ioctl(struct io * io, int op, void * arg) {

struct memio * const mio = (struct memio*)io;

unsigned long long * const ullarg = arg;

​

switch (op) {

case IOC_GETPOS:

case IOC_SETPOS:

return seekio_ioctl(io, op, arg); // pos is seekio’s

​

case IOC_GETEND:

*ullarg = mio->end; // end is mine

return 0;

​

default:

return -ENOTSUP; // including SETEND

}

}

pos belongs to seekio. end belongs to you. Neither reaches into the other.

18 of 18

End to End: One ioread() on a memio

ELECTRICAL & COMPUTER ENGINEERING

GRAINGER ENGINEERING

27

ioread(mio, buf, 64) with pos = 100 and end = 140

1

ioread()

io.c

Checks refcnt and bufsz, rounds 64 down to a multiple of blksz (blksz is 1, so 64).

2

intf->read

dispatch

memio_intf.read is seekio_read — the borrowed slot.

3

seekio_read()

io.c

Calls ioctl(io, IOC_GETEND, &end) before moving a single byte.

4

memio_ioctl()

yours

*ullarg = mio->end → end = 140. Returns 0.

5

seekio_read()

io.c

end - pos is 40, which is less than 64, so bufsz is clamped to 40.

6

memio_fetch()

yours

Bounds check passes, then memcpy(buf, mio->buf + 100, 40). Returns 40.

7

seekio_read()

io.c

assert(retlen == bufsz), pos += 40 → pos is now 140. Returns 40.

The caller asked for 64 and got 40. A short read is success (check the return value)