blob: ddd587179f27a64ba4a3882d2414fff510118f7c [file] [log] [blame]
Marc Olberding5d50e522025-09-03 18:23:32 -07001#include "gpio.hpp"
2#include "i2c.hpp"
3#include "utilities.hpp"
4
5#include <systemd/sd-daemon.h>
6
Marc Olberdingc9c86122025-09-08 17:45:21 -07007#include <sdbusplus/asio/connection.hpp>
8
Marc Olberding5d50e522025-09-03 18:23:32 -07009#include <chrono>
Marc Olberding1b83d212025-10-03 17:42:58 -070010#include <expected>
Marc Olberding5d50e522025-09-03 18:23:32 -070011#include <filesystem>
12#include <fstream>
13#include <iostream>
14#include <thread>
Marc Olberdingc9c86122025-09-08 17:45:21 -070015#include <unordered_map>
Marc Olberding5d50e522025-09-03 18:23:32 -070016
Marc Olberdingc9c86122025-09-08 17:45:21 -070017using JsonVariantType =
18 std::variant<uint8_t, std::vector<std::string>, std::vector<double>,
19 std::string, int64_t, uint64_t, double, int32_t, uint32_t,
20 int16_t, uint16_t, bool>;
Marc Olberding5d50e522025-09-03 18:23:32 -070021namespace nvidia
22{
23
24using steady_clock = std::chrono::steady_clock;
25using namespace std::chrono_literals;
26
27void logged_system(std::string_view cmd)
28{
29 std::cerr << std::format("calling {} \n", cmd);
30 int rc = std::system(cmd.data());
31 (void)rc;
32}
33
34void setup_devmem()
35{
36 logged_system("mknod /dev/mem c 1 1");
37}
38
39void handle_passthrough_registers(bool enable)
40{
41 static constexpr uint32_t reg = 0x1e6e24bc;
42 std::string command;
43 if (enable)
44 {
45 command = std::format("devmem 0x{:x} 32 0x3f000000", reg);
46 }
47 else
48 {
49 command = std::format("devmem 0x{:x} 32 0", reg);
50 }
51 logged_system(command);
52}
53
54void wait_for_i2c_ready()
55{
56 // hpm cpld is at bus 4, address 0x17
57 i2c::RawDevice cpld{4, 0x17};
58 auto now = steady_clock::now();
59 auto end = now + 20min;
60 while (steady_clock::now() < end)
61 {
62 static constexpr uint8_t i2c_ready = 0xf2;
Marc Olberding1b83d212025-10-03 17:42:58 -070063 const auto result = cpld.read_byte(i2c_ready);
Marc Olberding5d50e522025-09-03 18:23:32 -070064
Marc Olberding1b83d212025-10-03 17:42:58 -070065 if (result.has_value() && *result == 1)
Marc Olberding5d50e522025-09-03 18:23:32 -070066 {
67 return;
68 }
Marc Olberding1b83d212025-10-03 17:42:58 -070069 else if (result.error())
70 {
71 std::string err =
72 std::format("Unable to communicate with cpld. rc: {}\n",
73 result.error().value());
74 std::cerr << err;
75 throw std::runtime_error(err);
76 }
Marc Olberding5d50e522025-09-03 18:23:32 -070077
78 std::this_thread::sleep_for(std::chrono::seconds{10});
79 }
80
81 throw std::runtime_error("Waiting for host timed out!\n");
82}
83
84void probe_dev(size_t bus, uint8_t address, std::string_view dev_type)
85{
Marc Olberdingc9c86122025-09-08 17:45:21 -070086 std::string path =
Marc Olberding5d50e522025-09-03 18:23:32 -070087 std::format("/sys/bus/i2c/devices/i2c-{}/new_device", bus);
88
Marc Olberdingc9c86122025-09-08 17:45:21 -070089 wait_for_path_to_exist(path, std::chrono::milliseconds{1000});
90
Marc Olberding5d50e522025-09-03 18:23:32 -070091 std::ofstream f{path};
92 if (!f.good())
93 {
94 std::cerr << std::format("Unable to open {}\n", path.c_str());
95 std::exit(EXIT_FAILURE);
96 }
97
98 f << std::format("{} 0x{:02x}", dev_type, address);
99 f.close();
100
101 std::string created_path =
102 std::format("/sys/bus/i2c/devices/{}-{:04x}", bus, address);
103 wait_for_path_to_exist(created_path, 10ms);
104}
105
106void create_i2c_mux(size_t bus, uint8_t address, std::string_view dev_type)
107{
108 probe_dev(bus, address, dev_type);
109
Marc Olberdingc9c86122025-09-08 17:45:21 -0700110 std::string idle =
Marc Olberding5d50e522025-09-03 18:23:32 -0700111 std::format("/sys/bus/i2c/devices/{}-{:04x}/idle_state", bus, address);
112 std::ofstream idle_f{idle};
113 if (!idle_f.good())
114 {
115 std::string err = std::format("Unable to open {}\n", idle.c_str());
116 std::cerr << err;
117 throw std::runtime_error(err);
118 }
119
120 // -2 is idle-mux-disconnect
121 idle_f << -2;
122 idle_f.close();
123}
124
125size_t get_bus_from_channel(size_t parent_bus, uint8_t address, size_t channel)
126{
127 std::filesystem::path path =
128 std::format("/sys/bus/i2c/devices/{}-{:04x}/channel-{}/i2c-dev/",
129 parent_bus, address, channel);
130 int bus = -1;
131 std::error_code ec{};
132 for (const auto& f : std::filesystem::directory_iterator(path, ec))
133 {
134 // we expect to see i2c-<bus>, trim and parse everything after the dash
135 const std::string& p = f.path().filename().string();
136 std::cerr << "Reading from " << p << "\n";
137 auto [_, err] = std::from_chars(p.data() + 4, p.data() + p.size(), bus);
138 if (err != std::errc{})
139 {
140 std::string err_s = std::format("Failed to parse {}\n", p);
141 std::cerr << err_s;
142 throw std::runtime_error(err_s);
143 }
144 }
145 if (bus == -1 || ec)
146 {
147 std::string err_s =
148 std::format("Failed to find a channel at {}\n", path.string());
149 std::cerr << err_s;
150 throw std::runtime_error(err_s);
151 }
152 return bus;
153}
154
155void bringup_cx8_mcu(size_t bus)
156{
157 probe_dev(bus, 0x26, "pca9555");
158 std::string gpio_p =
159 std::format("/sys/bus/i2c/devices/{}-{:04x}/", bus, 0x26);
160 int chip_num = gpio::find_chip_idx_from_dir(gpio_p);
161 if (chip_num < 0)
162 {
163 std::cerr << std::format("Failed to find cx8 gpio at {}\n", gpio_p);
164 std::exit(EXIT_FAILURE);
165 }
166
167 // 14 is the reset pin on the MCU
168 // reset pin is active low
169 gpio::set_raw(chip_num, 14, 1);
170}
171
172void gringup_gpu_sma(size_t bus, size_t channel)
173{
174 size_t gpu_bus = get_bus_from_channel(bus, 0x72, channel);
175 probe_dev(gpu_bus, 0x20, "pca6408");
176 std::string gpio_p =
177 std::format("/sys/bus/i2c/devices/{}-{:04x}/", gpu_bus, 0x20);
178 int chip_num = gpio::find_chip_idx_from_dir(gpio_p);
179 if (chip_num < 0)
180 {
181 std::cerr << std::format("Failed to find gpu gpio {}\n", gpio_p);
182 std::exit(EXIT_FAILURE);
183 }
184
185 // pin 4 is the reset pin, active low
186 // pin 5 engages the telemetry path from the SMA
187 gpio::set_raw(chip_num, 5, 1);
188 gpio::set_raw(chip_num, 4, 1);
189}
190
191void bringup_gpus_on_mcio(size_t bus)
192{
193 create_i2c_mux(bus, 0x72, "pca9546");
194
195 gringup_gpu_sma(bus, 2);
196 gringup_gpu_sma(bus, 3);
197}
198
199void bringup_cx8_mcio(size_t mux_addr, size_t channel, bool has_cx8)
200{
201 size_t bus = get_bus_from_channel(5, mux_addr, channel);
202 if (has_cx8)
203 {
204 bringup_cx8_mcu(bus);
205 }
206 bringup_gpus_on_mcio(bus);
207}
208
Marc Olberdingc9c86122025-09-08 17:45:21 -0700209const char* mctpd_service = "au.com.codeconstruct.MCTP1";
210const char* mctp_obj = "/au/com/codeconstruct/mctp1/";
211const char* mctp_busowner = "au.com.codeconstruct.MCTP.BusOwner1";
212const char* mctp_bridge = "au.com.codeconstruct.MCTP.Bridge1";
Marc Olberding5d50e522025-09-03 18:23:32 -0700213
Marc Olberdingc9c86122025-09-08 17:45:21 -0700214template <typename PropertyType>
215PropertyType get_property(const char* service, const char* object,
216 const char* interface, const char* property_name)
217{
218 auto b = sdbusplus::bus::new_default_system();
219 auto m = b.new_method_call(service, object,
220 "org.freedesktop.DBus.Properties", "Get");
221 m.append(interface, property_name);
222
223 std::variant<PropertyType> t;
224 auto reply = b.call(m);
225
226 reply.read(t);
227 return std::get<PropertyType>(t);
Marc Olberding5d50e522025-09-03 18:23:32 -0700228}
229
Marc Olberdingc9c86122025-09-08 17:45:21 -0700230// given a device index
231// enumerate the mctp interface
232// and give back the eid
233uint8_t enumerate_mctp(uint8_t device_idx)
Marc Olberding5d50e522025-09-03 18:23:32 -0700234{
Marc Olberdingc9c86122025-09-08 17:45:21 -0700235 std::vector<uint8_t> address = {};
236 std::string obj = std::format(
237 "/au/com/codeconstruct/mctp1/interfaces/mctpusb{}", device_idx);
238
239 std::cerr << "calling " << obj << std::endl;
240
241 auto b = sdbusplus::bus::new_default_system();
242 auto m = b.new_method_call(mctpd_service, obj.c_str(), mctp_busowner,
243 "AssignEndpoint");
244 m.append(address);
245
246 auto reply = b.call(m);
247
248 uint8_t eid;
249 int32_t net;
250 std::string intf;
251 bool probed;
252 reply.read(eid, net, intf, probed);
253
254 return eid;
255}
256
257// We need to get the pool start and size
258std::tuple<uint8_t, uint8_t> get_pool_start_and_size(uint8_t eid)
259{
260 std::string obj =
261 std::format("/au/com/codeconstruct/mctp1/networks/1/endpoints/{}", eid);
262 std::cerr << "calling " << obj << std::endl;
263
264 uint8_t poolstart = get_property<uint8_t>(mctpd_service, obj.c_str(),
265 mctp_bridge, "PoolStart");
266 uint8_t poolend = get_property<uint8_t>(mctpd_service, obj.c_str(),
267 mctp_bridge, "PoolEnd");
268
269 uint8_t poolsize = poolend - poolstart + 1;
270
271 std::cerr << std::format("eid {} has pool start {} and size {}", eid,
272 poolstart, poolsize)
273 << std::endl;
274 return {poolstart, poolsize};
275}
276
277int get_device_from_port_string(std::string_view port_string)
278{
279 std::filesystem::path path = port_string;
280 path /= "net";
281 int dev_index = -1;
282 auto p = path.native();
283 wait_for_path_to_exist(p, std::chrono::milliseconds{20000});
284
285 for (const auto& dir : std::filesystem::directory_iterator(path))
286 {
287 // this looks something like:
288 // /sys/devices/platform/ahb/1e6a3000.usb/usb1/1-1/1-1.2/1-1.2.3/1-1.2.3:1.0/net/mctpusb7
289 // we want to extract the final "7"
290 std::cerr << "Looking at " << dir.path().native() << std::endl;
291
292 auto f_name = dir.path().filename().native();
293 if (f_name.starts_with("mctpusb"))
294 {
295 std::from_chars(f_name.data() + 7, f_name.data() + f_name.size(),
296 dev_index);
297 break;
298 }
299 }
300
301 if (dev_index == -1)
302 {
303 std::cerr << std::format("Unable to find an mctpusb net device at {}\n",
304 path.native());
305 }
306
307 std::cerr << "found mctp device index " << dev_index << std::endl;
308 return dev_index;
309}
310
311bool is_populated(std::string board, std::string name)
312{
313 std::string obj = std::format(
314 "/xyz/openbmc_project/inventory/system/board/{}/{}", board, name);
315 std::cerr << "inspecting " << obj << std::endl;
316 try
317 {
318 uint8_t eid = get_property<uint8_t>(
319 "xyz.openbmc_project.EntityManager", obj.c_str(),
320 "xyz.openbmc_project.Configuration.NvidiaMctpVdm", "StaticEid");
321 (void)eid;
322 return true;
323 }
324 catch (...)
325 {
326 return false;
327 }
328}
329
330void force_rescan()
331{
332 auto b = sdbusplus::bus::new_default_system();
333 auto m = b.new_method_call("xyz.openbmc_project.EntityManager",
334 "/xyz/openbmc_project/EntityManager",
335 "xyz.openbmc_project.EntityManager", "ReScan");
336 b.call(m);
337}
338
339void populate_gpu(std::string board, uint8_t eid, std::string name)
340{
341 if (is_populated(board, name))
342 {
343 std::cerr << name << " already exists" << std::endl;
344 return;
345 }
346
347 std::string obj =
348 std::format("/xyz/openbmc_project/inventory/system/board/{}", board);
349
350 std::cerr << "calling with " << obj << std::endl;
351
352 std::chrono::steady_clock::time_point start =
353 std::chrono::steady_clock::now();
354 std::chrono::steady_clock::time_point end = start + std::chrono::minutes{3};
355 auto b = sdbusplus::bus::new_default_system();
356 auto m = b.new_method_call("xyz.openbmc_project.EntityManager", obj.c_str(),
357 "xyz.openbmc_project.AddObject", "AddObject");
358 std::unordered_map<std::string, JsonVariantType> param;
359 param["Name"] = name;
360 param["StaticEid"] = eid;
361 param["Type"] = "NvidiaMctpVdm";
362
363 m.append(param);
364
365 do
366 {
367 auto now = std::chrono::steady_clock::now();
368 if (now >= end)
369 {
370 std::cerr << "Timeout: Failed to add " << obj << std::endl;
371 return;
372 }
373 try
374 {
375 b.call(m);
376 return;
377 }
378 catch (...)
379 {
380 std::cerr << "Failed to find " << obj << " trying again"
381 << std::endl;
382 std::this_thread::sleep_for(std::chrono::seconds{10});
383 continue;
384 }
385 } while (true);
386}
387
388struct bridge_device
389{
390 std::string usb_path;
391 std::string name;
392 std::string board_name;
393};
394
395void bringup_devices()
396{
397 // There's a lot of hackery going on here
398 // This is for handling (as of today) unsupported bridged endpoints
399 // The MCU's on this platform act as MCTP bridges
400 // We know their absolute USB path through the platform hub, and that's
401 // symlinked to a mctp net device So we will start there we also know that
402 // each device the USB device is bridging to will always have the same
403 // relative ordering
404 // inside of a given pool. This is not a generally true assumption but it
405 // is true for our MCU's
406 // So we can put each bridge and is downstream devices through enumeration
407 // with mctpd, when we get the response, we know the bridges eid we can then
408 // ask mctpd what the pool size and start eid is for the bridge pool. From
409 // there we can infer the eid of each bridged device behind it and call
410 // AddObject on EntityManager for each board to bring up the requisite nodes
411 // beneath it which will allow the rest of the system to start behaving as
412 // expected. Once we have real support for bridged eid's, we can and should
413 // delete this mess.
414 static constexpr const char* usb_prefix =
415 "/sys/devices/platform/ahb/1e6a3000.usb/usb1/1-1/";
416 const std::array<bridge_device, 10> device_name_map = {
417 {{.usb_path = "1-1.2/1-1.2.1/1-1.2.1:1.0",
418 .name = "GPU_0",
419 .board_name = "Nvidia_RTX_PRO_6000_Blackwell_1"},
420 {.usb_path = "1-1.1/1-1.1.2/1-1.1.2.1/1-1.1.2.1:1.0",
421 .name = "GPU_1",
422 .board_name = "Nvidia_RTX_PRO_6000_Blackwell_2"},
423 {.usb_path = "1-1.4/1-1.4.1/1-1.4.1:1.0",
424 .name = "GPU_2",
425 .board_name = "Nvidia_RTX_PRO_6000_Blackwell_3"},
426 {.usb_path = "1-1.2/1-1.2.2/1-1.2.2:1.0",
427 .name = "GPU_3",
428 .board_name = "Nvidia_RTX_PRO_6000_Blackwell_4"},
429 {.usb_path = "1-1.1/1-1.1.4/1-1.1.4.1/1-1.1.4.1:1.0",
430 .name = "GPU_4",
431 .board_name = "Nvidia_RTX_PRO_6000_Blackwell_5"},
432 {.usb_path = "1-1.1/1-1.1.2/1-1.1.2.2/1-1.1.2.2:1.0",
433 .name = "GPU_5",
434 .board_name = "Nvidia_RTX_PRO_6000_Blackwell_6"},
435 {.usb_path = "1-1.4/1-1.4.2/1-1.4.2:1.0",
436 .name = "GPU_6",
437 .board_name = "Nvidia_RTX_PRO_6000_Blackwell_7"},
438 {.usb_path = "1-1.2/1-1.2.3/1-1.2.3:1.0",
439 .name = "CX8_0",
440 .board_name = "NVIDIA_Alon_cx8_Fru"},
441 {.usb_path = "1-1.1/1-1.1.4/1-1.1.4.2/1-1.1.4.2:1.0",
442 .name = "GPU_7",
443 .board_name = "Nvidia_RTX_PRO_6000_Blackwell_8"},
444 {.usb_path = "1-1.1/1-1.1.2/1-1.1.2.3/1-1.1.2.3:1.0",
445 .name = "CX8_1",
446 .board_name = "NVIDIA_Alon_cx8_Fru"}}};
447
448 for (const auto& [usb_path, name, board_name] : device_name_map)
449 {
450 std::cerr << "looking at device " << name << std::endl;
451 std::string path = std::format("{}/{}", usb_prefix, usb_path);
452 int dev_index = get_device_from_port_string(path);
453 if (dev_index < 0)
454 {
455 std::cerr << std::format(
456 "Unable to bring up {} because it doesn't seem to exist\n",
457 name);
458 continue;
459 }
460
461 // enumerate the bridge device
462 uint8_t bridge_eid = enumerate_mctp(dev_index);
463
464 auto [pool_start, pool_size] = get_pool_start_and_size(bridge_eid);
465
466 std::this_thread::sleep_for(std::chrono::milliseconds{500});
467
468 // yes this sucks, no I don't like it but we know we'll only have two
469 // types of bridged endpoints on this platform and its 9PM the night
470 // before it needs to work so we're going to do it *to* it
471 if (name.starts_with("GPU"))
472 {
473 // each GPU has an SMA, as well as a GPU, they both talk over vdm
474 // so add both as seperate nodes
475 std::cerr << "Adding SMA\n";
476 populate_gpu(board_name, bridge_eid, name + "SMA");
477 std::cerr << "Adding GPU\n";
478 populate_gpu(board_name, pool_start, name);
479 }
480 else if (name.starts_with("CX8"))
481 {
482 // TODO: deal with this
483 std::cerr << "Skipping CX8's for now\n";
484 }
485 else
486 {
487 std::cerr << std::format(
488 "Something awful happened with path: {}, name {}\n", path,
489 name);
490 }
491 }
492}
493
494void wait_for_frus_to_probe()
495{
496 std::string path = "/sys/bus/i2c/devices/17-0056";
497 wait_for_path_to_exist(path, std::chrono::milliseconds{30 * 1000});
498
499 std::this_thread::sleep_for(std::chrono::seconds{30});
Marc Olberding5d50e522025-09-03 18:23:32 -0700500}
501
502int init_nvl32()
503{
504 setup_devmem();
505 handle_passthrough_registers(false);
506 sd_notify(0, "READY=1");
507
508 wait_for_i2c_ready();
Marc Olberdingc9c86122025-09-08 17:45:21 -0700509 // we suspect that the CPLD tells us we're ready before
510 // we actually are. This sleep stabilizes this discrepency
511 std::this_thread::sleep_for(std::chrono::seconds{1});
Marc Olberding5d50e522025-09-03 18:23:32 -0700512
513 create_i2c_mux(5, 0x70, "pca9548");
514 create_i2c_mux(5, 0x71, "pca9548");
515 create_i2c_mux(5, 0x73, "pca9548");
516 create_i2c_mux(5, 0x75, "pca9548");
517
518 bringup_cx8_mcio(0x70, 1, true);
519 bringup_cx8_mcio(0x70, 5, false);
520 bringup_cx8_mcio(0x73, 3, true);
521 bringup_cx8_mcio(0x73, 7, false);
522
Marc Olberdingc9c86122025-09-08 17:45:21 -0700523 // there's a weird bug in EntityManager
524 // Where Fru devices don't probe automatically
525 // We'll wait for the drivers to be probed
526 // and then force a rescan
527 // we'll follow up with a proper fix
528 wait_for_frus_to_probe();
529
530 force_rescan();
531 // allow for things to settle
532 std::this_thread::sleep_for(std::chrono::seconds{1});
533
534 bringup_devices();
Marc Olberding5d50e522025-09-03 18:23:32 -0700535 std::cerr << "platform init complete\n";
536 pause();
537 std::cerr << "Releasing platform\n";
538
539 return EXIT_SUCCESS;
540}
541
542} // namespace nvidia