blob: 92b5e9c3cdb58ef3964acc4250378c7b571bf589 [file] [log] [blame]
// Copyright 2018 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 "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/bind.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <base/strings/string_util.h>
namespace cecservice {
class StateBase;
class CecDeviceImpl::Impl {
public:
// 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.
};
Impl(std::unique_ptr<CecFd> fd, const base::FilePath& device_path);
// 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();
void SetWakeUp();
// Enters a given state.
void EnterState(State state);
// Adds active source message to the queue.
void EnqueueActiveSourceMessage();
// Adds Image View On message to the queue.
void EnqueueImageViewOnMessage();
// Adds Stand By message to the queue.
void EnqueueStandByMessage();
// Enqueues Get Power Status request.
void EnqueueGetTvPowerStatusMessage(
CecDevice::GetTvPowerStatusCallback callback);
// Enqueues a message, returns true if the message was added to the queue.
bool EnqueueMessage(struct cec_msg msg);
// 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();
// 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:
// Represents 'get TV power request' that either is to be sent or has been
// sent and awaits response.
struct RequestInFlight {
// The callback to invoke when the request completes.
CecDevice::GetTvPowerStatusCallback callback;
// Message id assigned by CEC API or 0 if the request has not been sent yet.
uint32_t sequence_id;
};
// 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. Returns false if
// the message is not a response to a previously sent request.
bool ProcessPowerStatusResponse(const struct cec_msg& msg);
// 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);
// Immediately responds to all currently ongoing queries.
void RespondToAllQueries(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;
// Queue of messages we are about to send.
std::deque<struct cec_msg> message_queue_;
// Queue of requests that are in flight.
std::list<RequestInFlight> requests_;
// 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};
DISALLOW_COPY_AND_ASSIGN(Impl);
};
// 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(uint16_t destination_address) {
struct cec_msg message;
cec_msg_init(&message, CEC_LOG_ADDR_UNREGISTERED, destination_address);
return message;
}
void SetMessageSourceAddress(uint16_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(uint16_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() = 0;
// Called when Stand By request is made by a user.
virtual void SetWakeUp() = 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();
virtual ~StateBase();
protected:
CecDeviceImpl::Impl* device_;
private:
DISALLOW_COPY_AND_ASSIGN(StateBase);
};
// 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() override;
void SetWakeUp() 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() override;
void SetWakeUp() 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() override;
void SetWakeUp() 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() override;
void SetWakeUp() override;
bool ProcessResponse(const cec_msg& msg) override;
bool ProcessWrite() override;
void ProcessMessagesLostEvent() override;
private:
// 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;
};
class ReadyState : public StateBase {
public:
// StateBase overrides:
void GetTvPowerStatus(CecDevice::GetTvPowerStatusCallback callback) override;
bool NeedsWrite() override;
void SetStandBy() override;
void SetWakeUp() override;
bool ProcessResponse(const cec_msg& msg) override;
bool ProcessWrite() override;
};
void CecDeviceImpl::Impl::EnqueueActiveSourceMessage() {
struct cec_msg msg = CreateMessage(CEC_LOG_ADDR_BROADCAST);
cec_msg_active_source(&msg, physical_address_);
EnqueueMessage(msg);
}
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() {
struct cec_msg msg = CreateMessage(CEC_LOG_ADDR_TV);
cec_msg_standby(&msg);
EnqueueMessage(msg);
}
void CecDeviceImpl::Impl::EnqueueGetTvPowerStatusMessage(
CecDevice::GetTvPowerStatusCallback callback) {
struct cec_msg msg = CreateMessage(CEC_LOG_ADDR_TV);
cec_msg_give_device_power_status(&msg, 1);
if (EnqueueMessage(msg)) {
requests_.push_back({std::move(callback), 0});
} else {
std::move(callback).Run(kTvPowerStatusError);
}
}
bool CecDeviceImpl::Impl::EnqueueMessage(struct cec_msg msg) {
if (message_queue_.size() < kCecDeviceMaxTxQueueSize) {
message_queue_.push_back(msg);
return true;
} else {
LOG(ERROR) << base::StringPrintf(
"Output queue size too large, message 0x%x not enqueued",
cec_msg_opcode(&msg));
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() {
impl_->SetStandBy();
}
void CecDeviceImpl::SetWakeUp() {
impl_->SetWakeUp();
}
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::Bind(&CecDeviceImpl::Impl::OnFdEvent,
weak_factory_.GetWeakPtr()))) {
DisableDevice();
return false;
}
if (!SetLogicalAddress()) {
DisableDevice();
return false;
}
return true;
}
void CecDeviceImpl::Impl::GetTvPowerStatus(
CecDevice::GetTvPowerStatusCallback callback) {
state_->GetTvPowerStatus(std::move(callback));
RequestWriteWatch();
}
void CecDeviceImpl::Impl::SetStandBy() {
probe_tv_address_if_unknown_ = true;
active_source_ = false;
state_->SetStandBy();
RequestWriteWatch();
}
void CecDeviceImpl::Impl::SetWakeUp() {
probe_tv_address_if_unknown_ = true;
active_source_ = true;
state_->SetWakeUp();
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();
// Respond to all ongoing power status queries with an error.
std::list<RequestInFlight> ongoing;
std::list<RequestInFlight>::iterator i = requests_.begin();
while (i != requests_.end()) {
if (i->sequence_id != 0) {
ongoing.push_back(std::move(*i));
i = requests_.erase(i);
} else {
i++;
}
}
for (auto& request : ongoing) {
std::move(request.callback).Run(kTvPowerStatusError);
}
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) {
message_queue_.clear();
tv_logical_address_ = CEC_LOG_ADDR_INVALID;
RespondToAllQueries(kTvPowerStatusAdapterNotConfigured);
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());
struct cec_msg message = message_queue_.front();
if (IsMessageDirectedToTv(message)) {
uint16_t address = tv_logical_address_;
if (address == CEC_LOG_ADDR_INVALID) {
VLOG(1) << device_path_.value()
<< ": Unknown TV logical address, falling back on 0.";
address = CEC_LOG_ADDR_TV;
}
SetMessageDestinationAddress(address, &message);
}
CecFd::TransmitResult ret = SendMessage(&message);
if (ret == CecFd::TransmitResult::kBusy) {
return true;
}
if (cec_msg_opcode(&message) == CEC_MSG_GIVE_DEVICE_POWER_STATUS) {
auto iterator = std::find_if(requests_.begin(), requests_.end(),
[](const RequestInFlight& request) {
return request.sequence_id == 0;
});
CHECK(iterator != requests_.end());
if (ret == CecFd::TransmitResult::kOk) {
iterator->sequence_id = message.sequence;
} else {
std::move(iterator->callback).Run(kTvPowerStatusError);
requests_.erase(iterator);
}
}
message_queue_.pop_front();
return ret != CecFd::TransmitResult::kError;
}
bool CecDeviceImpl::Impl::ProcessPowerStatusResponse(
const struct cec_msg& msg) {
auto iterator = std::find_if(requests_.begin(), requests_.end(),
[&](const RequestInFlight& request) {
return request.sequence_id == msg.sequence;
});
if (iterator == requests_.end()) {
return false;
}
TvPowerStatus status;
if (cec_msg_status_is_ok(&msg)) {
status = GetPowerStatus(msg);
} else {
VLOG(1) << 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(iterator->callback).Run(status);
requests_.erase(iterator);
return true;
}
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::ProcessSentMessage(const struct cec_msg& msg) {
if (state_->ProcessResponse(msg)) {
return;
}
if (ProcessPowerStatusResponse(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;
default:
if (!cec_msg_is_broadcast(msg)) {
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, "Chrome OS", 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());
}
void CecDeviceImpl::Impl::RespondToAllQueries(TvPowerStatus response) {
std::list<RequestInFlight> requests;
requests.swap(requests_);
for (auto& request : requests) {
std::move(request.callback).Run(response);
}
}
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();
message_queue_.clear();
RespondToAllQueries(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() {
LOG(WARNING) << device_->GetDevicePathString()
<< ": device is disabled due to previous errors, ignoring "
"standby request";
}
void DisabledState::SetWakeUp() {
LOG(WARNING) << device_->GetDevicePathString()
<< ": device in disabled due to previous errors, ignoring wake "
"up request";
}
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() {
VLOG(1) << device_->GetDevicePathString()
<< ": ignoring standby request, we are not connected";
}
void StartState::SetWakeUp() {
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";
} else {
device_->EnqueueActiveSourceMessage();
}
}
void NoLogicalAddressState::GetTvPowerStatus(
CecDevice::GetTvPowerStatusCallback callback) {
device_->EnqueueGetTvPowerStatusMessage(std::move(callback));
}
bool NoLogicalAddressState::NeedsWrite() {
return false;
}
void NoLogicalAddressState::SetStandBy() {
device_->EnqueueStandByMessage();
}
void NoLogicalAddressState::SetWakeUp() {
device_->EnqueueImageViewOnMessage();
}
void ProbingTvAddressState::Enter() {
subState_ = SubState::kStart;
}
bool ProbingTvAddressState::ProcessResponse(const cec_msg& msg) {
bool handled = false;
switch (cec_msg_opcode(&msg)) {
case CEC_MSG_REPORT_PHYSICAL_ADDR:
device_->ProcessReportPhysicalAddress(msg);
// fallthrough
case CEC_MSG_GIVE_PHYSICAL_ADDR:
switch (subState_) {
case SubState::kStart:
case SubState::kProbing0Completed:
break;
case SubState::kProbing0:
subState_ = SubState::kProbing0Completed;
break;
case SubState::kProbing14:
if (!device_->HasTvAddress()) {
VLOG(1) << device_->GetDevicePathString()
<< ": failed to find a TV";
device_->SetTvProbingCompleted();
device_->EnterState(CecDeviceImpl::Impl::State::kReady);
}
}
handled = true;
break;
default:
break;
}
if (device_->HasTvAddress()) {
device_->EnterState(CecDeviceImpl::Impl::State::kReady);
}
return handled;
}
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, 0);
break;
case SubState::kProbing0Completed:
cec_msg_init(&message, 0, CEC_LOG_ADDR_SPECIFIC);
cec_msg_give_physical_addr(&message, 0);
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:
subState_ = (subState_ == SubState::kStart) ? SubState::kProbing0
: SubState::kProbing14;
return true;
default:
if (subState_ == SubState::kStart) {
subState_ = SubState::kProbing0Completed;
} else {
VLOG(1) << device_->GetDevicePathString() << ": failed to find a TV";
device_->SetTvProbingCompleted();
device_->EnterState(CecDeviceImpl::Impl::State::kReady);
}
return true;
}
}
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() {
device_->EnqueueStandByMessage();
}
void ProbingTvAddressState::SetWakeUp() {
device_->EnqueueImageViewOnMessage();
}
void ReadyState::GetTvPowerStatus(
CecDevice::GetTvPowerStatusCallback callback) {
device_->EnqueueGetTvPowerStatusMessage(std::move(callback));
}
bool ReadyState::NeedsWrite() {
return device_->MessagesInOutputQueue();
}
void ReadyState::SetStandBy() {
device_->EnqueueStandByMessage();
}
void ReadyState::SetWakeUp() {
device_->EnqueueImageViewOnMessage();
device_->EnqueueActiveSourceMessage();
}
bool ReadyState::ProcessResponse(const cec_msg& msg) {
if ((msg.tx_status & CEC_TX_STATUS_NACK) &&
device_->IsMessageDirectedToTv(msg) && device_->HasTvAddress()) {
device_->ResetTvAddress();
VLOG(1) << 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