diff --git a/pkg/mmio/mmio.go b/pkg/mmio/mmio.go
new file mode 100644
index 0000000..c0b7ac2
--- /dev/null
+++ b/pkg/mmio/mmio.go
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2021, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package mmio
+
+import (
+	"fmt"
+	"os"
+	"syscall"
+	"unsafe"
+
+	"gitlab.com/nvidia/cloud-native/go-nvlib/pkg/bytes"
+)
+
+type Mmio interface {
+	bytes.Raw
+	bytes.Reader
+	bytes.Writer
+	Sync() error
+	Close() error
+	Slice(offset int, size int) Mmio
+	LittleEndian() Mmio
+	BigEndian() Mmio
+}
+
+type mmio struct {
+	bytes.Bytes
+}
+
+func open(path string, offset int, size int, flags int) (Mmio, error) {
+	var mmap_flags int
+	switch flags {
+	case os.O_RDONLY:
+		mmap_flags = syscall.PROT_READ
+	case os.O_RDWR:
+		mmap_flags = syscall.PROT_READ | syscall.PROT_WRITE
+	default:
+		return nil, fmt.Errorf("invalid flags: %v\n", flags)
+	}
+
+	file, err := os.OpenFile(path, flags, 0)
+	if err != nil {
+		return nil, fmt.Errorf("failed to open file: %v\n", err)
+	}
+	defer file.Close()
+
+	fi, err := file.Stat()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get file info: %v\n", err)
+	}
+
+	if size > int(fi.Size()) {
+		return nil, fmt.Errorf("requested size larger than file size")
+	}
+
+	if size < 0 {
+		size = int(fi.Size())
+	}
+
+	mmap, err := syscall.Mmap(
+		int(file.Fd()),
+		int64(offset),
+		size,
+		mmap_flags,
+		syscall.MAP_SHARED)
+	if err != nil {
+		return nil, fmt.Errorf("failed to mmap file: %v\n", err)
+	}
+
+	return &mmio{bytes.New(&mmap)}, nil
+}
+
+func OpenRO(path string, offset int, size int) (Mmio, error) {
+	return open(path, offset, size, os.O_RDONLY)
+}
+
+func OpenRW(path string, offset int, size int) (Mmio, error) {
+	return open(path, offset, size, os.O_RDWR)
+}
+
+func (m *mmio) Slice(offset int, size int) Mmio {
+	return &mmio{m.Bytes.Slice(offset, size)}
+}
+
+func (m *mmio) LittleEndian() Mmio {
+	return &mmio{m.Bytes.LittleEndian()}
+}
+
+func (m *mmio) BigEndian() Mmio {
+	return &mmio{m.Bytes.BigEndian()}
+}
+
+func (m *mmio) Close() error {
+	err := syscall.Munmap(*m.Bytes.Raw())
+	if err != nil {
+		return fmt.Errorf("failed to munmap file: %v\n", err)
+	}
+	return nil
+}
+
+func (m *mmio) Sync() error {
+	_, _, errno := syscall.Syscall(
+		syscall.SYS_MSYNC,
+		uintptr(unsafe.Pointer(&(*m.Bytes.Raw())[0])),
+		uintptr(m.Len()),
+		uintptr(syscall.MS_SYNC|syscall.MS_INVALIDATE))
+	if errno != 0 {
+		return fmt.Errorf("failed to msync file: %v", errno)
+	}
+	return nil
+}
diff --git a/pkg/mmio/mmio_test.go b/pkg/mmio/mmio_test.go
new file mode 100644
index 0000000..9c1ece5
--- /dev/null
+++ b/pkg/mmio/mmio_test.go
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2021, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package mmio
+
+import (
+	"encoding/binary"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestMmioRead(t *testing.T) {
+	source := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
+
+	type testCase struct {
+		description string
+		offset      int
+	}
+	testCases := func() []testCase {
+		var testCases []testCase
+		for i := 0; i < len(source)/2; i++ {
+			tc := testCase{
+				fmt.Sprintf("offset: %v", i),
+				i,
+			}
+			testCases = append(testCases, tc)
+		}
+		return testCases
+	}()
+
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+			mmio, err := MockOpenRO(&source, 0, len(source))
+			require.Nil(t, err, "Unexpected error from OpenRO")
+
+			defer func() {
+				err = mmio.Close()
+				require.Nil(t, err, "Unexpected error from Close")
+			}()
+
+			r8 := mmio.Read8(tc.offset)
+			require.Equal(t, r8, source[tc.offset])
+
+			r16 := mmio.Read16(tc.offset)
+			require.Equal(t, r16, binary.LittleEndian.Uint16(source[tc.offset:tc.offset+2]))
+
+			r32 := mmio.Read32(tc.offset)
+			require.Equal(t, r32, binary.LittleEndian.Uint32(source[tc.offset:tc.offset+4]))
+
+			r64 := mmio.Read64(tc.offset)
+			require.Equal(t, r64, binary.LittleEndian.Uint64(source[tc.offset:tc.offset+8]))
+		})
+	}
+}
+
+func TestMmioWrite(t *testing.T) {
+	source := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
+
+	type testCase struct {
+		description string
+		offset      int
+	}
+	testCases := func() []testCase {
+		var testCases []testCase
+		for i := 0; i < len(source)/2; i++ {
+			tc := testCase{
+				fmt.Sprintf("offset: %v", i),
+				i,
+			}
+			testCases = append(testCases, tc)
+		}
+		return testCases
+	}()
+
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+			mmio, err := MockOpenRW(&source, 0, len(source))
+			require.Nil(t, err, "Unexpected error from OpenRW")
+
+			defer func() {
+				err = mmio.Close()
+				require.Nil(t, err, "Unexpected error from Close")
+			}()
+
+			r8 := mmio.Read8(tc.offset)
+			mmio.Write8(tc.offset, (1<<8)-1)
+			require.Equal(t, r8, source[tc.offset])
+			r8 = mmio.Read8(tc.offset)
+			require.Equal(t, r8, uint8((1<<8)-1))
+			mmio.Sync()
+			require.Equal(t, r8, source[tc.offset])
+			mmio.Write8(tc.offset, uint8(tc.offset))
+			mmio.Sync()
+
+			r16 := mmio.Read16(tc.offset)
+			mmio.Write16(tc.offset, (1<<16)-1)
+			require.Equal(t, r16, binary.LittleEndian.Uint16(source[tc.offset:tc.offset+2]))
+			r16 = mmio.Read16(tc.offset)
+			require.Equal(t, r16, uint16((1<<16)-1))
+			mmio.Sync()
+			require.Equal(t, r16, binary.LittleEndian.Uint16(source[tc.offset:tc.offset+2]))
+			mmio.Write8(tc.offset+0, uint8(tc.offset+0))
+			mmio.Write8(tc.offset+1, uint8(tc.offset+1))
+			mmio.Sync()
+
+			r32 := mmio.Read32(tc.offset)
+			mmio.Write32(tc.offset, (1<<32)-1)
+			require.Equal(t, r32, binary.LittleEndian.Uint32(source[tc.offset:tc.offset+4]))
+			r32 = mmio.Read32(tc.offset)
+			require.Equal(t, r32, uint32((1<<32)-1))
+			mmio.Sync()
+			require.Equal(t, r32, binary.LittleEndian.Uint32(source[tc.offset:tc.offset+4]))
+			mmio.Write8(tc.offset+0, uint8(tc.offset+0))
+			mmio.Write8(tc.offset+1, uint8(tc.offset+1))
+			mmio.Write8(tc.offset+2, uint8(tc.offset+2))
+			mmio.Write8(tc.offset+3, uint8(tc.offset+3))
+			mmio.Sync()
+
+			r64 := mmio.Read64(tc.offset)
+			mmio.Write64(tc.offset, (1<<64)-1)
+			require.Equal(t, r64, binary.LittleEndian.Uint64(source[tc.offset:tc.offset+8]))
+			r64 = mmio.Read64(tc.offset)
+			require.Equal(t, r64, uint64((1<<64)-1))
+			mmio.Sync()
+			require.Equal(t, r64, binary.LittleEndian.Uint64(source[tc.offset:tc.offset+8]))
+			mmio.Write8(tc.offset+0, uint8(tc.offset+0))
+			mmio.Write8(tc.offset+1, uint8(tc.offset+1))
+			mmio.Write8(tc.offset+2, uint8(tc.offset+2))
+			mmio.Write8(tc.offset+3, uint8(tc.offset+3))
+			mmio.Write8(tc.offset+4, uint8(tc.offset+4))
+			mmio.Write8(tc.offset+5, uint8(tc.offset+5))
+			mmio.Write8(tc.offset+6, uint8(tc.offset+6))
+			mmio.Write8(tc.offset+7, uint8(tc.offset+7))
+			mmio.Sync()
+		})
+	}
+}
diff --git a/pkg/mmio/mock.go b/pkg/mmio/mock.go
new file mode 100644
index 0000000..bf760e5
--- /dev/null
+++ b/pkg/mmio/mock.go
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2021, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package mmio
+
+import (
+	"fmt"
+
+	"gitlab.com/nvidia/cloud-native/go-nvlib/pkg/bytes"
+)
+
+type mockMmio struct {
+	mmio
+	source *[]byte
+	offset int
+	rw     bool
+}
+
+func mockOpen(source *[]byte, offset int, size int, rw bool) (Mmio, error) {
+	if size < 0 {
+		size = len(*source) - offset
+	}
+	if (offset + size) > len(*source) {
+		return nil, fmt.Errorf("offset+size out of range")
+	}
+
+	data := append([]byte{}, (*source)[offset:offset+size]...)
+
+	m := &mockMmio{}
+	m.Bytes = bytes.New(&data).LittleEndian()
+	m.source = source
+	m.offset = offset
+	m.rw = rw
+
+	return m, nil
+}
+
+func MockOpenRO(source *[]byte, offset int, size int) (Mmio, error) {
+	return mockOpen(source, offset, size, false)
+}
+
+func MockOpenRW(source *[]byte, offset int, size int) (Mmio, error) {
+	return mockOpen(source, offset, size, true)
+}
+
+func (m *mockMmio) Close() error {
+	m = &mockMmio{}
+	return nil
+}
+
+func (m *mockMmio) Sync() error {
+	if !m.rw {
+		return fmt.Errorf("opened read-only")
+	}
+	for i := range *m.Bytes.Raw() {
+		(*m.source)[m.offset+i] = (*m.Bytes.Raw())[i]
+	}
+	return nil
+}