blob: e14129d0f0a9fb34f4b56a6af490a869715329cc [file] [log] [blame]
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "utils.hpp"
#include <boost/asio.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <optional>
#include <set>
extern "C"
{
#include <i2c/smbus.h>
#include <linux/i2c-dev.h>
}
constexpr static char daemonType[] = "USBReset";
// a set of i2c<bus, addr> indicating if the i2c device has been intiated or not
// the set is used to ensure each USBHub just get reset once per power-up
std::set<std::pair<int, int>> initSet;
static std::optional<int>
extractBusNumber(const std::string& path,
const SensorBaseConfigMap& properties)
{
auto findBus = properties.find("Bus");
if (findBus == properties.end())
{
std::cerr << "could not determine bus number for " << path << "\n";
return std::nullopt;
}
return std::visit(VariantToIntVisitor(), findBus->second);
}
static std::optional<int> extractAddress(const std::string& path,
const SensorBaseConfigMap& properties)
{
auto findAddr = properties.find("Address");
if (findAddr == properties.end())
{
std::cerr << "could not determine address for " << path << "\n";
return std::nullopt;
}
return std::visit(VariantToIntVisitor(), findAddr->second);
}
void resetUSBHub(int i2cBus, int i2cAddr)
{
std::cerr << "resetting i2c device: " << i2cBus << ", " << i2cAddr << '\n';
std::filesystem::path devpath = "/dev/i2c-" + std::to_string(i2cBus);
int fd = ::open(devpath.c_str(), std::ios_base::in | std::ios_base::out);
if (fd < 0) {
std::cerr << "fail to open i2c device: " << i2cBus << ", " << i2cAddr
<< '\n';
return;
}
/* Select the target device */
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
if (::ioctl(fd, I2C_SLAVE_FORCE, i2cAddr) < 0)
{
std::cerr << "Failed to configure device address 0x" << std::hex
<< i2cAddr << " for bus " << std::dec << i2cBus << ": "
<< strerror(errno) << "\n";
return;
}
// retry max 5 times
int i = 0;
int res = -1;
for (; i < 5; i++)
{
if (i > 0)
{
std::cerr << "retry the reset command" << '\n';
}
res = i2c_smbus_write_byte_data(fd, 0x01, 0x00);
if (res < 0)
{
std::cerr
<< "Failed to set output register to all zero on address 0x"
<< std::hex << i2cAddr << " for bus " << std::dec << i2cBus
<< ": " << strerror(-res) << "\n";
continue;
}
res = i2c_smbus_write_byte_data(fd, 0x03, 0xfe);
if (res < 0)
{
std::cerr << "Failed to asserts USB Hub reset on address 0x"
<< std::hex << i2cAddr << " for bus " << std::dec
<< i2cBus << ": " << strerror(-res) << "\n";
continue;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
res = i2c_smbus_write_byte_data(fd, 0x01, 0x01);
if (res < 0)
{
std::cerr << "Failed to deasserts USB Hub reset on address 0x"
<< std::hex << i2cAddr << " for bus " << std::dec
<< i2cBus << ": " << strerror(-res) << "\n";
continue;
}
break;
}
if (i >= 5)
{
std::cerr << "Failed to send reset command to address 0x" << std::hex
<< i2cAddr << " for bus " << std::dec << i2cBus << "\n";
}
::close(fd);
}
void handler(const std::shared_ptr<sdbusplus::asio::connection> &bus)
{
auto getter = std::make_shared<GetSensorConfiguration>(
bus, [](const ManagedObjectType &configurations)
{
if (!readingStateGood(PowerState::on))
{
return;
}
for (const auto &[path, configData] : configurations)
{
auto find = configData.find(configInterfaceName(daemonType));
if (find == configData.end())
{
continue;
}
const auto &config = find->second;
auto i2cBus = extractBusNumber(path, config);
auto i2cAddr = extractAddress(path, config);
if (!i2cBus || !i2cAddr)
{
std::cerr << "failed to find i2c bus info: continue" << '\n';
continue;
}
auto initiated = initSet.find({*i2cBus, *i2cAddr});
if (initiated != initSet.end())
{
continue;
}
resetUSBHub(*i2cBus, *i2cAddr);
initSet.emplace(*i2cBus, *i2cAddr);
}
});
getter->getConfiguration(std::vector<std::string>{daemonType});
}
int main()
{
boost::asio::io_service io;
auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server objectServer(systemBus, true);
[[maybe_unused]] auto powerCb =
setupPowerMatchCallback(systemBus, [&](PowerState type, bool state) {
if (type != PowerState::on)
{
return;
}
// clear the initSet when the Host power is done
if (!state)
{
initSet.clear();
}
else
{
handler(systemBus);
}
});
std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
setupPropertiesChangedMatches(
*systemBus, std::to_array<const char*>({daemonType}),
[&](sdbusplus::message_t&) {
static boost::asio::steady_timer timer(io);
timer.expires_from_now(std::chrono::seconds(1));
timer.async_wait([&](const boost::system::error_code& ec) {
if (ec == boost::asio::error::operation_aborted)
{
return; // we're being canceled
}
if (ec)
{
std::cerr << "Error: " << ec.message() << "\n";
return;
}
handler(systemBus);
});
});
boost::asio::post(io, [systemBus] { handler(systemBus); });
io.run();
}