blob: ff0e88f8ade2d71f8291870da207ebbd435dcdd9 [file] [log] [blame]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cecservice/cec_device.h"
#include <fcntl.h>
#include <linux/cec-funcs.h>
#include <poll.h>
#include <string.h>
#include <deque>
#include <list>
#include <map>
#include <unordered_map>
#include <utility>
#include <base/compiler_specific.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
namespace cecservice {
class StateBase;
class CecDeviceImpl::Impl {
public:
using MessageSentCallback = std::variant<std::monostate,
CecDevice::GetTvPowerStatusCallback,
CecDevice::PowerChangeSentCallback>;
// Enum identifiying CEC device states.
enum class State {
kStart, // State when no physical address is known, in this state we
// are only allowed to send image view on message.
kNoLogicalAddress, // The physical address is known but the logical
// address is not (yet) configured.
kProbingTvAddress, // Obtaining logical address of the TV.
kReady, // All is set up, we are free to send any type of messages.
kDisabled, // Device is disabled due to previous errors.
};
struct CecTransmission {
// Message to be sent.
struct cec_msg message;
// Optional callback to run when the transmission completes.
MessageSentCallback callback;
};
Impl(std::unique_ptr<CecFd> fd, const base::FilePath& device_path);
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
// Performs object initialization. Returns false if the initialization
// failed and object is unusable.
bool Init();
// Implementation of respective methods from CecDevice:
void GetTvPowerStatus(CecDevice::GetTvPowerStatusCallback callback);
void SetStandBy(CecDevice::PowerChangeSentCallback callback);
void SetWakeUp(CecDevice::PowerChangeSentCallback callback);
// Enters a given state.
void EnterState(State state);
// Run the given callback if it's non-null.
void RunMessageSentCallback(MessageSentCallback callback,
TvPowerStatus status);
// Adds active source message to the queue.
void EnqueueActiveSourceMessage(CecDevice::PowerChangeSentCallback callback);
// Adds Image View On message to the queue.
void EnqueueImageViewOnMessage();
// Adds Stand By message to the queue.
void EnqueueStandByMessage(CecDevice::PowerChangeSentCallback callback);
// Enqueues Get Power Status request.
void EnqueueGetTvPowerStatusMessage(
CecDevice::GetTvPowerStatusCallback callback);
// Enqueues a message, returns true if the message was added to the queue.
// Optionally takes a callback to run when the transmission completes.
bool EnqueueMessage(struct cec_msg msg,
MessageSentCallback callback = MessageSentCallback());
// Flags that the we didn't manage to find a TV. Will make the object send
// request to address 0.
void SetTvProbingCompleted();
// Attempts to send next queued message, true if successful, false if an
// unrecoverable error occurred.
bool SendNextPendingMessage();
// Sends provided message.
CecFd::TransmitResult SendMessage(struct cec_msg* msg);
// Get path to device node (for logging purposes).
const std::string& GetDevicePathString() const;
// Processes report physical address message. If that message
// comes from a TV, updates a TV address.
void ProcessReportPhysicalAddress(const struct cec_msg& msg);
// Returns true is we know TV address.
bool HasTvAddress() const;
// Sets the current TV address to invalid.
void ResetTvAddress();
// Set assumed TV address.
void SetAssumedTVAddress(uint8_t address);
// Returns true if there are any queued messages.
bool MessagesInOutputQueue() const;
// Returns true if next scheduled message is directed to a TV.
bool NeedsToQueryTvAddress() const;
// Returns true if this is a message directed to the TV
// and should be send to whatever is the current TV's
// logical address.
bool IsMessageDirectedToTv(const struct cec_msg& msg) const;
private:
// Schedules watching for write readiness on the fd (if demanded by the
// current state).
void RequestWriteWatch();
// Processes messages lost event from CEC. Always returns true.
bool ProcessMessagesLostEvent(const struct cec_event_lost_msgs& event);
// Acts on process update event from CEC core. If this method returns false
// then an unexpected error was encountered and the object should be disabled.
bool ProcessStateChangeEvent(const struct cec_event_state_change& event);
// Processes incoming events. If false is returned, then unexpected failure
// occurred and the object should be disabled.
bool ProcessEvents();
// Attempts to read incoming data from the fd. If false is returned, then
// an unexpected failure occurred and the object should be disabled.
bool ProcessRead();
// Called when the fd is ready to be written to. Delegates the write
// operation to the current state. False is returned in case of unrecoverable
// error occurred.
bool ProcessWrite();
// Processes response received to get power status request. Should only be
// called for messages which are part of a get power status request (i.e. have
// a non-null get power status callback).
void ProcessPowerStatusResponse(const struct cec_msg& msg,
CecDevice::GetTvPowerStatusCallback callback);
// Handles responses to previously sent requests.
void ProcessSentMessage(const struct cec_msg& msg);
// Handles messages directed to us.
void ProcessIncomingMessage(struct cec_msg* msg);
// Sets the type of logical address on the adapter (if it has not been yet
// configured), returns false if the operation failed. Here we are only
// choosing the type of the address, the kernel will then do the probing and
// try to allocate first free address of the chosen type. The address type
// selection is permanent and survives device reconnects (every time EDID
// shows up, kernel will redo the probing and potentially can end up selecting
// different logical address). Since this selection is permanent it is
// sufficient to do it only once when we create the device.
bool SetLogicalAddress();
// Handles fd event.
void OnFdEvent(CecFd::EventType event);
// Clear all messages in the queue and the in flight list, running any
// callbacks.
void ClearMessages(TvPowerStatus response);
// Disables device.
void DisableDevice();
// Instances of all states.
std::unordered_map<State, std::unique_ptr<StateBase>> states_;
// Current state.
StateBase* state_;
// The descriptor associated with the device.
std::unique_ptr<CecFd> fd_;
// Path to the device, for logging purposes only.
const base::FilePath device_path_;
// Current physical address.
uint16_t physical_address_ = CEC_PHYS_ADDR_INVALID;
// Current logical address.
uint8_t logical_address_ = CEC_LOG_ADDR_INVALID;
// TV logical address.
uint8_t tv_logical_address_ = CEC_LOG_ADDR_INVALID;
// In case we don't get a proper response from any tried TV addresses
// (0 or 14), we will fall back on that address.
uint8_t assummed_tv_logical_address_ = CEC_LOG_ADDR_TV;
// Queue of messages we are about to send.
std::deque<CecTransmission> message_queue_;
// Messages which have been sent and are awaiting response.
// Should have a non-zero sequence number.
std::list<CecTransmission> messages_in_flight_;
// Flag indicating if we believe we are the active source.
bool active_source_ = false;
// Flag saying if we should probe the TV address if it is unknown.
bool probe_tv_address_if_unknown_ = true;
base::WeakPtrFactory<Impl> weak_factory_{this};
};
// Maximum size of a cec device's queue with outgoing messages, roughly
// 10 secs of continus flow of messages.
//
// Extern to make it avaiable to UTs.
extern const size_t kCecDeviceMaxTxQueueSize = 250;
namespace {
struct cec_msg CreateMessage(uint8_t destination_address) {
struct cec_msg message;
cec_msg_init(&message, CEC_LOG_ADDR_UNREGISTERED, destination_address);
return message;
}
void SetMessageSourceAddress(uint8_t source_address, struct cec_msg* msg) {
if (source_address == CEC_LOG_ADDR_INVALID) {
source_address = CEC_LOG_ADDR_UNREGISTERED;
}
msg->msg[0] = (source_address << 4) | cec_msg_destination(msg);
}
void SetMessageDestinationAddress(uint8_t dest_address, struct cec_msg* msg) {
msg->msg[0] = (cec_msg_initiator(msg) << 4) | dest_address;
}
TvPowerStatus GetPowerStatus(const struct cec_msg& msg) {
uint8_t power_status;
cec_ops_report_power_status(&msg, &power_status);
switch (power_status) {
case CEC_OP_POWER_STATUS_ON:
return kTvPowerStatusOn;
case CEC_OP_POWER_STATUS_STANDBY:
return kTvPowerStatusStandBy;
case CEC_OP_POWER_STATUS_TO_ON:
return kTvPowerStatusToOn;
case CEC_OP_POWER_STATUS_TO_STANDBY:
return kTvPowerStatusToStandBy;
default:
return kTvPowerStatusUnknown;
}
}
} // namespace
// Bases class for all states the CecDevice object can be in.
class StateBase {
public:
// Assigned the device imp object to object.
void Init(CecDeviceImpl::Impl* device);
// Called when the state is being entered.
virtual void Enter();
// Called when 'get TV power status' request is made by a user.
virtual void GetTvPowerStatus(
CecDevice::GetTvPowerStatusCallback callback) = 0;
// Called to learn if the state wants to write sth out on the fd.
virtual bool NeedsWrite() = 0;
// Called when Stand By request is made by a user.
virtual void SetStandBy(CecDevice::PowerChangeSentCallback callback) = 0;
// Called when Stand By request is made by a user.
virtual void SetWakeUp(CecDevice::PowerChangeSentCallback callback) = 0;
// Called when a message is sent (or response received),
// should return true if the message was handled by the state and false
// otherwise.
virtual bool ProcessResponse(const cec_msg& msg);
// Called whenever fd is available for writing.
virtual bool ProcessWrite();
// Called whenever an event saying that the kernel is dropping messages is
// recevied.
virtual void ProcessMessagesLostEvent();
StateBase();
StateBase(const StateBase&) = delete;
StateBase& operator=(const StateBase&) = delete;
virtual ~StateBase();
protected:
CecDeviceImpl::Impl* device_;
};
// The state the object transitions to whenever an unrecoverable
// error is encountered. We do nothing in this state.
class DisabledState : public StateBase {
public:
// StateBase overrides:
void GetTvPowerStatus(CecDevice::GetTvPowerStatusCallback callback) override;
bool NeedsWrite() override;
void SetStandBy(CecDevice::PowerChangeSentCallback callback) override;
void SetWakeUp(CecDevice::PowerChangeSentCallback callback) override;
bool ProcessWrite() override;
};
// The state when we don't have a physical address. The only action performed
// by this state is an attempt to send 'image view on' request when requested
// to set wake up.
class StartState : public StateBase {
public:
// StateBase overrides:
void GetTvPowerStatus(CecDevice::GetTvPowerStatusCallback callback) override;
bool NeedsWrite() override;
void SetStandBy(CecDevice::PowerChangeSentCallback callback) override;
void SetWakeUp(CecDevice::PowerChangeSentCallback callback) override;
};
// The state where we don't have a logical address yet. All requests received
// in this state are queued up.
class NoLogicalAddressState : public StateBase {
public:
// StateBase overrides:
void GetTvPowerStatus(CecDevice::GetTvPowerStatusCallback callback) override;
bool NeedsWrite() override;
void SetStandBy(CecDevice::PowerChangeSentCallback callback) override;
void SetWakeUp(CecDevice::PowerChangeSentCallback callback) override;
};
// The state where we probe TV's address. All requests are being queued up.
class ProbingTvAddressState : public StateBase {
public:
// StateBase overrides:
void Enter() override;
void GetTvPowerStatus(CecDevice::GetTvPowerStatusCallback callback) override;
bool NeedsWrite() override;
void SetStandBy(CecDevice::PowerChangeSentCallback callback) override;
void SetWakeUp(CecDevice::PowerChangeSentCallback callback) override;
bool ProcessResponse(const cec_msg& msg) override;
bool ProcessWrite() override;
void ProcessMessagesLostEvent() override;
private:
// Completes probing, setting appropriate assumed address (if needed).
void CompleteProbing();
// Represent state TV address querying op.
enum class SubState {
kStart, // Inital state.
kProbing0, // The probe for address 0 was sent.
kProbing0Completed, // The probe for address 0 is completed.
kProbing14, // The probe for address 14 was sent.
} subState_ = SubState::kStart;
// Sequence id of the ongoing transaction.
uint32_t sequence_id_;
// In case a device has not responded but still ACKd our request, use that
// info to make a guess.
bool address_0_acked_ = false;
bool address_14_acked_ = false;
};
class ReadyState : public StateBase {
public:
// StateBase overrides:
void GetTvPowerStatus(CecDevice::GetTvPowerStatusCallback callback) override;
bool NeedsWrite() override;
void SetStandBy(CecDevice::PowerChangeSentCallback callback) override;
void SetWakeUp(CecDevice::PowerChangeSentCallback callback) override;
bool ProcessResponse(const cec_msg& msg) override;
bool ProcessWrite() override;
};
void CecDeviceImpl::Impl::RunMessageSentCallback(MessageSentCallback callback,
TvPowerStatus status) {
if (auto* cb = std::get_if<CecDevice::GetTvPowerStatusCallback>(&callback)) {
std::move(*cb).Run(status);
} else if (auto* cb =
std::get_if<CecDevice::PowerChangeSentCallback>(&callback)) {
std::move(*cb).Run();
}
}
void CecDeviceImpl::Impl::EnqueueActiveSourceMessage(
CecDevice::PowerChangeSentCallback callback) {
struct cec_msg msg = CreateMessage(CEC_LOG_ADDR_BROADCAST);
cec_msg_active_source(&msg, physical_address_);
EnqueueMessage(msg, std::move(callback));
}
void CecDeviceImpl::Impl::EnqueueImageViewOnMessage() {
struct cec_msg msg = CreateMessage(CEC_LOG_ADDR_TV);
cec_msg_image_view_on(&msg);
EnqueueMessage(msg);
}
void CecDeviceImpl::Impl::EnqueueStandByMessage(
CecDevice::PowerChangeSentCallback callback) {
struct cec_msg msg = CreateMessage(CEC_LOG_ADDR_TV);
cec_msg_standby(&msg);
EnqueueMessage(msg, std::move(callback));
}
void CecDeviceImpl::Impl::EnqueueGetTvPowerStatusMessage(
CecDevice::GetTvPowerStatusCallback callback) {
struct cec_msg msg = CreateMessage(CEC_LOG_ADDR_TV);
cec_msg_give_device_power_status(&msg, 1);
EnqueueMessage(msg, std::move(callback));
}
bool CecDeviceImpl::Impl::EnqueueMessage(struct cec_msg msg,
MessageSentCallback callback) {
if (message_queue_.size() < kCecDeviceMaxTxQueueSize) {
message_queue_.push_back({msg, std::move(callback)});
return true;
} else {
LOG(ERROR) << base::StringPrintf(
"Output queue size too large, message 0x%x not enqueued",
cec_msg_opcode(&msg));
RunMessageSentCallback(std::move(callback), kTvPowerStatusError);
return false;
}
}
void CecDeviceImpl::Impl::SetTvProbingCompleted() {
probe_tv_address_if_unknown_ = false;
}
CecDeviceImpl::CecDeviceImpl(std::unique_ptr<CecFd> fd,
const base::FilePath& device_path)
: impl_(std::make_unique<Impl>(std::move(fd), device_path)) {}
CecDeviceImpl::~CecDeviceImpl() = default;
bool CecDeviceImpl::Init() {
return impl_->Init();
}
void CecDeviceImpl::GetTvPowerStatus(
CecDevice::GetTvPowerStatusCallback callback) {
impl_->GetTvPowerStatus(std::move(callback));
}
void CecDeviceImpl::SetStandBy(CecDevice::PowerChangeSentCallback callback) {
impl_->SetStandBy(std::move(callback));
}
void CecDeviceImpl::SetWakeUp(CecDevice::PowerChangeSentCallback callback) {
impl_->SetWakeUp(std::move(callback));
}
CecDeviceImpl::Impl::Impl(std::unique_ptr<CecFd> fd,
const base::FilePath& device_path)
: fd_(std::move(fd)), device_path_(device_path) {
states_[State::kStart] = std::make_unique<StartState>();
states_[State::kNoLogicalAddress] = std::make_unique<NoLogicalAddressState>();
states_[State::kProbingTvAddress] = std::make_unique<ProbingTvAddressState>();
states_[State::kReady] = std::make_unique<ReadyState>();
states_[State::kDisabled] = std::make_unique<DisabledState>();
for (auto& kv : states_) {
kv.second->Init(this);
}
EnterState(State::kStart);
}
bool CecDeviceImpl::Impl::Init() {
if (!fd_->SetEventCallback(base::BindRepeating(
&CecDeviceImpl::Impl::OnFdEvent, weak_factory_.GetWeakPtr()))) {
DisableDevice();
return false;
}
if (!SetLogicalAddress()) {
DisableDevice();
return false;
}
return true;
}
void CecDeviceImpl::Impl::GetTvPowerStatus(
CecDevice::GetTvPowerStatusCallback callback) {
probe_tv_address_if_unknown_ = true;
state_->GetTvPowerStatus(std::move(callback));
RequestWriteWatch();
}
void CecDeviceImpl::Impl::SetStandBy(
CecDevice::PowerChangeSentCallback callback) {
probe_tv_address_if_unknown_ = true;
active_source_ = false;
state_->SetStandBy(std::move(callback));
RequestWriteWatch();
}
void CecDeviceImpl::Impl::SetWakeUp(
CecDevice::PowerChangeSentCallback callback) {
probe_tv_address_if_unknown_ = true;
active_source_ = true;
state_->SetWakeUp(std::move(callback));
RequestWriteWatch();
}
void CecDeviceImpl::Impl::RequestWriteWatch() {
if (!state_->NeedsWrite())
return;
if (!fd_->WriteWatch()) {
LOG(ERROR) << device_path_.value()
<< ": failed to request write watch on fd, disabling device";
DisableDevice();
}
}
bool CecDeviceImpl::Impl::ProcessMessagesLostEvent(
const struct cec_event_lost_msgs& event) {
LOG(WARNING) << device_path_.value() << ": received event lost message, lost "
<< event.lost_msgs << " messages";
state_->ProcessMessagesLostEvent();
// Discard all messages in flight, responding to their callbacks with an
// error.
for (auto& entry : messages_in_flight_) {
RunMessageSentCallback(std::move(entry.callback), kTvPowerStatusError);
}
messages_in_flight_.clear();
return true;
}
bool CecDeviceImpl::Impl::ProcessStateChangeEvent(
const struct cec_event_state_change& event) {
physical_address_ = event.phys_addr;
logical_address_ = CEC_LOG_ADDR_INVALID;
if (event.log_addr_mask) {
logical_address_ = ffs(event.log_addr_mask) - 1;
}
LOG(INFO) << base::StringPrintf(
"%s: state update, physical address: 0x%x logical address: 0x%x",
device_path_.value().c_str(), static_cast<uint32_t>(physical_address_),
static_cast<uint32_t>(logical_address_));
if (physical_address_ == CEC_PHYS_ADDR_INVALID) {
ClearMessages(kTvPowerStatusAdapterNotConfigured);
tv_logical_address_ = CEC_LOG_ADDR_INVALID;
assummed_tv_logical_address_ = CEC_LOG_ADDR_TV;
EnterState(State::kStart);
} else if (logical_address_ == CEC_LOG_ADDR_INVALID) {
EnterState(State::kNoLogicalAddress);
} else {
EnterState(State::kReady);
}
return true;
}
bool CecDeviceImpl::Impl::ProcessEvents() {
struct cec_event event;
if (!fd_->ReceiveEvent(&event)) {
return false;
}
switch (event.event) {
case CEC_EVENT_LOST_MSGS:
return ProcessMessagesLostEvent(event.lost_msgs);
break;
case CEC_EVENT_STATE_CHANGE:
return ProcessStateChangeEvent(event.state_change);
default:
LOG(WARNING) << base::StringPrintf("%s: unexpected cec event type: 0x%x",
device_path_.value().c_str(),
event.event);
return true;
}
}
bool CecDeviceImpl::Impl::ProcessRead() {
struct cec_msg msg;
if (!fd_->ReceiveMessage(&msg)) {
return false;
}
if (msg.sequence) {
ProcessSentMessage(msg);
} else {
ProcessIncomingMessage(&msg);
}
return true;
}
bool CecDeviceImpl::Impl::MessagesInOutputQueue() const {
return !message_queue_.empty();
}
bool CecDeviceImpl::Impl::SendNextPendingMessage() {
CHECK(!message_queue_.empty());
CecTransmission& entry = message_queue_.front();
struct cec_msg message = entry.message;
if (IsMessageDirectedToTv(message)) {
uint8_t address = tv_logical_address_;
if (address == CEC_LOG_ADDR_INVALID) {
address = assummed_tv_logical_address_;
VLOG(1) << base::StringPrintf(
"%s: Unknown TV logical address, falling back to: 0x%x",
device_path_.value().c_str(), static_cast<uint32_t>(address));
}
SetMessageDestinationAddress(address, &message);
}
CecFd::TransmitResult ret = SendMessage(&message);
if (ret == CecFd::TransmitResult::kBusy) {
return true;
}
if (ret == CecFd::TransmitResult::kOk) {
// message.sequence was filled in by SendMessage() above.
messages_in_flight_.push_back({message, std::move(entry.callback)});
} else {
RunMessageSentCallback(std::move(entry.callback), kTvPowerStatusError);
}
message_queue_.pop_front();
return ret != CecFd::TransmitResult::kError;
}
void CecDeviceImpl::Impl::ProcessPowerStatusResponse(
const struct cec_msg& msg, CecDevice::GetTvPowerStatusCallback callback) {
TvPowerStatus status;
if (cec_msg_status_is_ok(&msg) &&
cec_msg_opcode(&msg) == CEC_MSG_REPORT_POWER_STATUS) {
status = GetPowerStatus(msg);
} else {
LOG(INFO) << base::StringPrintf(
"%s: power status query failed, rx_status: 0x%x tx_status: 0x%x",
device_path_.value().c_str(), static_cast<uint32_t>(msg.rx_status),
static_cast<uint32_t>(msg.tx_status));
if (msg.tx_status & CEC_TX_STATUS_NACK) {
status = kTvPowerStatusNoTv;
} else {
status = kTvPowerStatusError;
}
}
std::move(callback).Run(status);
}
void CecDeviceImpl::Impl::ProcessReportPhysicalAddress(
const struct cec_msg& msg) {
uint16_t phys_addr;
uint8_t prim_devtype;
cec_ops_report_physical_addr(&msg, &phys_addr, &prim_devtype);
if (phys_addr != 0 || prim_devtype != CEC_OP_PRIM_DEVTYPE_TV) {
return;
}
tv_logical_address_ = cec_msg_initiator(&msg);
VLOG(1) << device_path_.value()
<< ": TV's logical address: " << uint32_t(tv_logical_address_);
}
bool CecDeviceImpl::Impl::HasTvAddress() const {
return tv_logical_address_ != CEC_LOG_ADDR_INVALID;
}
void CecDeviceImpl::Impl::ResetTvAddress() {
tv_logical_address_ = CEC_LOG_ADDR_INVALID;
}
void CecDeviceImpl::Impl::SetAssumedTVAddress(uint8_t address) {
assummed_tv_logical_address_ = address;
}
void CecDeviceImpl::Impl::ProcessSentMessage(const struct cec_msg& msg) {
// Find the message in messages_in_flight_ based on its sequence number.
auto iterator =
std::find_if(messages_in_flight_.begin(), messages_in_flight_.end(),
[&](const CecTransmission& message) {
return message.message.sequence == msg.sequence;
});
// Not all messages end up in messages_in_flight_. E.g. direct calls to
// SendMessage(), message cleared in response to a message lost event.
if (iterator != messages_in_flight_.end()) {
// Run message sent callback.
if (auto* cb = std::get_if<CecDevice::GetTvPowerStatusCallback>(
&iterator->callback)) {
ProcessPowerStatusResponse(msg, std::move(*cb));
} else if (auto* cb = std::get_if<CecDevice::PowerChangeSentCallback>(
&iterator->callback)) {
std::move(*cb).Run();
}
messages_in_flight_.erase(iterator);
}
if (state_->ProcessResponse(msg)) {
return;
}
if (cec_msg_status_is_ok(&msg)) {
VLOG(1) << base::StringPrintf("%s: successfully sent message, opcode: 0x%x",
device_path_.value().c_str(),
cec_msg_opcode(&msg));
} else {
VLOG(1) << base::StringPrintf(
"%s: failed to send message, opcode: 0x%x tx_status: 0x%x",
device_path_.value().c_str(), cec_msg_opcode(&msg),
static_cast<uint32_t>(msg.tx_status));
}
}
void CecDeviceImpl::Impl::ProcessIncomingMessage(struct cec_msg* msg) {
struct cec_msg reply;
VLOG(1) << base::StringPrintf(
"%s: received message, opcode:0x%x from:0x%x to:0x%x",
device_path_.value().c_str(), cec_msg_opcode(msg),
static_cast<unsigned>(cec_msg_initiator(msg)),
static_cast<unsigned>(cec_msg_destination(msg)));
switch (cec_msg_opcode(msg)) {
case CEC_MSG_REQUEST_ACTIVE_SOURCE:
if (active_source_) {
cec_msg_init(&reply, logical_address_, CEC_LOG_ADDR_BROADCAST);
cec_msg_active_source(&reply, physical_address_);
EnqueueMessage(std::move(reply));
}
break;
case CEC_MSG_ACTIVE_SOURCE:
if (active_source_) {
VLOG(1) << device_path_.value() << ": we ceased to be active source";
active_source_ = false;
}
break;
case CEC_MSG_GIVE_DEVICE_POWER_STATUS:
cec_msg_init(&reply, logical_address_, cec_msg_initiator(msg));
cec_msg_report_power_status(&reply, CEC_OP_POWER_STATUS_ON);
EnqueueMessage(reply);
break;
case CEC_MSG_REPORT_PHYSICAL_ADDR:
ProcessReportPhysicalAddress(*msg);
break;
case CEC_MSG_STANDBY:
// Ignore standby.
break;
case CEC_MSG_FEATURE_ABORT:
// Ignore.
break;
case CEC_MSG_REPORT_POWER_STATUS:
// This is most likely a delayed response to our power status
// request. Ingore instead of rejecting.
break;
default:
if (!cec_msg_is_broadcast(msg) &&
cec_msg_initiator(msg) != CEC_LOG_ADDR_UNREGISTERED) {
cec_msg_reply_feature_abort(msg, CEC_OP_ABORT_UNRECOGNIZED_OP);
EnqueueMessage(std::move(*msg));
}
break;
}
}
CecFd::TransmitResult CecDeviceImpl::Impl::SendMessage(struct cec_msg* msg) {
SetMessageSourceAddress(logical_address_, msg);
VLOG(1) << base::StringPrintf(
"%s: transmitting message, opcode:0x%x to:0x%x",
device_path_.value().c_str(), cec_msg_opcode(msg),
static_cast<unsigned>(cec_msg_destination(msg)));
return fd_->TransmitMessage(msg);
}
const std::string& CecDeviceImpl::Impl::GetDevicePathString() const {
return device_path_.value();
}
bool CecDeviceImpl::Impl::SetLogicalAddress() {
struct cec_log_addrs addresses = {};
if (!fd_->GetLogicalAddresses(&addresses)) {
return false;
}
// The address has already been set, so we will reuse it.
if (addresses.num_log_addrs) {
return true;
}
memset(&addresses, 0, sizeof(addresses));
addresses.cec_version = CEC_OP_CEC_VERSION_1_4;
addresses.vendor_id = CEC_VENDOR_ID_NONE;
base::strlcpy(addresses.osd_name, CECSERVICE_OSD_NAME,
sizeof(addresses.osd_name));
addresses.num_log_addrs = 1;
addresses.log_addr_type[0] = CEC_LOG_ADDR_TYPE_PLAYBACK;
addresses.primary_device_type[0] = CEC_OP_PRIM_DEVTYPE_PLAYBACK;
addresses.all_device_types[0] = CEC_OP_ALL_DEVTYPE_PLAYBACK;
addresses.flags = CEC_LOG_ADDRS_FL_ALLOW_UNREG_FALLBACK;
return fd_->SetLogicalAddresses(&addresses);
}
void CecDeviceImpl::Impl::OnFdEvent(CecFd::EventType event) {
bool ret;
switch (event) {
case CecFd::EventType::kPriorityRead:
ret = ProcessEvents();
break;
case CecFd::EventType::kRead:
ret = ProcessRead();
break;
case CecFd::EventType::kWrite:
ret = state_->ProcessWrite();
break;
}
if (!ret) {
DisableDevice();
return;
}
RequestWriteWatch();
}
bool CecDeviceImpl::Impl::IsMessageDirectedToTv(
const struct cec_msg& msg) const {
switch (cec_msg_opcode(&msg)) {
case CEC_MSG_GIVE_DEVICE_POWER_STATUS:
case CEC_MSG_STANDBY:
case CEC_MSG_IMAGE_VIEW_ON:
return true;
default:
return false;
}
}
bool CecDeviceImpl::Impl::NeedsToQueryTvAddress() const {
if (HasTvAddress()) {
return false;
}
if (!probe_tv_address_if_unknown_) {
return false;
}
CHECK(!message_queue_.empty());
return IsMessageDirectedToTv(message_queue_.front().message);
}
void CecDeviceImpl::Impl::ClearMessages(TvPowerStatus response) {
for (auto& entry : message_queue_) {
RunMessageSentCallback(std::move(entry.callback), response);
}
message_queue_.clear();
for (auto& entry : messages_in_flight_) {
RunMessageSentCallback(std::move(entry.callback), response);
}
messages_in_flight_.clear();
}
void CecDeviceImpl::Impl::EnterState(State state) {
StateBase* new_state = states_[state].get();
if (new_state == state_) {
return;
}
state_ = new_state;
state_->Enter();
}
void CecDeviceImpl::Impl::DisableDevice() {
fd_.reset();
ClearMessages(kTvPowerStatusError);
EnterState(State::kDisabled);
}
void StateBase::Enter() {}
void StateBase::Init(CecDeviceImpl::Impl* device) {
device_ = device;
}
bool StateBase::ProcessResponse(const cec_msg& msg) {
return false;
}
bool StateBase::ProcessWrite() {
return true;
}
void StateBase::ProcessMessagesLostEvent() {}
StateBase::StateBase() = default;
StateBase::~StateBase() = default;
void DisabledState::GetTvPowerStatus(
CecDevice::GetTvPowerStatusCallback callback) {
LOG(WARNING) << device_->GetDevicePathString()
<< ": device is disabled due to errors, unable to query";
std::move(callback).Run(kTvPowerStatusError);
}
bool DisabledState::NeedsWrite() {
return false;
}
void DisabledState::SetStandBy(CecDevice::PowerChangeSentCallback callback) {
LOG(WARNING) << device_->GetDevicePathString()
<< ": device is disabled due to previous errors, ignoring "
"standby request";
std::move(callback).Run();
}
void DisabledState::SetWakeUp(CecDevice::PowerChangeSentCallback callback) {
LOG(WARNING) << device_->GetDevicePathString()
<< ": device in disabled due to previous errors, ignoring wake "
"up request";
std::move(callback).Run();
}
bool DisabledState::ProcessWrite() {
return false;
}
void StartState::GetTvPowerStatus(
CecDevice::GetTvPowerStatusCallback callback) {
VLOG(1) << device_->GetDevicePathString()
<< ": not configured, not querying TV power state";
std::move(callback).Run(kTvPowerStatusAdapterNotConfigured);
}
bool StartState::NeedsWrite() {
return false;
}
void StartState::SetStandBy(CecDevice::PowerChangeSentCallback callback) {
VLOG(1) << device_->GetDevicePathString()
<< ": ignoring standby request, we are not connected";
std::move(callback).Run();
}
void StartState::SetWakeUp(CecDevice::PowerChangeSentCallback callback) {
struct cec_msg msg = CreateMessage(CEC_LOG_ADDR_TV);
cec_msg_image_view_on(&msg);
if (device_->SendMessage(&msg) != CecFd::TransmitResult::kOk) {
VLOG(1) << device_->GetDevicePathString()
<< ": failed to send image view on message while in start "
"state, we are not able to wake up this TV";
std::move(callback).Run();
} else {
device_->EnqueueActiveSourceMessage(std::move(callback));
}
}
void NoLogicalAddressState::GetTvPowerStatus(
CecDevice::GetTvPowerStatusCallback callback) {
device_->EnqueueGetTvPowerStatusMessage(std::move(callback));
}
bool NoLogicalAddressState::NeedsWrite() {
return false;
}
void NoLogicalAddressState::SetStandBy(
CecDevice::PowerChangeSentCallback callback) {
device_->EnqueueStandByMessage(std::move(callback));
}
void NoLogicalAddressState::SetWakeUp(
CecDevice::PowerChangeSentCallback callback) {
device_->EnqueueImageViewOnMessage();
device_->EnqueueActiveSourceMessage(std::move(callback));
}
void ProbingTvAddressState::Enter() {
subState_ = SubState::kStart;
address_0_acked_ = false;
address_14_acked_ = false;
}
bool ProbingTvAddressState::ProcessResponse(const cec_msg& msg) {
if (msg.sequence != sequence_id_)
return false;
if (msg.tx_status == CEC_TX_STATUS_OK) {
switch (subState_) {
case SubState::kProbing0:
address_0_acked_ = true;
break;
case SubState::kProbing14:
address_14_acked_ = true;
break;
default:
break;
}
}
if (cec_msg_status_is_ok(&msg) &&
cec_msg_opcode(&msg) == CEC_MSG_REPORT_PHYSICAL_ADDR) {
device_->ProcessReportPhysicalAddress(msg);
} else {
VLOG(1) << base::StringPrintf(
"%s: give physical address status query failed, rx_status: 0x%x "
"tx_status: 0x%x",
device_->GetDevicePathString().c_str(),
static_cast<uint32_t>(msg.rx_status),
static_cast<uint32_t>(msg.tx_status));
}
if (device_->HasTvAddress()) {
device_->EnterState(CecDeviceImpl::Impl::State::kReady);
return true;
}
switch (subState_) {
case SubState::kStart:
case SubState::kProbing0Completed:
break;
case SubState::kProbing0:
subState_ = SubState::kProbing0Completed;
break;
case SubState::kProbing14:
CompleteProbing();
break;
}
return true;
}
bool ProbingTvAddressState::ProcessWrite() {
struct cec_msg message;
switch (subState_) {
case SubState::kStart:
cec_msg_init(&message, 0, CEC_LOG_ADDR_TV);
cec_msg_give_physical_addr(&message, 1);
break;
case SubState::kProbing0Completed:
cec_msg_init(&message, 0, CEC_LOG_ADDR_SPECIFIC);
cec_msg_give_physical_addr(&message, 1);
break;
default:
return true;
}
CecFd::TransmitResult ret = device_->SendMessage(&message);
switch (ret) {
case CecFd::TransmitResult::kBusy:
return true;
case CecFd::TransmitResult::kError:
return false;
case CecFd::TransmitResult::kOk:
sequence_id_ = message.sequence;
subState_ = (subState_ == SubState::kStart) ? SubState::kProbing0
: SubState::kProbing14;
return true;
default:
if (subState_ == SubState::kStart) {
subState_ = SubState::kProbing0Completed;
} else {
CompleteProbing();
}
return true;
}
}
void ProbingTvAddressState::CompleteProbing() {
if (!device_->HasTvAddress()) {
if (address_0_acked_) {
device_->SetAssumedTVAddress(CEC_LOG_ADDR_TV);
} else if (address_14_acked_) {
device_->SetAssumedTVAddress(CEC_LOG_ADDR_SPECIFIC);
} else {
VLOG(1) << device_->GetDevicePathString() << ": failed to find a TV";
}
}
device_->SetTvProbingCompleted();
device_->EnterState(CecDeviceImpl::Impl::State::kReady);
}
bool ProbingTvAddressState::NeedsWrite() {
switch (subState_) {
case SubState::kStart:
case SubState::kProbing0Completed:
return true;
default:
return false;
}
}
void ProbingTvAddressState::ProcessMessagesLostEvent() {
LOG(WARNING) << device_->GetDevicePathString()
<< ": losing messages, giving up on probing TV address";
device_->SetTvProbingCompleted();
device_->EnterState(CecDeviceImpl::Impl::State::kReady);
}
void ProbingTvAddressState::GetTvPowerStatus(
CecDevice::GetTvPowerStatusCallback callback) {
device_->EnqueueGetTvPowerStatusMessage(std::move(callback));
}
void ProbingTvAddressState::SetStandBy(
CecDevice::PowerChangeSentCallback callback) {
device_->EnqueueStandByMessage(std::move(callback));
}
void ProbingTvAddressState::SetWakeUp(
CecDevice::PowerChangeSentCallback callback) {
device_->EnqueueImageViewOnMessage();
device_->EnqueueActiveSourceMessage(std::move(callback));
}
void ReadyState::GetTvPowerStatus(
CecDevice::GetTvPowerStatusCallback callback) {
device_->EnqueueGetTvPowerStatusMessage(std::move(callback));
}
bool ReadyState::NeedsWrite() {
return device_->MessagesInOutputQueue();
}
void ReadyState::SetStandBy(CecDevice::PowerChangeSentCallback callback) {
device_->EnqueueStandByMessage(std::move(callback));
}
void ReadyState::SetWakeUp(CecDevice::PowerChangeSentCallback callback) {
device_->EnqueueImageViewOnMessage();
device_->EnqueueActiveSourceMessage(std::move(callback));
}
bool ReadyState::ProcessResponse(const cec_msg& msg) {
if ((msg.tx_status & CEC_TX_STATUS_NACK) &&
device_->IsMessageDirectedToTv(msg) && device_->HasTvAddress()) {
device_->ResetTvAddress();
LOG(INFO) << device_->GetDevicePathString()
<< ": message directed to TV not acked. "
<< "Setting TV address to unknown";
}
return false;
}
bool ReadyState::ProcessWrite() {
if (!device_->MessagesInOutputQueue()) {
return true;
}
if (device_->NeedsToQueryTvAddress()) {
device_->EnterState(CecDeviceImpl::Impl::State::kProbingTvAddress);
return true;
}
return device_->SendNextPendingMessage();
}
CecDeviceFactoryImpl::CecDeviceFactoryImpl(const CecFdOpener* cec_fd_opener)
: cec_fd_opener_(cec_fd_opener) {}
CecDeviceFactoryImpl::~CecDeviceFactoryImpl() = default;
std::unique_ptr<CecDevice> CecDeviceFactoryImpl::Create(
const base::FilePath& path) const {
std::unique_ptr<CecFd> fd = cec_fd_opener_->Open(path, O_NONBLOCK);
if (!fd) {
return nullptr;
}
struct cec_caps caps;
if (!fd->GetCapabilities(&caps)) {
return nullptr;
}
LOG(INFO) << base::StringPrintf(
"CEC adapter: %s, driver:%s name:%s caps:0x%x", path.value().c_str(),
caps.driver, caps.name, caps.capabilities);
// At the moment the only adapters supported are the ones that:
// - handle configuration of physical address on their own (i.e. don't have
// CEC_CAP_PHYS_ADDR flag set)
// - allow us to configure logical addrresses (i.e. have CEC_CAP_LOG_ADDRS
// set)
if ((caps.capabilities & CEC_CAP_PHYS_ADDR) ||
!(caps.capabilities & CEC_CAP_LOG_ADDRS)) {
LOG(WARNING) << path.value()
<< ": device does not have required capabilities to function "
"with this service";
return nullptr;
}
uint32_t mode = CEC_MODE_EXCL_INITIATOR | CEC_MODE_EXCL_FOLLOWER;
if (!fd->SetMode(mode)) {
LOG(ERROR) << path.value()
<< ": failed to set an exclusive initiator mode on the device";
return nullptr;
}
auto device = std::make_unique<CecDeviceImpl>(std::move(fd), path);
if (!device->Init()) {
return nullptr;
}
return device;
}
} // namespace cecservice