xref: /openbmc/phosphor-host-postd/main.cpp (revision f69ad7e69b8d064479d048dc749bb80d37070aed)
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 #include <fcntl.h>
18 #include <getopt.h>
19 #include <poll.h>
20 #include <unistd.h>
21 
22 #include <array>
23 #include <cstdint>
24 #include <iostream>
25 #include <memory>
26 #include <thread>
27 
28 #include <sdbusplus/bus.hpp>
29 #include <sdbusplus/server.hpp>
30 #include "xyz/openbmc_project/State/Boot/Raw/server.hpp"
31 
32 #include "lpcsnoop/snoop.hpp"
33 
34 template <typename... T>
35 using ServerObject = typename sdbusplus::server::object::object<T...>;
36 using PostInterface = sdbusplus::xyz::openbmc_project::State::Boot::server::Raw;
37 using PostObject = ServerObject<PostInterface>;
38 
39 class PostReporter : public PostObject
40 {
41   public:
42     PostReporter(sdbusplus::bus::bus& bus, const char* objPath, bool defer) :
43         PostObject(bus, objPath, defer)
44     {
45     }
46 };
47 
48 static const char* snoopFilename = "/dev/aspeed-lpc-snoop0";
49 static size_t codeSize = 1; /* Size of each POST code in bytes */
50 
51 /*
52  * 256 bytes is a nice amount.  It's improbable we'd need this many, but its
53  * gives us leg room in the event the driver poll doesn't return in a timely
54  * fashion.  So, mostly arbitrarily chosen.
55  */
56 static constexpr size_t BUFFER_SIZE = 256;
57 
58 /*
59  * Process any incoming dbus inquiries, which include introspection etc.
60  */
61 void ProcessDbus(sdbusplus::bus::bus& bus)
62 {
63     while (true)
64     {
65         bus.process_discard();
66         bus.wait(); // wait indefinitely
67     }
68 
69     return;
70 }
71 
72 static void usage(const char* name)
73 {
74     fprintf(stderr,
75             "Usage: %s [-d <DEVICE>]\n"
76             "  -b, --bytes <SIZE>     set POST code length to <SIZE> bytes. "
77             "Default is %zu\n"
78             "  -d, --device <DEVICE>  use <DEVICE> file. Default is '%s'\n\n",
79             name, codeSize, snoopFilename);
80 }
81 
82 static uint64_t assembleBytes(std::array<uint8_t, BUFFER_SIZE> buf, int start,
83                               int size)
84 {
85     uint64_t result = 0;
86 
87     for (int i = start + size - 1; i >= start; i--)
88     {
89         result <<= 8;
90         result |= buf[i];
91     }
92 
93     return result;
94 }
95 
96 /*
97  * TODO(venture): this only listens one of the possible snoop ports, but
98  * doesn't share the namespace.
99  *
100  * This polls() the lpc snoop character device and it owns the dbus object
101  * whose value is the latest port 80h value.
102  */
103 int main(int argc, char* argv[])
104 {
105     int rc = 0;
106     int opt;
107     struct pollfd pollset;
108     int pollr;
109     int readb;
110     int postFd = -1;
111     std::array<uint8_t, BUFFER_SIZE> buffer;
112 
113     /*
114      * These string constants are only used in this method within this object
115      * and this object is the only object feeding into the final binary.
116      *
117      * If however, another object is added to this binary it would be proper
118      * to move these declarations to be global and extern to the other object.
119      */
120     const char* snoopObject = SNOOP_OBJECTPATH;
121     const char* snoopDbus = SNOOP_BUSNAME;
122 
123     bool deferSignals = true;
124 
125     static const struct option long_options[] = {
126         {"bytes", required_argument, NULL, 'b'},
127         {"device", required_argument, NULL, 'd'},
128         {0, 0, 0, 0}};
129 
130     while ((opt = getopt_long(argc, argv, "b:d:", long_options, NULL)) != -1)
131     {
132         switch (opt)
133         {
134             case 0:
135                 break;
136             case 'b':
137                 codeSize = atoi(optarg);
138 
139                 if (codeSize < 1 || codeSize > 8)
140                 {
141                     fprintf(stderr,
142                             "Invalid POST code size '%s'. Must be "
143                             "an integer from 1 to 8.\n",
144                             optarg);
145                     exit(EXIT_FAILURE);
146                 }
147                 break;
148             case 'd':
149                 snoopFilename = optarg;
150                 break;
151             default:
152                 usage(argv[0]);
153                 exit(EXIT_FAILURE);
154         }
155     }
156 
157     postFd = open(snoopFilename, 0);
158     if (postFd < 0)
159     {
160         fprintf(stderr, "Unable to open: %s\n", snoopFilename);
161         return -1;
162     }
163 
164     pollset.fd = postFd;
165     pollset.events |= POLLIN;
166 
167     auto bus = sdbusplus::bus::new_default();
168 
169     // Add systemd object manager.
170     sdbusplus::server::manager::manager(bus, snoopObject);
171 
172     PostReporter reporter(bus, snoopObject, deferSignals);
173     reporter.emit_object_added();
174 
175     bus.request_name(snoopDbus);
176 
177     /*
178      * I don't see a public interface for getting the underlying sd_bus*
179      * so instead of poll(bus, driver), I'll just create a separate thread.
180      *
181      * TODO(venture): There may be a way to use sdevent to poll both the file
182      * and the dbus in the same event loop.  If I could get the sdbus pointer
183      * from bus directly, I'd grab a file handler from it, and then just poll on
184      * both in one loop.  From a cursory look at sdevent, I should be able to do
185      * something similar with that at some point.
186      */
187     std::thread lt(ProcessDbus, std::ref(bus));
188 
189     /* infinitely listen for POST codes and broadcast. */
190     while (true)
191     {
192         pollr = poll(&pollset, 1, -1); /* polls indefinitely. */
193         if (pollr < 0)
194         {
195             /* poll returned error. */
196             rc = -errno;
197             goto exit;
198         }
199 
200         if (pollr > 0)
201         {
202             if (pollset.revents & POLLIN)
203             {
204                 readb = read(postFd, buffer.data(), buffer.size());
205                 if (readb < 0)
206                 {
207                     /* Read failure. */
208                     rc = readb;
209                     goto exit;
210                 }
211                 else
212                 {
213                     if (readb % codeSize != 0)
214                     {
215                         fprintf(stderr,
216                                 "Warning: read size %d not a multiple of "
217                                 "POST code length %zu. Some codes may be "
218                                 "corrupt or missing\n",
219                                 readb, codeSize);
220                         readb -= (readb % codeSize);
221                     }
222 
223                     /* Broadcast the values read. */
224                     for (int i = 0; i < readb; i += codeSize)
225                     {
226                         reporter.value(assembleBytes(buffer, i, codeSize));
227                     }
228                 }
229             }
230         }
231     }
232 
233 exit:
234     if (postFd > -1)
235     {
236         close(postFd);
237     }
238 
239     lt.join();
240 
241     return rc;
242 }
243