xref: /openbmc/phosphor-modbus/rtu/inventory/modbus_inventory.cpp (revision e92aba4516471f5a01d4ab1f93eb9919ec05c21f)
1 #include "modbus_inventory.hpp"
2 
3 #include "common/entity_manager_interface.hpp"
4 
5 #include <phosphor-logging/lg2.hpp>
6 #include <xyz/openbmc_project/Configuration/ModbusRTUDetect/client.hpp>
7 #include <xyz/openbmc_project/Inventory/Item/client.hpp>
8 
9 #include <flat_map>
10 
11 namespace phosphor::modbus::rtu::inventory
12 {
13 PHOSPHOR_LOG2_USING;
14 
15 namespace config
16 {
17 
18 using BasicVariantType =
19     std::variant<std::vector<std::string>, std::vector<uint8_t>, std::string,
20                  int64_t, uint64_t, double, int32_t, uint32_t, int16_t,
21                  uint16_t, uint8_t, bool>;
22 using InventoryBaseConfigMap = std::flat_map<std::string, BasicVariantType>;
23 using InventoryData = std::flat_map<std::string, InventoryBaseConfigMap>;
24 using ManagedObjectType =
25     std::flat_map<sdbusplus::message::object_path, InventoryData>;
26 
27 static constexpr std::array<std::pair<std::string_view, Parity>, 3>
28     validParities = {
29         {{"Odd", Parity::odd}, {"Even", Parity::even}, {"None", Parity::none}}};
30 
31 // TODO: This API will be dropped once EM supports non-indexed interfaces for
32 // array objects.
processModbusAddressInterface(Config & config,const InventoryBaseConfigMap & configMap)33 static auto processModbusAddressInterface(
34     Config& config, const InventoryBaseConfigMap& configMap) -> bool
35 {
36     debug("Processing ModbusAddress {NAME}", "NAME", config.name);
37 
38     auto rangeStartIter = configMap.find("RangeStart");
39     if (rangeStartIter == configMap.end())
40     {
41         error("Missing RangeStart for {NAME}", "NAME", config.name);
42         return false;
43     }
44     auto rangeStart = std::get<uint64_t>(rangeStartIter->second);
45 
46     auto rangeEndIter = configMap.find("RangeEnd");
47     if (rangeEndIter == configMap.end())
48     {
49         error("Missing RangeEnd for {NAME}", "NAME", config.name);
50         return false;
51     }
52     auto rangeEnd = std::get<uint64_t>(rangeEndIter->second);
53 
54     auto serialPortIter = configMap.find("SerialPort");
55     if (serialPortIter == configMap.end())
56     {
57         error("Missing SerialPort for {NAME}", "NAME", config.name);
58         return false;
59     }
60     auto serialPort = std::get<std::string>(serialPortIter->second);
61 
62     config.addressMap[serialPort].push_back(AddressRange{
63         static_cast<uint8_t>(rangeStart), static_cast<uint8_t>(rangeEnd)});
64 
65     debug("ModbusAddress {NAME} {PORT} {START} {END}", "NAME", config.name,
66           "PORT", serialPort, "START", rangeStart, "END", rangeEnd);
67 
68     return true;
69 }
70 
71 // TODO: This API will be dropped once EM supports non-indexed interfaces for
72 // array objects.
processModbusRegistersInterface(Config & config,const InventoryBaseConfigMap & configMap)73 static auto processModbusRegistersInterface(
74     Config& config, const InventoryBaseConfigMap& configMap) -> bool
75 {
76     debug("Processing ModbusRegisters {NAME}", "NAME", config.name);
77     Register registerConfig = {};
78 
79     auto nameIter = configMap.find("Name");
80     if (nameIter == configMap.end())
81     {
82         error("Missing Name for {NAME}", "NAME", config.name);
83         return false;
84     }
85     registerConfig.name = std::get<std::string>(nameIter->second);
86 
87     auto address = configMap.find("Address");
88     if (address == configMap.end())
89     {
90         error("Missing Address for {NAME}", "NAME", config.name);
91         return false;
92     }
93     registerConfig.offset = std::get<uint64_t>(address->second);
94 
95     auto sizeIter = configMap.find("Size");
96     if (sizeIter == configMap.end())
97     {
98         error("Missing Size for {NAME}", "NAME", config.name);
99         return false;
100     }
101     registerConfig.size = std::get<uint64_t>(sizeIter->second);
102 
103     config.registers.push_back(registerConfig);
104 
105     debug("ModbusRegisters {NAME} {ADDRESS} {SIZE}", "NAME",
106           registerConfig.name, "ADDRESS", registerConfig.offset, "SIZE",
107           registerConfig.size);
108 
109     return true;
110 }
111 
printConfig(const Config & config)112 static auto printConfig(const Config& config) -> void
113 {
114     info("Inventory device config: {NAME} {BAUDRATE} {PARITY}", "NAME",
115          config.name, "BAUDRATE", config.baudRate, "PARITY", config.parity);
116 
117     for (const auto& [port, addressRanges] : config.addressMap)
118     {
119         for (const auto& addressRange : addressRanges)
120         {
121             info(
122                 "Inventory device config: {PORT} {ADDRESS_START} {ADDRESS_END}",
123                 "PORT", port, "ADDRESS_START", addressRange.start,
124                 "ADDRESS_END", addressRange.end);
125         }
126     }
127 
128     for (const auto& registerConfig : config.registers)
129     {
130         info("Inventory device config: {NAME} {ADDRESS} {SIZE}", "NAME",
131              registerConfig.name, "ADDRESS", registerConfig.offset, "SIZE",
132              registerConfig.size);
133     }
134 }
135 
getConfigSubInterfaces(sdbusplus::async::context & ctx,sdbusplus::message::object_path objectPath,Config & config)136 auto getConfigSubInterfaces(sdbusplus::async::context& ctx,
137                             sdbusplus::message::object_path objectPath,
138                             Config& config) -> sdbusplus::async::task<bool>
139 {
140     constexpr auto modbusAddressInterface =
141         "xyz.openbmc_project.Configuration.ModbusRTUDetect.Address";
142     constexpr auto modbusRegistersInterface =
143         "xyz.openbmc_project.Configuration.ModbusRTUDetect.Registers";
144 
145     using InventoryIntf =
146         sdbusplus::client::xyz::openbmc_project::inventory::Item<>;
147 
148     constexpr auto entityManager =
149         sdbusplus::async::proxy()
150             .service(entity_manager::EntityManagerInterface::serviceName)
151             .path(InventoryIntf::namespace_path)
152             .interface("org.freedesktop.DBus.ObjectManager");
153 
154     for (const auto& [path, deviceConfig] :
155          co_await entityManager.call<ManagedObjectType>(ctx,
156                                                         "GetManagedObjects"))
157     {
158         if (!(path.str).starts_with(objectPath.str))
159         {
160             debug("Skipping device {PATH}", "PATH", path.str);
161             continue;
162         }
163         debug("Processing device {PATH}", "PATH", path.str);
164         for (const auto& [interfaceName, interfaceConfig] : deviceConfig)
165         {
166             if (interfaceName.starts_with(modbusAddressInterface))
167             {
168                 if (!processModbusAddressInterface(config, interfaceConfig))
169                 {
170                     error("Failed to process {INTERFACE} for {NAME}",
171                           "INTERFACE", modbusAddressInterface, "NAME",
172                           config.name);
173                     co_return false;
174                 }
175             }
176             else if (interfaceName.starts_with(modbusRegistersInterface))
177             {
178                 if (!processModbusRegistersInterface(config, interfaceConfig))
179                 {
180                     error("Failed to process {INTERFACE} for {NAME}",
181                           "INTERFACE", modbusRegistersInterface, "NAME",
182                           config.name);
183                     co_return false;
184                 }
185             }
186         }
187     }
188 
189     co_return true;
190 }
191 
getConfig(sdbusplus::async::context & ctx,sdbusplus::message::object_path objectPath)192 auto getConfig(sdbusplus::async::context& ctx,
193                sdbusplus::message::object_path objectPath)
194     -> sdbusplus::async::task<std::optional<Config>>
195 {
196     using ModbusRTUDetectIntf = sdbusplus::client::xyz::openbmc_project::
197         configuration::ModbusRTUDetect<>;
198 
199     Config config = {};
200 
201     auto properties =
202         co_await ModbusRTUDetectIntf(ctx)
203             .service(entity_manager::EntityManagerInterface::serviceName)
204             .path(objectPath.str)
205             .properties();
206 
207     config.name = properties.name;
208     config.baudRate = properties.baud_rate;
209 
210     for (const auto& [parityStr, parity] : config::validParities)
211     {
212         if (parityStr == properties.data_parity)
213         {
214             config.parity = parity;
215             break;
216         }
217     }
218     if (config.parity == Parity::unknown)
219     {
220         error("Invalid parity {PARITY} for {NAME}", "PARITY",
221               properties.data_parity, "NAME", properties.name);
222         co_return std::nullopt;
223     }
224 
225     if (!co_await getConfigSubInterfaces(ctx, objectPath, config))
226     {
227         co_return std::nullopt;
228     }
229 
230     printConfig(config);
231 
232     co_return config;
233 }
234 
235 } // namespace config
236 
Device(sdbusplus::async::context & ctx,const config::Config & config,serial_port_map_t & serialPorts)237 Device::Device(sdbusplus::async::context& ctx, const config::Config& config,
238                serial_port_map_t& serialPorts) :
239     config(config), ctx(ctx), serialPorts(serialPorts)
240 {
241     for (const auto& [serialPort, _] : config.addressMap)
242     {
243         if (serialPorts.find(serialPort) == serialPorts.end())
244         {
245             error("Serial port {PORT} not found for {NAME}", "PORT", serialPort,
246                   "NAME", config.name);
247             continue;
248         }
249     }
250 }
251 
probePorts()252 auto Device::probePorts() -> sdbusplus::async::task<void>
253 {
254     debug("Probing ports for {NAME}", "NAME", config.name);
255     while (!ctx.stop_requested())
256     {
257         for (const auto& [serialPort, _] : config.addressMap)
258         {
259             if (serialPorts.find(serialPort) == serialPorts.end())
260             {
261                 continue;
262             }
263             ctx.spawn(probePort(serialPort));
264         }
265         constexpr auto probeInterval = 3;
266         co_await sdbusplus::async::sleep_for(
267             ctx, std::chrono::seconds(probeInterval));
268         debug("Probing ports for {NAME} in {INTERVAL} seconds", "NAME",
269               config.name, "INTERVAL", probeInterval);
270     }
271 }
272 
probePort(std::string portName)273 auto Device::probePort(std::string portName) -> sdbusplus::async::task<void>
274 {
275     debug("Probing port {PORT}", "PORT", portName);
276 
277     auto portConfig = config.addressMap.find(portName);
278     if (portConfig == config.addressMap.end())
279     {
280         error("Serial port {PORT} address map not found for {NAME}", "PORT",
281               portName, "NAME", config.name);
282         co_return;
283     }
284     auto addressRanges = portConfig->second;
285 
286     auto port = serialPorts.find(portName);
287     if (port == serialPorts.end())
288     {
289         error("Serial port {PORT} not found for {NAME}", "PORT", portName,
290               "NAME", config.name);
291         co_return;
292     }
293 
294     for (const auto& addressRange : addressRanges)
295     {
296         for (auto address = addressRange.start; address <= addressRange.end;
297              address++)
298         {
299             co_await probeDevice(address, portName, *port->second);
300         }
301     }
302 }
303 
probeDevice(uint8_t address,const std::string & portName,SerialPortIntf & port)304 auto Device::probeDevice(uint8_t address, const std::string& portName,
305                          SerialPortIntf& port) -> sdbusplus::async::task<void>
306 {
307     debug("Probing device at {ADDRESS} on port {PORT}", "ADDRESS", address,
308           "PORT", portName);
309 
310     if (config.registers.size() == 0)
311     {
312         error("No registers configured for {NAME}", "NAME", config.name);
313         co_return;
314     }
315     auto probeRegister = config.registers[0].offset;
316     auto registers = std::vector<uint16_t>(config.registers[0].size);
317 
318     auto sourceId = std::to_string(address) + "_" + portName;
319 
320     auto ret = co_await port.readHoldingRegisters(
321         address, probeRegister, config.baudRate, config.parity, registers);
322     if (ret)
323     {
324         if (inventorySources.find(sourceId) == inventorySources.end())
325         {
326             debug("Device found at {ADDRESS}", "ADDRESS", address);
327             co_await addInventorySource(address, portName, port);
328         }
329         else
330         {
331             debug("Device already exists at {ADDRESS}", "ADDRESS", address);
332         }
333     }
334     else
335     {
336         if (inventorySources.find(sourceId) != inventorySources.end())
337         {
338             warning(
339                 "Device removed at {ADDRESS} due to probe failure for {PROBE_REGISTER}",
340                 "ADDRESS", address, "PROBE_REGISTER", probeRegister);
341             inventorySources[sourceId]->emit_removed();
342             inventorySources.erase(sourceId);
343         }
344     }
345 }
346 
fillInventorySourceProperties(InventorySourceIntf::properties_t & properties,const std::string & regName,std::string & strValue)347 static auto fillInventorySourceProperties(
348     InventorySourceIntf::properties_t& properties, const std::string& regName,
349     std::string& strValue) -> void
350 {
351     constexpr auto partNumber = "PartNumber";
352     constexpr auto sparePartNumber = "SparePartNumber";
353     constexpr auto serialNumber = "SerialNumber";
354     constexpr auto buildDate = "BuildDate";
355     constexpr auto model = "Model";
356     constexpr auto manufacturer = "Manufacturer";
357 
358     if (regName == partNumber)
359     {
360         properties.part_number = strValue;
361     }
362     else if (regName == sparePartNumber)
363     {
364         properties.spare_part_number = strValue;
365     }
366     else if (regName == serialNumber)
367     {
368         properties.serial_number = strValue;
369     }
370     else if (regName == buildDate)
371     {
372         properties.build_date = strValue;
373     }
374     else if (regName == model)
375     {
376         properties.model = strValue;
377     }
378     else if (regName == manufacturer)
379     {
380         properties.manufacturer = strValue;
381     }
382 }
383 
addInventorySource(uint8_t address,const std::string & portName,SerialPortIntf & port)384 auto Device::addInventorySource(uint8_t address, const std::string& portName,
385                                 SerialPortIntf& port)
386     -> sdbusplus::async::task<void>
387 {
388     InventorySourceIntf::properties_t properties;
389 
390     for (const auto& reg : config.registers)
391     {
392         auto registers = std::vector<uint16_t>(reg.size);
393         auto ret = co_await port.readHoldingRegisters(
394             address, reg.offset, config.baudRate, config.parity, registers);
395         if (!ret)
396         {
397             error(
398                 "Failed to read holding registers {NAME} for {DEVICE_ADDRESS}",
399                 "NAME", reg.name, "DEVICE_ADDRESS", address);
400             continue;
401         }
402 
403         std::string strValue = "";
404 
405         // Reswap bytes in each register for string conversion
406         for (const auto& value : registers)
407         {
408             strValue += static_cast<char>((value >> 8) & 0xFF);
409             strValue += static_cast<char>(value & 0xFF);
410         }
411 
412         fillInventorySourceProperties(properties, reg.name, strValue);
413     }
414 
415     auto pathSuffix =
416         config.name + " " + std::to_string(address) + " " + portName;
417 
418     properties.name = pathSuffix;
419     properties.address = address;
420     properties.link_tty = portName;
421 
422     std::replace(pathSuffix.begin(), pathSuffix.end(), ' ', '_');
423 
424     auto objectPath =
425         std::string(InventorySourceIntf::namespace_path) + "/" + pathSuffix;
426     auto sourceId = std::to_string(address) + "_" + portName;
427 
428     inventorySources[sourceId] = std::make_unique<InventorySourceIntf>(
429         ctx, objectPath.c_str(), properties);
430     inventorySources[sourceId]->emit_added();
431 
432     info("Added InventorySource at {PATH}", "PATH", objectPath);
433 }
434 
435 } // namespace phosphor::modbus::rtu::inventory
436