blob: 03ef2ad82273c5c7be4932122ef4174adff2eda6 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "power_manager/powerd/policy/cellular_controller_trogdor.h"
#include <algorithm>
#include <libqrtr.h>
#include <utility>
#include <base/bind.h>
#include <base/check.h>
#include <base/strings/string_number_conversions.h>
#include "power_manager/common/prefs.h"
#include <base/system/sys_info.h>
#define TROGDOR_MODEM_NODE_ID (0x0)
#define TROGDOR_WDS_SERVICE_ID (0x1)
namespace {
const char kUpstartServiceName[] = "com.ubuntu.Upstart";
const char kNodeAddedEvent[] = "qrtr-service-added";
const char kNodeRemovedEvent[] = "qrtr-service-removed";
} // namespace
namespace power_manager {
namespace policy {
CellularControllerTrogdor::CellularControllerTrogdor() : buffer_(4096) {}
CellularControllerTrogdor::~CellularControllerTrogdor() {
watcher_ = nullptr;
if (socket_.is_valid()) {
StopServiceLookup(TROGDOR_WDS_SERVICE_ID, 1, 0);
watcher_ = nullptr;
socket_.reset();
}
}
void CellularControllerTrogdor::Init(Delegate* delegate,
PrefsInterface* prefs) {
DCHECK(delegate);
DCHECK(prefs);
delegate_ = delegate;
prefs->GetBool(kSetCellularTransmitPowerForTabletModePref,
&set_transmit_power_for_tablet_mode_);
prefs->GetBool(kSetCellularTransmitPowerForProximityPref,
&set_transmit_power_for_proximity_);
LOG(INFO)
<< "In CellularController::Init set_transmit_power_for_proximity_ = "
<< set_transmit_power_for_proximity_
<< " set_transmit_power_for_tablet_mode_ = "
<< set_transmit_power_for_tablet_mode_;
CHECK(InitQrtrSocket());
}
void CellularControllerTrogdor::EmitEvent(const char* event) {
brillo::ErrorPtr error;
LOG(INFO) << "In EmitEvent sending = " << event;
if (!upstart_proxy_->EmitEvent(event, {}, false /* wait */, &error)) {
LOG(ERROR) << "Could not emit upstart event: " << error->GetMessage();
}
}
void CellularControllerTrogdor::ProximitySensorDetected(UserProximity value) {
if (set_transmit_power_for_proximity_) {
if (set_transmit_power_for_tablet_mode_) {
LOG(INFO) << "Cellular power will be handled by proximity sensor and "
"tablet mode";
} else {
LOG(INFO) << "Cellular power will be handled by proximity sensor";
}
HandleProximityChange(value);
}
}
void CellularControllerTrogdor::HandleTabletModeChange(TabletMode mode) {
if (!set_transmit_power_for_tablet_mode_)
return;
if (tablet_mode_ == mode)
return;
tablet_mode_ = mode;
UpdateTransmitPower();
}
void CellularControllerTrogdor::HandleProximityChange(UserProximity proximity) {
if (!set_transmit_power_for_proximity_)
return;
if (proximity_ == proximity)
return;
proximity_ = proximity;
UpdateTransmitPower();
}
void CellularControllerTrogdor::HandleModemStateChange(ModemState state) {
if (state_ == state)
return;
state_ = state;
UpdateTransmitPower();
}
// Add support for 3 SAR power levels in trogdor
// compared to 2 on other boards (see DetermineTransmitPower)
RadioTransmitPower CellularControllerTrogdor::DetermineTransmitPower() const {
RadioTransmitPower proximity_power = RadioTransmitPower::HIGH;
RadioTransmitPower tablet_mode_power = RadioTransmitPower::HIGH;
// Note: On devices where dynamic SAR is not enabled (i.e. neither
// set_transmit_power_for_proximity_ or set_transmit_power_for_tablet_mode_
// is set) Set the power level to 0 (since the other levels are not
// programmed)
if (!set_transmit_power_for_proximity_ &&
!set_transmit_power_for_tablet_mode_) {
LOG(INFO) << "Set Cellular power level to index 0 as SAR is disabled";
return RadioTransmitPower::LOW;
}
if (set_transmit_power_for_proximity_) {
switch (proximity_) {
case UserProximity::UNKNOWN:
break;
case UserProximity::NEAR:
proximity_power = RadioTransmitPower::LOW;
break;
case UserProximity::FAR:
proximity_power = RadioTransmitPower::HIGH;
break;
}
}
if (set_transmit_power_for_tablet_mode_) {
switch (tablet_mode_) {
case TabletMode::UNSUPPORTED:
break;
case TabletMode::ON:
tablet_mode_power = RadioTransmitPower::LOW;
break;
case TabletMode::OFF:
tablet_mode_power = RadioTransmitPower::HIGH;
break;
}
}
if (proximity_power == RadioTransmitPower::LOW &&
tablet_mode_power == RadioTransmitPower::LOW) {
return RadioTransmitPower::LOW;
}
if (proximity_power == RadioTransmitPower::LOW &&
tablet_mode_power == RadioTransmitPower::HIGH) {
return RadioTransmitPower::MEDIUM;
}
return RadioTransmitPower::HIGH;
}
void CellularControllerTrogdor::UpdateTransmitPower() {
if (state_ == ModemState::ONLINE) {
RadioTransmitPower wanted_power = DetermineTransmitPower();
delegate_->SetCellularTransmitPower(wanted_power);
}
}
void CellularControllerTrogdor::OnFileCanReadWithoutBlocking() {
OnDataAvailable(this);
}
int CellularControllerTrogdor::Recv(void* buf, size_t size, void* metadata) {
uint32_t node, port;
int ret = qrtr_recvfrom(socket_.get(), buf, size, &node, &port);
VLOG(2) << "Receiving packet from node: " << node << " port: " << port;
if (metadata) {
PacketMetadata* data = reinterpret_cast<PacketMetadata*>(metadata);
data->node = node;
data->port = port;
}
return ret;
}
void CellularControllerTrogdor::ProcessQrtrPacket(uint32_t node,
uint32_t port,
int size) {
sockaddr_qrtr qrtr_sock;
qrtr_sock.sq_family = AF_QIPCRTR;
qrtr_sock.sq_node = node;
qrtr_sock.sq_port = port;
qrtr_packet pkt;
int ret = qrtr_decode(&pkt, buffer_.data(), size, &qrtr_sock);
if (ret < 0) {
LOG(ERROR) << "qrtr_decode failed";
return;
}
switch (pkt.type) {
case QRTR_TYPE_NEW_SERVER:
VLOG(1) << "Received NEW_SERVER QRTR packet node = " << pkt.node
<< " port = " << pkt.port << " service = " << pkt.service;
if (pkt.node == TROGDOR_MODEM_NODE_ID &&
pkt.service == TROGDOR_WDS_SERVICE_ID) {
EmitEvent(kNodeAddedEvent);
HandleModemStateChange(ModemState::ONLINE);
}
break;
case QRTR_TYPE_DEL_SERVER:
VLOG(1) << "Received DEL_SERVER QRTR packet node = " << pkt.node
<< " port = " << pkt.port << " service = " << pkt.service;
if (pkt.node == TROGDOR_MODEM_NODE_ID &&
pkt.service == TROGDOR_WDS_SERVICE_ID) {
EmitEvent(kNodeRemovedEvent);
HandleModemStateChange(ModemState::OFFLINE);
}
break;
default:
VLOG(1) << "Received QRTR packet but did not recognize packet type "
<< pkt.type << ".";
}
}
int CellularControllerTrogdor::Send(const void* data,
size_t size,
const void* metadata) {
uint32_t node = 0, port = 0;
if (metadata) {
const PacketMetadata* data =
reinterpret_cast<const PacketMetadata*>(metadata);
node = data->node;
port = data->port;
}
VLOG(2) << "Sending packet to node: " << node << " port: " << port;
return qrtr_sendto(socket_.get(), node, port, data, size);
}
bool CellularControllerTrogdor::StartServiceLookup(uint32_t service,
uint16_t version_major,
uint16_t version_minor) {
return qrtr_new_lookup(socket_.get(), service, version_major,
version_minor) >= 0;
}
bool CellularControllerTrogdor::StopServiceLookup(uint32_t service,
uint16_t version_major,
uint16_t version_minor) {
return qrtr_remove_lookup(socket_.get(), service, version_major,
version_minor) >= 0;
}
inline void CellularControllerTrogdor::OnDataAvailable(
CellularControllerTrogdor* cc) {
void* metadata = nullptr;
CellularControllerTrogdor::PacketMetadata data = {0, 0};
metadata = reinterpret_cast<void*>(&data);
int bytes_received = cc->Recv(buffer_.data(), buffer_.size(), metadata);
if (bytes_received < 0) {
LOG(ERROR) << "Socket recv failed";
return;
}
VLOG(1) << "ModemQrtr recevied raw data (" << bytes_received
<< " bytes): " << base::HexEncode(buffer_.data(), bytes_received);
ProcessQrtrPacket(data.node, data.port, bytes_received);
}
bool CellularControllerTrogdor::InitQrtrSocket() {
uint8_t kQrtrPort = 0;
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
scoped_refptr<dbus::Bus> bus(new dbus::Bus(options));
CHECK(bus->Connect());
upstart_proxy_ =
std::make_unique<com::ubuntu::Upstart0_6Proxy>(bus, kUpstartServiceName);
socket_.reset(qrtr_open(kQrtrPort));
if (!socket_.is_valid()) {
LOG(ERROR) << "Failed to open QRTR socket with port " << kQrtrPort;
return false;
}
watcher_ = base::FileDescriptorWatcher::WatchReadable(
socket_.get(),
base::BindRepeating(
&CellularControllerTrogdor::OnFileCanReadWithoutBlocking,
base::Unretained(this)));
if (!watcher_) {
LOG(ERROR) << "Failed to set up WatchFileDescriptor";
socket_.reset();
return false;
}
return StartServiceLookup(TROGDOR_WDS_SERVICE_ID, 1, 0);
}
} // namespace policy
} // namespace power_manager