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 +}