| // 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 "diagnostics/wilco_dtc_supportd/telemetry/ec_service.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <poll.h> |
| #include <sys/eventfd.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/check.h> |
| #include <base/files/file_path.h> |
| #include <base/files/scoped_file.h> |
| #include <base/logging.h> |
| #include <base/memory/ref_counted.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/threading/thread_task_runner_handle.h> |
| |
| namespace diagnostics { |
| |
| namespace internal { |
| |
| // This is the background ("monitoring") thread delegate used by |
| // |EcService|. |
| class EcEventMonitoringThreadDelegate final |
| : public base::DelegateSimpleThread::Delegate { |
| public: |
| using OnEventAvailableCallback = |
| base::RepeatingCallback<void(const EcService::EcEvent&)>; |
| |
| // |EcService| guarantees that the unowned pointer and file descriptors |
| // outlive this delegate. This delegate will post |
| // |on_event_available_callback| on the |foreground_task_runner| when an EC |
| // event is available and it will post |on_shutdown_callback| on the |
| // |foreground_task_runner| when it is shutting down. |
| EcEventMonitoringThreadDelegate( |
| int event_fd, |
| int16_t event_fd_events, |
| int shutdown_fd, |
| scoped_refptr<base::SequencedTaskRunner> foreground_task_runner, |
| OnEventAvailableCallback on_event_available_callback, |
| base::OnceClosure on_shutdown_callback) |
| : foreground_task_runner_(foreground_task_runner), |
| on_event_available_callback_(std::move(on_event_available_callback)), |
| on_shutdown_callback_(std::move(on_shutdown_callback)) { |
| fds[0] = pollfd{event_fd, event_fd_events, 0}; |
| fds[1] = pollfd{shutdown_fd, POLLIN, 0}; |
| } |
| |
| ~EcEventMonitoringThreadDelegate() override = default; |
| |
| void Run() override { |
| while (true) { |
| int retval = |
| HANDLE_EINTR(poll(fds, 2 /* nfds */, -1 /* infinite timeout */)); |
| if (retval < 0) { |
| PLOG(ERROR) |
| << "EC event poll error. Shutting down EC monitoring thread"; |
| break; |
| } |
| if (fds[1].events & fds[1].revents) { |
| // Exit: the main thread requested our shutdown by writing data into |
| // |shutdown_fd_|. |
| break; |
| } |
| if ((fds[0].revents & POLLERR) || (fds[1].revents & POLLERR)) { |
| LOG(ERROR) << "EC event POLLERR poll error. Shutting down EC" |
| " monitoring thread"; |
| break; |
| } |
| if ((fds[0].events & fds[0].revents) == 0) { |
| // No data available for reading from |event_fd_|, so proceed to poll() |
| // to wait for new events. |
| continue; |
| } |
| |
| EcService::EcEvent ec_event; |
| ssize_t bytes_read = |
| HANDLE_EINTR(read(fds[0].fd, &ec_event, sizeof(ec_event))); |
| if (bytes_read < 0) { |
| PLOG(ERROR) |
| << "EC event read error. Shutting down EC monitoring thread"; |
| break; |
| } |
| if (bytes_read > 0) { |
| foreground_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(on_event_available_callback_, ec_event)); |
| } |
| } |
| |
| foreground_task_runner_->PostTask(FROM_HERE, |
| std::move(on_shutdown_callback_)); |
| } |
| |
| private: |
| // Pollfd array, where |fds[0]| is a real sysfs fd and |fds[1]| is a fake fd |
| // used to shutdown this monitoring thread delegate. |
| // Not owned. |
| pollfd fds[2]; |
| |
| // The |SequencedTaskRunner| this object is posting tasks to. It is accessed |
| // from the monitoring thread. |
| scoped_refptr<base::SequencedTaskRunner> foreground_task_runner_; |
| |
| OnEventAvailableCallback on_event_available_callback_; |
| base::OnceClosure on_shutdown_callback_; |
| }; |
| |
| } // namespace internal |
| |
| EcService::EcEvent::EcEvent() = default; |
| |
| EcService::EcEvent::EcEvent(uint16_t num_words_in_payload, |
| Type type, |
| const uint16_t payload[6]) |
| : size(num_words_in_payload + 1), type(type), payload{} { |
| memcpy(&this->payload, payload, |
| std::min(sizeof(this->payload), |
| num_words_in_payload * sizeof(payload[0]))); |
| } |
| |
| EcService::EcEvent::Reason EcService::EcEvent::GetReason() const { |
| if (type != Type::SYSTEM_NOTIFY) { |
| return Reason::kNonSysNotification; |
| } |
| |
| const SystemNotifyPayload::SystemNotifyFlags& flags = |
| payload.system_notify.flags; |
| switch (payload.system_notify.sub_type) { |
| case SystemNotifySubType::AC_ADAPTER: |
| if (flags.ac_adapter.cause & AcAdapterFlags::Cause::NON_WILCO_CHARGER) { |
| return Reason::kNonWilcoCharger; |
| } |
| if (flags.ac_adapter.cause & AcAdapterFlags::Cause::LOW_POWER_CHARGER) { |
| return Reason::kLowPowerCharger; |
| } |
| break; |
| case SystemNotifySubType::BATTERY: |
| if (flags.battery.cause & BatteryFlags::Cause::BATTERY_AUTH) { |
| return Reason::kBatteryAuth; |
| } |
| break; |
| case SystemNotifySubType::USB_C: |
| if (flags.usb_c.billboard & UsbCFlags::Billboard::HDMI_USBC_CONFLICT) { |
| return Reason::kDockDisplay; |
| } |
| if (flags.usb_c.dock & |
| UsbCFlags::Dock::THUNDERBOLT_UNSUPPORTED_USING_USBC) { |
| return Reason::kDockThunderbolt; |
| } |
| if (flags.usb_c.dock & UsbCFlags::Dock::INCOMPATIBLE_DOCK) { |
| return Reason::kIncompatibleDock; |
| } |
| if (flags.usb_c.dock & UsbCFlags::Dock::OVERTEMP_ERROR) { |
| return Reason::kDockError; |
| } |
| break; |
| } |
| return Reason::kSysNotification; |
| } |
| |
| size_t EcService::EcEvent::PayloadSizeInBytes() const { |
| // Guard against the case when |size| == 0. |
| uint16_t sanitized_size = std::max(size, static_cast<uint16_t>(1)); |
| return (sanitized_size - 1) * sizeof(uint16_t); |
| } |
| |
| EcService::EcService() : task_runner_(base::ThreadTaskRunnerHandle::Get()) {} |
| |
| EcService::~EcService() { |
| DCHECK(sequence_checker_.CalledOnValidSequence()); |
| DCHECK(!monitoring_thread_); |
| } |
| |
| bool EcService::Start() { |
| DCHECK(sequence_checker_.CalledOnValidSequence()); |
| DCHECK(!monitoring_thread_); |
| |
| auto event_file_path = root_dir_.Append(kEcEventFilePath); |
| event_fd_.reset(HANDLE_EINTR( |
| open(event_file_path.value().c_str(), O_RDONLY | O_NONBLOCK))); |
| if (!event_fd_.is_valid()) { |
| PLOG(ERROR) << "Unable to open sysfs event file: " |
| << event_file_path.value(); |
| return false; |
| } |
| |
| shutdown_fd_.reset(eventfd(0, EFD_NONBLOCK)); |
| if (!shutdown_fd_.is_valid()) { |
| PLOG(ERROR) << "Unable to create eventfd"; |
| return false; |
| } |
| |
| monitoring_thread_delegate_ = |
| std::make_unique<internal::EcEventMonitoringThreadDelegate>( |
| event_fd_.get(), event_fd_events_, shutdown_fd_.get(), task_runner_, |
| base::BindRepeating(&EcService::OnEventAvailable, |
| base::Unretained(this)), |
| base::BindOnce(&EcService::OnShutdown, base::Unretained(this))); |
| monitoring_thread_ = std::make_unique<base::DelegateSimpleThread>( |
| monitoring_thread_delegate_.get(), |
| "WilcoDtcSupportdEcEventMonitoring" /* name_prefix */); |
| monitoring_thread_->Start(); |
| return true; |
| } |
| |
| void EcService::ShutDown(base::Closure on_shutdown_callback) { |
| DCHECK(sequence_checker_.CalledOnValidSequence()); |
| DCHECK(on_shutdown_callback_.is_null()); |
| DCHECK(!on_shutdown_callback.is_null()); |
| |
| if (!monitoring_thread_) { |
| on_shutdown_callback.Run(); |
| return; |
| } |
| |
| on_shutdown_callback_ = on_shutdown_callback; |
| |
| ShutDownMonitoringThread(); |
| } |
| |
| EcService::GetEcTelemetryResponse EcService::GetEcTelemetry( |
| const std::string& request_payload) { |
| auto reply = GetEcTelemetryResponse(); |
| if (request_payload.empty()) { |
| LOG(ERROR) << "GetEcTelemetry request payload was empty"; |
| reply.status = GetEcTelemetryResponse::STATUS_ERROR_INPUT_PAYLOAD_EMPTY; |
| return reply; |
| } |
| if (request_payload.length() > kEcGetTelemetryPayloadMaxSize) { |
| LOG(ERROR) << "GetEcTelemetry request payload size was exceeded: " |
| << request_payload.length() << " vs " |
| << kEcGetTelemetryPayloadMaxSize << " allowed"; |
| reply.status = |
| GetEcTelemetryResponse::STATUS_ERROR_INPUT_PAYLOAD_MAX_SIZE_EXCEEDED; |
| return reply; |
| } |
| |
| base::FilePath telemetry_file_path = |
| root_dir_.Append(kEcGetTelemetryFilePath); |
| |
| // Use base::ScopedFD to operate with non-seekable files. |
| base::ScopedFD telemetry_file( |
| HANDLE_EINTR(open(telemetry_file_path.value().c_str(), O_RDWR))); |
| |
| if (!telemetry_file.is_valid()) { |
| VPLOG(2) << "GetEcTelemetry could not open the " |
| << "telemetry node: " << telemetry_file_path.value(); |
| reply.status = GetEcTelemetryResponse::STATUS_ERROR_ACCESSING_DRIVER; |
| return reply; |
| } |
| |
| int write_result = HANDLE_EINTR(write( |
| telemetry_file.get(), request_payload.c_str(), request_payload.length())); |
| if (write_result != request_payload.length()) { |
| VPLOG(2) << "GetEcTelemetry could not write request payload to the " |
| << "telemetry node: " << telemetry_file_path.value(); |
| reply.status = GetEcTelemetryResponse::STATUS_ERROR_ACCESSING_DRIVER; |
| return reply; |
| } |
| |
| // Reply payload must be empty in case of any failure. |
| char file_content[kEcGetTelemetryPayloadMaxSize]; |
| int read_result = HANDLE_EINTR( |
| read(telemetry_file.get(), file_content, kEcGetTelemetryPayloadMaxSize)); |
| if (read_result == 0) { |
| VPLOG(2) << "GetEcTelemetry could not read EC telemetry command " |
| << "response from telemetry node: " << telemetry_file_path.value(); |
| reply.status = GetEcTelemetryResponse::STATUS_ERROR_ACCESSING_DRIVER; |
| return reply; |
| } |
| |
| reply.status = GetEcTelemetryResponse::STATUS_OK; |
| reply.payload = std::string(file_content, read_result); |
| return reply; |
| } |
| |
| void EcService::AddObserver(EcService::Observer* observer) { |
| DCHECK(observer); |
| observers_.AddObserver(observer); |
| } |
| |
| void EcService::RemoveObserver(EcService::Observer* observer) { |
| DCHECK(observer); |
| observers_.RemoveObserver(observer); |
| } |
| |
| bool EcService::HasObserver(EcService::Observer* observer) { |
| DCHECK(observer); |
| return observers_.HasObserver(observer); |
| } |
| |
| void EcService::ShutDownMonitoringThread() { |
| // Due to |eventfd| documentation to invoke |poll()| on |shutdown_fd_| file |
| // descriptor we must write any 8-byte value greater than 0 except |
| // |0xffffffffffffffff|. |
| uint64_t counter = 1; |
| if (HANDLE_EINTR(write(shutdown_fd_.get(), &counter, sizeof(counter))) != |
| sizeof(counter)) { |
| PLOG(ERROR) |
| << "Unable to write data in fake fd to shutdown EC event service"; |
| } |
| } |
| |
| void EcService::OnEventAvailable(const EcEvent& ec_event) { |
| DCHECK(sequence_checker_.CalledOnValidSequence()); |
| |
| for (auto& observer : observers_) |
| observer.OnEcEvent(ec_event); |
| } |
| |
| void EcService::OnShutdown() { |
| DCHECK(sequence_checker_.CalledOnValidSequence()); |
| |
| monitoring_thread_->Join(); |
| monitoring_thread_.reset(); |
| monitoring_thread_delegate_.reset(); |
| |
| if (!on_shutdown_callback_.is_null()) { |
| on_shutdown_callback_.Run(); |
| on_shutdown_callback_.Reset(); |
| } |
| } |
| |
| } // namespace diagnostics |