1 /*
2  * Copyright 2018 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "lpc_nuvoton.hpp"
18 
19 #include "mapper_errors.hpp"
20 #include "window_hw_interface.hpp"
21 
22 #include <fcntl.h>
23 #include <sys/mman.h>
24 
25 #include <cerrno>
26 #include <cinttypes>
27 #include <cstdint>
28 #include <cstdio>
29 #include <memory>
30 #include <utility>
31 #include <vector>
32 
33 namespace ipmi_flash
34 {
35 using std::uint16_t;
36 using std::uint32_t;
37 using std::uint8_t;
38 
createNuvotonMapper(std::uint32_t regionAddress,std::uint32_t regionSize)39 std::unique_ptr<HardwareMapperInterface> LpcMapperNuvoton::createNuvotonMapper(
40     std::uint32_t regionAddress, std::uint32_t regionSize)
41 {
42     /* NOTE: Considered making one factory for both types. */
43     return std::make_unique<LpcMapperNuvoton>(regionAddress, regionSize);
44 }
45 
open()46 MemorySet LpcMapperNuvoton::open()
47 {
48     static constexpr auto devmem = "/dev/mem";
49 
50     mappedFd = sys->open(devmem, O_RDWR | O_SYNC);
51     if (mappedFd == -1)
52     {
53         throw MapperException("Unable to open /dev/mem");
54     }
55 
56     mapped = reinterpret_cast<uint8_t*>(sys->mmap(
57         0, memoryRegionSize, PROT_READ, MAP_SHARED, mappedFd, regionAddress));
58     if (mapped == MAP_FAILED)
59     {
60         sys->close(mappedFd);
61         mappedFd = -1;
62         mapped = nullptr;
63 
64         throw MapperException("Unable to map region");
65     }
66 
67     MemorySet output;
68     output.mappedFd = mappedFd;
69     output.mapped = mapped;
70 
71     return output;
72 }
73 
close()74 void LpcMapperNuvoton::close()
75 {
76     if (mapped)
77     {
78         sys->munmap(mapped, memoryRegionSize);
79         mapped = nullptr;
80     }
81 
82     if (mappedFd != -1)
83     {
84         sys->close(mappedFd);
85         mappedFd = -1;
86     }
87 }
88 
89 /*
90  * The host buffer address is configured by host through
91  * SuperIO. On BMC side the max memory can be mapped is 4kB with the caveat that
92  * first byte of the buffer is reserved as host/BMC semaphore and not usable as
93  * shared memory.
94  *
95  * Mapper returns success for (addr, len) where (addr & 0x7) == 4 and len <=
96  * (4096 - 4). Otherwise, mapper returns either
97  *   - WindowOffset = 4 and WindowSize = len - 4 if (addr & 0x7) == 0
98  *   - WindowSize = 0 means that the region cannot be mapped otherwise
99  */
100 WindowMapResult
mapWindow(std::uint32_t address,std::uint32_t length)101     LpcMapperNuvoton::mapWindow(std::uint32_t address, std::uint32_t length)
102 {
103     WindowMapResult result = {};
104 
105     /* We reserve the first 4 bytes from the mapped region; the first byte
106      * is shared semaphore, and the number of 4 is for alignment.
107      */
108     const uint32_t bmcMapReserveBytes = 4;
109     const uint32_t bmcMapMaxSizeBytes = 4 * 1024 - bmcMapReserveBytes;
110 
111     if (length <= bmcMapReserveBytes)
112     {
113         std::fprintf(stderr, "window size %" PRIx32 " too small to map.\n",
114                      length);
115         result.response = EINVAL;
116         return result;
117     }
118 
119     if (length > bmcMapMaxSizeBytes)
120     {
121         std::fprintf(stderr,
122                      "window size %" PRIx32 " not supported. Max size 4k.\n",
123                      length);
124         length = bmcMapMaxSizeBytes;
125     }
126 
127     /* If host requested region starts at an aligned address, return offset of 4
128      * bytes so as to skip the semaphore register.
129      */
130     uint32_t windowOffset = bmcMapReserveBytes;
131     // uint32_t windowSize = length;
132 
133     result.response = 0;
134     result.windowOffset = windowOffset;
135     result.windowSize = length;
136 
137     const uint32_t addressOffset = address & 0x7;
138 
139     if (addressOffset == 0)
140     {
141         std::fprintf(stderr, "Map address offset should be 4 for Nuvoton.\n");
142 
143         result.response = EFBIG;
144         return result;
145     }
146     else if (addressOffset != bmcMapReserveBytes)
147     {
148         std::fprintf(stderr, "Map address offset should be 4 for Nuvoton.\n");
149 
150         result.response = EINVAL;
151         return result;
152     }
153 
154     /* TODO: need a kernel driver to handle mapping configuration.
155      * Until then program the register through /dev/mem.
156      */
157     int fd;
158     if ((fd = sys->open("/dev/mem", O_RDWR | O_SYNC)) == -1)
159     {
160         std::fprintf(stderr, "Failed to open /dev/mem\n");
161         sys->close(fd);
162 
163         result.response = EINVAL;
164         return result;
165     }
166 
167     const uint32_t bmcMapConfigBaseAddr = 0xc0001000;
168     const uint32_t bmcMapConfigWindowSizeOffset = 0x7;
169     const uint32_t bmcMapConfigWindowBaseOffset = 0xa;
170     const uint8_t bmcWindowSizeValue = 0xc;     // 4k
171     const uint16_t bmcWindowBaseValue = 0x8000; // BMC phyAddr from 0xc0008000
172 
173     int pageSize = sys->getpagesize();
174 
175     auto mapBasePtr = reinterpret_cast<uint8_t*>(
176         sys->mmap(nullptr, pageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
177                   bmcMapConfigBaseAddr));
178 
179     uint8_t* bmcWindowSize = mapBasePtr + bmcMapConfigWindowSizeOffset;
180     uint16_t* bmcWindowBase =
181         reinterpret_cast<uint16_t*>(mapBasePtr + bmcMapConfigWindowBaseOffset);
182 
183     *bmcWindowSize = bmcWindowSizeValue;
184     *bmcWindowBase = bmcWindowBaseValue;
185 
186     sys->munmap(mapBasePtr, pageSize);
187     sys->close(fd);
188 
189     return result;
190 }
191 
192 } // namespace ipmi_flash
193