ECE 391 / DISCUSSION 6
memio and timer
GRAINGER ENGINEERING
Announcements
ELECTRICAL & COMPUTER ENGINEERING
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.
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
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);
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.
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.
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.
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
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
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.
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.
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
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.
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".
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
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.
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)