xref: /openbmc/phosphor-host-postd/main.cpp (revision 789dab8fed600a885a8073387eea6d363f21ed68)
1 /**
2  * Copyright 2017 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 #ifdef ENABLE_IPMI_SNOOP
18 #include "ipmisnoop/ipmisnoop.hpp"
19 #endif
20 #include "lpcsnoop/snoop.hpp"
21 
22 #include <endian.h>
23 #include <fcntl.h>
24 #include <getopt.h>
25 #include <sys/epoll.h>
26 #include <systemd/sd-event.h>
27 #include <unistd.h>
28 
29 #include <sdeventplus/event.hpp>
30 #include <sdeventplus/source/event.hpp>
31 #include <sdeventplus/source/io.hpp>
32 #include <sdeventplus/source/signal.hpp>
33 #include <sdeventplus/source/time.hpp>
34 #include <sdeventplus/utility/sdbus.hpp>
35 #include <stdplus/signal.hpp>
36 
37 #include <chrono>
38 #include <cstdint>
39 #include <exception>
40 #include <functional>
41 #include <iostream>
42 #include <optional>
43 #include <thread>
44 
45 static size_t codeSize = 1; /* Size of each POST code in bytes */
46 static bool verbose = false;
47 static std::function<bool(std::vector<uint8_t>&, ssize_t)> procPostCode;
48 
usage(const char * name)49 static void usage(const char* name)
50 {
51     fprintf(stderr,
52             "Usage: %s\n"
53 #ifdef ENABLE_IPMI_SNOOP
54             "  -h, --host <host instances>  Default is '0'\n"
55 #else
56             "  -d, --device <DEVICE>  use <DEVICE> file.\n"
57             "  -r, --rate-limit=<N>   Only process N POST codes from the "
58             "device per second.\n"
59             "  -b, --bytes <SIZE>     set POST code length to <SIZE> bytes. "
60             "Default is 1\n"
61 #endif
62             "  -v, --verbose  Prints verbose information while running\n\n",
63             name);
64 }
65 
66 /**
67  * Call once for each POST code received. If the number of POST codes exceeds
68  * the configured rate limit, this function will disable the snoop device IO
69  * source until the end of the 1 second interval, then re-enable it.
70  *
71  * @return Whether the rate limit is exceeded.
72  */
rateLimit(PostReporter & reporter,sdeventplus::source::IO & ioSource)73 bool rateLimit(PostReporter& reporter, sdeventplus::source::IO& ioSource)
74 {
75     if (reporter.rateLimit == 0)
76     {
77         // Rate limiting is disabled.
78         return false;
79     }
80 
81     using Clock = sdeventplus::Clock<sdeventplus::ClockId::Monotonic>;
82 
83     static constexpr std::chrono::seconds rateLimitInterval(1);
84     static unsigned int rateLimitCount = 0;
85     static Clock::time_point rateLimitEndTime;
86 
87     const sdeventplus::Event& event = ioSource.get_event();
88 
89     if (rateLimitCount == 0)
90     {
91         // Initialize the end time when we start a new interval
92         rateLimitEndTime = Clock(event).now() + rateLimitInterval;
93     }
94 
95     if (++rateLimitCount < reporter.rateLimit)
96     {
97         return false;
98     }
99 
100     rateLimitCount = 0;
101 
102     if (rateLimitEndTime < Clock(event).now())
103     {
104         return false;
105     }
106 
107     if (verbose)
108     {
109         fprintf(stderr, "Hit POST code rate limit - disabling temporarily\n");
110     }
111 
112     ioSource.set_enabled(sdeventplus::source::Enabled::Off);
113     sdeventplus::source::Time<sdeventplus::ClockId::Monotonic>(
114         event, rateLimitEndTime, std::chrono::milliseconds(100),
115         [&ioSource](auto&, auto) {
116             if (verbose)
117             {
118                 fprintf(stderr, "Reenabling POST code handler\n");
119             }
120             ioSource.set_enabled(sdeventplus::source::Enabled::On);
121         })
122         .set_floating(true);
123     return true;
124 }
125 
126 /*
127  * Split input code into multiple 2 bytes PCC code, If the PCC code prefix
128  * matches the check code, store each PCC code in aspeedPCCBuffer, or clear
129  * aspeedPCCBuffer if the prefix does not match.
130  *
131  * Each PCC code contains one byte of port number (MSB) and another byte of
132  * partial postcode (LSB). To get a complete postcode, the PCC code should
133  * followed the sequence of 0x40AA, 0x41BB, 0x42CC & 0x43DD. When
134  * aspeedPCCBuffer contains enough PCC codes, the postcode will be assigned as
135  * 0xDDCCBBAA.
136  */
aspeedPCC(std::vector<uint8_t> & code,ssize_t readb)137 bool aspeedPCC(std::vector<uint8_t>& code, ssize_t readb)
138 {
139     // Size of data coming from the PCC hardware
140     constexpr size_t pccSize = sizeof(uint16_t);
141     // Required PCC count of a full postcode, if codeSize is 8 bytes, it means
142     // it require 4 PCC codes in correct sequence to get a complete postcode.
143     const size_t fullPostPCCCount = codeSize / pccSize;
144     // A PCC buffer for storing PCC code in sequence.
145     static std::vector<uint16_t> aspeedPCCBuffer;
146     constexpr uint16_t firstPCCPortNumber = 0x4000;
147     constexpr uint16_t pccPortNumberMask = 0xFF00;
148     constexpr uint16_t pccPostCodeMask = 0x00FF;
149     constexpr uint8_t byteShift = 8;
150 
151     uint16_t* codePtr = reinterpret_cast<uint16_t*>(code.data());
152 
153     for (size_t i = 0; i < (readb / pccSize); i++)
154     {
155         uint16_t checkCode =
156             firstPCCPortNumber +
157             ((aspeedPCCBuffer.size() % fullPostPCCCount) << byteShift);
158 
159         if (checkCode == (codePtr[i] & pccPortNumberMask))
160         {
161             aspeedPCCBuffer.emplace_back(codePtr[i]);
162         }
163         else
164         {
165             aspeedPCCBuffer.clear();
166 
167             // keep the PCC code if codePtr[i] matches with 0x40XX as first PCC
168             // code in buffer.
169             if ((codePtr[i] & pccPortNumberMask) == firstPCCPortNumber)
170             {
171                 aspeedPCCBuffer.emplace_back(codePtr[i]);
172             }
173         }
174     }
175 
176     if (aspeedPCCBuffer.size() < fullPostPCCCount)
177     {
178         // not receive full postcode yet.
179         return false;
180     }
181 
182     // Remove the prefix bytes and combine the partial postcodes together.
183     code.clear();
184     for (size_t i = fullPostPCCCount; i > 0; --i)
185     {
186         code.push_back(aspeedPCCBuffer[i - 1] & pccPostCodeMask);
187     }
188     aspeedPCCBuffer.erase(aspeedPCCBuffer.begin(),
189                           aspeedPCCBuffer.begin() + fullPostPCCCount);
190 
191     return true;
192 }
193 
194 /*
195  * Callback handling IO event from the POST code fd. i.e. there is new
196  * POST code available to read.
197  */
PostCodeEventHandler(PostReporter * reporter,sdeventplus::source::IO & s,int postFd,uint32_t)198 void PostCodeEventHandler(PostReporter* reporter, sdeventplus::source::IO& s,
199                           int postFd, uint32_t)
200 {
201     std::vector<uint8_t> code(codeSize, 0);
202     ssize_t readb;
203 
204     while ((readb = read(postFd, code.data(), codeSize)) > 0)
205     {
206         if (procPostCode && procPostCode(code, readb) == false)
207         {
208             return;
209         }
210 
211         if (verbose)
212         {
213             fprintf(stderr, "Code: 0x");
214             for (const auto& byte : code)
215             {
216                 fprintf(stderr, "%02x", byte);
217             }
218             fprintf(stderr, "\n");
219         }
220         // HACK: Always send property changed signal even for the same code
221         // since we are single threaded, external users will never see the
222         // first value.
223         code[0] = ~code[0];
224         reporter->value(std::make_tuple(code, secondary_post_code_t{}), true);
225         code[0] = ~code[0];
226         reporter->value(std::make_tuple(code, secondary_post_code_t{}));
227 
228         // read depends on old data being cleared since it doesn't always read
229         // the full code size
230         code.resize(codeSize);
231         std::fill(code.begin(), code.end(), 0);
232 
233         if (rateLimit(*reporter, s))
234         {
235             return;
236         }
237     }
238 
239     if (readb < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
240     {
241         return;
242     }
243 
244     /* Read failure. */
245     if (readb == 0)
246     {
247         fprintf(stderr, "Unexpected EOF reading postcode\n");
248     }
249     else
250     {
251         fprintf(stderr, "Failed to read postcode: %s\n", strerror(errno));
252     }
253     s.get_event().exit(1);
254 }
255 
256 /*
257  * TODO(venture): this only listens one of the possible snoop ports, but
258  * doesn't share the namespace.
259  *
260  * This polls() the lpc snoop character device and it owns the dbus object
261  * whose value is the latest port 80h value.
262  */
main(int argc,char * argv[])263 int main(int argc, char* argv[])
264 {
265     int postFd = -1;
266     unsigned int rateLimit = 0;
267 
268     int opt;
269 
270     std::vector<std::string> host;
271 
272     // clang-format off
273     static const struct option long_options[] = {
274 #ifdef ENABLE_IPMI_SNOOP
275         {"host", optional_argument, nullptr, 'h'},
276 #else
277         {"device", optional_argument, nullptr, 'd'},
278         {"rate-limit", optional_argument, nullptr, 'r'},
279         {"bytes",  required_argument, nullptr, 'b'},
280 #endif
281         {"verbose", no_argument, nullptr, 'v'},
282         {nullptr, 0, nullptr, 0}
283     };
284     // clang-format on
285 
286     constexpr const char* optstring =
287 #ifdef ENABLE_IPMI_SNOOP
288         "h:"
289 #else
290         "d:r:b:"
291 #endif
292         "v";
293 
294     while ((opt = getopt_long(argc, argv, optstring, long_options, nullptr)) !=
295            -1)
296     {
297         switch (opt)
298         {
299             case 0:
300                 break;
301             case 'h':
302             {
303                 std::string_view instances = optarg;
304                 size_t pos = 0;
305 
306                 while ((pos = instances.find(" ")) != std::string::npos)
307                 {
308                     host.emplace_back(instances.substr(0, pos));
309                     instances.remove_prefix(pos + 1);
310                 }
311                 host.emplace_back(instances);
312                 break;
313             }
314             case 'b':
315             {
316                 codeSize = atoi(optarg);
317 
318                 if (codeSize < 1 || codeSize > 8)
319                 {
320                     fprintf(stderr,
321                             "Invalid POST code size '%s'. Must be "
322                             "an integer from 1 to 8.\n",
323                             optarg);
324                     exit(EXIT_FAILURE);
325                 }
326                 break;
327             }
328             case 'd':
329                 if (std::string(optarg).starts_with("/dev/aspeed-lpc-pcc"))
330                 {
331                     procPostCode = aspeedPCC;
332                 }
333 
334                 postFd = open(optarg, O_NONBLOCK);
335                 if (postFd < 0)
336                 {
337                     fprintf(stderr, "Unable to open: %s\n", optarg);
338                     return -1;
339                 }
340                 break;
341             case 'r':
342             {
343                 int argVal = -1;
344                 try
345                 {
346                     argVal = std::stoi(optarg);
347                 }
348                 catch (...)
349                 {}
350 
351                 if (argVal < 1)
352                 {
353                     fprintf(stderr, "Invalid rate limit '%s'. Must be >= 1.\n",
354                             optarg);
355                     return EXIT_FAILURE;
356                 }
357 
358                 rateLimit = static_cast<unsigned int>(argVal);
359                 fprintf(stderr, "Rate limiting to %d POST codes per second.\n",
360                         argVal);
361                 break;
362             }
363             case 'v':
364                 verbose = true;
365                 break;
366             default:
367                 usage(argv[0]);
368                 return EXIT_FAILURE;
369         }
370     }
371 
372     auto bus = sdbusplus::bus::new_default();
373 
374 #ifdef ENABLE_IPMI_SNOOP
375     std::cout << "Verbose = " << verbose << std::endl;
376     int ret = postCodeIpmiHandler(ipmiSnoopObject, snoopDbus, bus, host);
377     if (ret < 0)
378     {
379         fprintf(stderr, "Error in postCodeIpmiHandler\n");
380         return ret;
381     }
382     return 0;
383 #endif
384 
385     bool deferSignals = true;
386 
387     // Add systemd object manager.
388     sdbusplus::server::manager_t snoopdManager(bus, snoopObject);
389 
390     PostReporter reporter(bus, snoopObject, deferSignals);
391     reporter.emit_object_added();
392     bus.request_name(snoopDbus);
393 
394     // Create sdevent and add IO source
395     try
396     {
397         sdeventplus::Event event = sdeventplus::Event::get_default();
398         std::optional<sdeventplus::source::IO> reporterSource;
399         if (postFd > 0)
400         {
401             reporter.rateLimit = rateLimit;
402             reporterSource.emplace(
403                 event, postFd, EPOLLIN,
404                 std::bind_front(PostCodeEventHandler, &reporter));
405         }
406         // Enable bus to handle incoming IO and bus events
407         auto intCb = [](sdeventplus::source::Signal& source,
408                         const struct signalfd_siginfo*) {
409             source.get_event().exit(0);
410         };
411         stdplus::signal::block(SIGINT);
412         sdeventplus::source::Signal(event, SIGINT, intCb).set_floating(true);
413         stdplus::signal::block(SIGTERM);
414         sdeventplus::source::Signal(event, SIGTERM, std::move(intCb))
415             .set_floating(true);
416         return sdeventplus::utility::loopWithBus(event, bus);
417     }
418     catch (const std::exception& e)
419     {
420         fprintf(stderr, "%s\n", e.what());
421     }
422 
423     if (postFd > -1)
424     {
425         close(postFd);
426     }
427 
428     return 0;
429 }
430