diff --git a/src/gopheros/kernel/kfmt/ringbuf.go b/src/gopheros/kernel/kfmt/ringbuf.go new file mode 100644 index 0000000..2bd5baa --- /dev/null +++ b/src/gopheros/kernel/kfmt/ringbuf.go @@ -0,0 +1,65 @@ +package kfmt + +import "io" + +// ringBufferSize defines size of the ring buffer that buffers early Printf +// output. Its default size is selected so it can buffer the contents of a +// standard 80*25 text-mode console. The ring buffer size must always be a +// power of 2. +const ringBufferSize = 2048 + +// ringBuffer models a ring buffer of size ringBufferSize. This buffer is used +// for capturing the output of Printf before the tty and console systems are +// initialized. +type ringBuffer struct { + buffer [ringBufferSize]byte + rIndex, wIndex int +} + +// Write writes len(p) bytes from p to the ringBuffer. +func (rb *ringBuffer) Write(p []byte) (int, error) { + for _, b := range p { + rb.buffer[rb.wIndex] = b + rb.wIndex = (rb.wIndex + 1) & (ringBufferSize - 1) + if rb.rIndex == rb.wIndex { + rb.rIndex = (rb.rIndex + 1) & (ringBufferSize - 1) + } + } + + return len(p), nil +} + +// Read reads up to len(p) bytes into p. It returns the number of bytes read (0 +// <= n <= len(p)) and any error encountered. +func (rb *ringBuffer) Read(p []byte) (n int, err error) { + switch { + case rb.rIndex < rb.wIndex: + // read up to min(wIndex - rIndex, len(p)) bytes + n = rb.wIndex - rb.rIndex + if pLen := len(p); pLen < n { + n = pLen + } + + copy(p, rb.buffer[rb.rIndex:rb.rIndex+n]) + rb.rIndex += n + + return n, nil + case rb.rIndex > rb.wIndex: + // Read up to min(len(buf) - rIndex, len(p)) bytes + n = len(rb.buffer) - rb.rIndex + if pLen := len(p); pLen < n { + n = pLen + } + + copy(p, rb.buffer[rb.rIndex:rb.rIndex+n]) + rb.rIndex += n + + if rb.rIndex == len(rb.buffer) { + rb.rIndex = 0 + } + + return n, nil + default: // rIndex == wIndex + return 0, io.EOF + } +} diff --git a/src/gopheros/kernel/kfmt/ringbuf_test.go b/src/gopheros/kernel/kfmt/ringbuf_test.go new file mode 100644 index 0000000..0f153bd --- /dev/null +++ b/src/gopheros/kernel/kfmt/ringbuf_test.go @@ -0,0 +1,96 @@ +package kfmt + +import ( + "bytes" + "io" + "testing" +) + +func TestRingBuffer(t *testing.T) { + var ( + buf bytes.Buffer + expStr = "the big brown fox jumped over the lazy dog" + rb ringBuffer + ) + + t.Run("read/write", func(t *testing.T) { + rb.wIndex = 0 + rb.rIndex = 0 + n, err := rb.Write([]byte(expStr)) + if err != nil { + t.Fatal(err) + } + + if n != len(expStr) { + t.Fatalf("expected to write %d bytes; wrote %d", len(expStr), n) + } + + if got := readByteByByte(&buf, &rb); got != expStr { + t.Fatalf("expected to read %q; got %q", expStr, got) + } + }) + + t.Run("write moves read pointer", func(t *testing.T) { + rb.wIndex = ringBufferSize - 1 + rb.rIndex = 0 + _, err := rb.Write([]byte{'!'}) + if err != nil { + t.Fatal(err) + } + + if exp := 1; rb.rIndex != exp { + t.Fatalf("expected write to push rIndex to %d; got %d", exp, rb.rIndex) + } + }) + + t.Run("wIndex < rIndex", func(t *testing.T) { + rb.wIndex = ringBufferSize - 2 + rb.rIndex = ringBufferSize - 2 + n, err := rb.Write([]byte(expStr)) + if err != nil { + t.Fatal(err) + } + + if n != len(expStr) { + t.Fatalf("expected to write %d bytes; wrote %d", len(expStr), n) + } + + if got := readByteByByte(&buf, &rb); got != expStr { + t.Fatalf("expected to read %q; got %q", expStr, got) + } + }) + + t.Run("with io.WriteTo", func(t *testing.T) { + rb.wIndex = ringBufferSize - 2 + rb.rIndex = ringBufferSize - 2 + n, err := rb.Write([]byte(expStr)) + if err != nil { + t.Fatal(err) + } + + if n != len(expStr) { + t.Fatalf("expected to write %d bytes; wrote %d", len(expStr), n) + } + + var buf bytes.Buffer + io.Copy(&buf, &rb) + + if got := buf.String(); got != expStr { + t.Fatalf("expected to read %q; got %q", expStr, got) + } + }) +} + +func readByteByByte(buf *bytes.Buffer, r io.Reader) string { + buf.Reset() + var b = make([]byte, 1) + for { + _, err := r.Read(b) + if err == io.EOF { + break + } + + buf.Write(b) + } + return buf.String() +}