| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * 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 <chrono> |
| #include <ctime> |
| #include <iomanip> |
| #include <fcntl.h> |
| #include <fstream> |
| #include <log/log.h> |
| #include <memory> |
| #include <sstream> |
| #include <sys/epoll.h> |
| #include <sys/prctl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <tuple> |
| #include <unistd.h> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include <drm/msm_drm.h> |
| #include <drm/msm_drm_pp.h> |
| #include <xf86drm.h> |
| #include <xf86drmMode.h> |
| |
| #include "histogram_collector.h" |
| #include "ringbuffer.h" |
| |
| namespace { |
| |
| class ManagedFd |
| { |
| public: |
| static std::unique_ptr<ManagedFd> create(int fd) { |
| if (fd < 0) |
| return nullptr; |
| return std::unique_ptr<ManagedFd>(new ManagedFd(fd)); |
| } |
| |
| ~ManagedFd() { |
| close(drmfd_); |
| } |
| |
| operator int() const { |
| return drmfd_; |
| } |
| |
| private: |
| ManagedFd(ManagedFd const&) = delete; |
| ManagedFd& operator=(ManagedFd const&) = delete; |
| |
| ManagedFd(int fd) : drmfd_(fd) { |
| } |
| int const drmfd_ = -1; |
| }; |
| |
| class DrmResources |
| { |
| public: |
| static std::unique_ptr<DrmResources> create(int drm_fd) { |
| auto resources = drmModeGetResources(drm_fd); |
| if (!resources || !resources->connectors || !resources->crtcs || !resources->encoders) { |
| return nullptr; |
| } |
| return std::unique_ptr<DrmResources>(new DrmResources(drm_fd, resources)); |
| } |
| |
| ~DrmResources() { |
| for (auto encoder : encoders_) |
| drmModeFreeEncoder(encoder.second); |
| for (auto crtc : crtcs_ ) |
| drmModeFreeCrtc(crtc.second); |
| for (auto connector : connectors_) |
| drmModeFreeConnector(connector.second); |
| drmModeFreeResources(resources_); |
| } |
| |
| drmModeConnectorPtr find_first_connector_of_type(uint32_t type) { |
| auto connector = std::find_if(connectors_.begin(), connectors_.end(), |
| [type] (auto const& c) { return c.second->connector_type == type; }); |
| if (connector != connectors_.end()) { |
| return connector->second; |
| } |
| return nullptr; |
| } |
| |
| drmModeEncoderPtr find_encoder_by_connector_and_type(drmModeConnectorPtr con, uint32_t type) { |
| for (auto i = 0; i < con->count_encoders; i++) { |
| auto enc = encoders_.find(con->encoders[i]); |
| if (enc != encoders_.end() && (enc->second->encoder_type == type)) { |
| return enc->second; |
| } |
| } |
| return nullptr; |
| } |
| |
| bool find_histogram_supporting_crtc(int fd, drmModeEncoderPtr encoder, |
| drmModeCrtcPtr* crtc, int* histogram_ctrl, int* histogram_irq) { |
| |
| for (auto i = 0; i < resources_->count_crtcs; i++) { |
| if (!(encoder->possible_crtcs & (1 << i))) |
| continue; |
| |
| auto it = crtcs_.find(resources_->crtcs[i]); |
| if (it == crtcs_.end()) { |
| ALOGW("Could not find CRTC %i reported as possible by encoder %i", |
| resources_->crtcs[i], encoder->encoder_id); |
| continue; |
| } |
| *crtc = it->second; |
| |
| int hist_ctl_found = -1; |
| int hist_irq_found = -1; |
| auto props = drmModeObjectGetProperties(fd, (*crtc)->crtc_id, DRM_MODE_OBJECT_CRTC); |
| for (auto j = 0u; j < props->count_props; j++) { |
| auto info = drmModeGetProperty(fd, props->props[j]); |
| if (std::string(info->name) == "SDE_DSPP_HIST_CTRL_V1") { |
| hist_ctl_found = props->props[j]; |
| } |
| if (std::string(info->name) == "SDE_DSPP_HIST_IRQ_V1") { |
| hist_irq_found = props->props[j]; |
| } |
| drmModeFreeProperty(info); |
| } |
| drmModeFreeObjectProperties(props); |
| if ((hist_ctl_found != -1 ) && (hist_irq_found != -1)) { |
| *histogram_ctrl = hist_ctl_found; |
| *histogram_irq = hist_irq_found; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private: |
| DrmResources(DrmResources const&) = delete; |
| DrmResources& operator=(DrmResources const&) = delete; |
| |
| DrmResources(int drm_fd, drmModeResPtr resources) : |
| resources_(resources), |
| crtcs_(resources_->count_crtcs), |
| connectors_(resources_->count_connectors), |
| encoders_(resources_->count_encoders) { |
| |
| for (auto i = 0; i < resources_->count_connectors; i++) { |
| auto connector = drmModeGetConnector(drm_fd, resources_->connectors[i]); |
| connectors_[connector->connector_id] = connector; |
| } |
| |
| for (auto i = 0; i < resources_->count_crtcs; i++) { |
| auto crtc = drmModeGetCrtc(drm_fd, resources_->crtcs[i]); |
| crtcs_[crtc->crtc_id] = crtc; |
| } |
| |
| for (auto i = 0; i < resources_->count_encoders; i++) { |
| auto encoder = drmModeGetEncoder(drm_fd, resources_->encoders[i]); |
| encoders_[encoder->encoder_id] = encoder; |
| } |
| } |
| |
| drmModeResPtr resources_; |
| std::unordered_map<int, drmModeCrtcPtr> crtcs_; |
| std::unordered_map<int, drmModeConnectorPtr> connectors_; |
| std::unordered_map<int, drmModeEncoderPtr> encoders_; |
| }; |
| |
| // Registering DRM_EVENT_CRTC_POWER does not trigger a notification on the DRM fd. |
| struct PowerEventRegistration |
| { |
| static std::unique_ptr<PowerEventRegistration> create(int drm_fd, int crtc_id) { |
| auto r = std::unique_ptr<PowerEventRegistration>(new PowerEventRegistration(drm_fd, crtc_id)); |
| if (drmIoctl(drm_fd, DRM_IOCTL_MSM_REGISTER_EVENT, &r->req)) |
| return nullptr; |
| return r; |
| } |
| |
| ~PowerEventRegistration() { |
| drmIoctl(fd, DRM_IOCTL_MSM_DEREGISTER_EVENT, &req); |
| } |
| private: |
| PowerEventRegistration(PowerEventRegistration const&) = delete; |
| PowerEventRegistration operator=(PowerEventRegistration const&) = delete; |
| |
| PowerEventRegistration(int drm_fd, int crtc_id) : |
| fd(drm_fd) { |
| req.object_id = crtc_id; |
| req.object_type = DRM_MODE_OBJECT_CRTC; |
| req.event = DRM_EVENT_CRTC_POWER; |
| } |
| |
| int const fd; //non-owning. |
| struct drm_msm_event_req req = {}; |
| }; |
| |
| struct HistogramRAIIEnabler |
| { |
| static std::unique_ptr<HistogramRAIIEnabler> create(int fd, int crtc_id, int histogram_prop) { |
| auto hist = std::unique_ptr<HistogramRAIIEnabler>( |
| new HistogramRAIIEnabler(fd, crtc_id, histogram_prop)); |
| if (drmModeObjectSetProperty(fd, crtc_id, DRM_MODE_OBJECT_CRTC, histogram_prop, 1)) |
| return nullptr; |
| return hist; |
| } |
| |
| ~HistogramRAIIEnabler() { |
| drmModeObjectSetProperty(fd, crtc_id, DRM_MODE_OBJECT_CRTC, histogram_property, 0); |
| } |
| |
| private: |
| HistogramRAIIEnabler(HistogramRAIIEnabler const&) = delete; |
| HistogramRAIIEnabler& operator=(HistogramRAIIEnabler const&) = delete; |
| |
| HistogramRAIIEnabler(int fd, int crtc_id, int histogram_property) : |
| fd(fd), |
| crtc_id(crtc_id), |
| histogram_property(histogram_property) { |
| } |
| |
| int fd; |
| int crtc_id; |
| int histogram_property; |
| }; |
| |
| struct EventRegistration |
| { |
| static std::unique_ptr<EventRegistration> create( |
| int drm_fd, int crtc_id, int histogram_property) { |
| auto reg = std::unique_ptr<EventRegistration>( |
| new EventRegistration(drm_fd, crtc_id, histogram_property)); |
| if (!reg->property_registration || |
| drmIoctl(drm_fd, DRM_IOCTL_MSM_REGISTER_EVENT, ®->req)) |
| return nullptr; |
| return reg; |
| } |
| |
| ~EventRegistration() { |
| drmIoctl(fd, DRM_IOCTL_MSM_DEREGISTER_EVENT, &req); |
| } |
| |
| private: |
| EventRegistration(int drm_fd, int crtc_id, int histogram_property) : |
| property_registration(HistogramRAIIEnabler::create(drm_fd, crtc_id, histogram_property)), |
| fd(drm_fd) { |
| req.object_id = crtc_id; |
| req.object_type = DRM_MODE_OBJECT_CRTC; |
| req.event = DRM_EVENT_HISTOGRAM; |
| } |
| EventRegistration(EventRegistration const&) = delete; |
| EventRegistration operator&(EventRegistration const&) = delete; |
| |
| //SDE_DSPP_HIST_CTRL_V1 must be turned on before receiving events |
| std::unique_ptr<HistogramRAIIEnabler> property_registration; |
| int const fd; //non-owning. |
| struct drm_msm_event_req req = {}; |
| }; |
| |
| //These are not the DPMS enum encodings. |
| enum class CrtcPowerState |
| { |
| OFF, |
| ON, |
| UNKNOWN |
| }; |
| |
| constexpr static auto implementation_defined_max_frame_ringbuffer = 300; |
| } |
| |
| histogram::HistogramCollector::HistogramCollector() : |
| histogram(histogram::Ringbuffer::create( |
| implementation_defined_max_frame_ringbuffer, std::make_unique<histogram::DefaultTimeKeeper>())) { |
| } |
| |
| histogram::HistogramCollector::~HistogramCollector() { |
| stop(); |
| } |
| |
| namespace { |
| static constexpr size_t numBuckets = 8; |
| static_assert((HIST_V_SIZE % numBuckets) == 0, |
| "histogram cannot be rebucketed to smaller number of buckets"); |
| static constexpr int bucket_compression = HIST_V_SIZE / numBuckets; |
| |
| std::array<uint64_t, numBuckets> rebucketTo8Buckets(std::array<uint64_t, HIST_V_SIZE> const& frame) { |
| std::array<uint64_t, numBuckets> bins; |
| bins.fill(0); |
| for (auto i = 0u; i < HIST_V_SIZE; i++) |
| bins[i / bucket_compression] += frame[i]; |
| return bins; |
| } |
| } |
| |
| std::string histogram::HistogramCollector::Dump() const { |
| uint64_t num_frames; |
| std::array<uint64_t, HIST_V_SIZE> all_sample_buckets; |
| std::tie(num_frames, all_sample_buckets) = histogram->collect_cumulative(); |
| std::array<uint64_t, numBuckets> samples = rebucketTo8Buckets(all_sample_buckets); |
| |
| std::stringstream ss; |
| ss << "Color Sampling, dark (0.0) to light (1.0): sampled frames: " << num_frames << '\n'; |
| if (num_frames == 0) { |
| ss << "\tno color statistics collected\n"; |
| return ss.str(); |
| } |
| |
| ss << std::fixed << std::setprecision(3); |
| ss << "\tbucket\t\t: # of displayed pixels at bucket value\n"; |
| for (auto i = 0u; i < samples.size(); i++) { |
| ss << "\t" << i / static_cast<float>(samples.size()) << |
| " to " << ( i + 1 ) / static_cast<float>(samples.size()) << "\t: " << |
| samples[i] << '\n'; |
| } |
| |
| return ss.str(); |
| } |
| |
| HWC2::Error histogram::HistogramCollector::collect( |
| uint64_t max_frames, |
| uint64_t timestamp, |
| int32_t out_samples_size[NUM_HISTOGRAM_COLOR_COMPONENTS], |
| uint64_t* out_samples[NUM_HISTOGRAM_COLOR_COMPONENTS], |
| uint64_t* out_num_frames) const { |
| |
| if (!out_samples_size || !out_num_frames) |
| return HWC2::Error::BadParameter; |
| |
| out_samples_size[0] = 0; |
| out_samples_size[1] = 0; |
| out_samples_size[2] = numBuckets; |
| out_samples_size[3] = 0; |
| |
| uint64_t num_frames; |
| std::array<uint64_t, HIST_V_SIZE> samples; |
| |
| if (max_frames == 0 && timestamp == 0) { |
| std::tie(num_frames, samples) = histogram->collect_cumulative(); |
| } else if (max_frames == 0) { |
| std::tie(num_frames, samples) = histogram->collect_after(timestamp); |
| } else if (timestamp == 0) { |
| std::tie(num_frames, samples) = histogram->collect_max(max_frames); |
| } else { |
| std::tie(num_frames, samples) = histogram->collect_max_after(timestamp, max_frames); |
| } |
| |
| auto samples_rebucketed = rebucketTo8Buckets(samples); |
| *out_num_frames = num_frames; |
| if (out_samples && out_samples[2]) |
| memcpy(out_samples[2], samples_rebucketed.data(), sizeof(uint64_t) * samples_rebucketed.size()); |
| |
| return HWC2::Error::None; |
| } |
| |
| HWC2::Error histogram::HistogramCollector::getAttributes(int32_t* format, |
| int32_t* dataspace, |
| uint8_t* supported_components) const { |
| if (!format || !dataspace || !supported_components) |
| return HWC2::Error::BadParameter; |
| |
| *format = HAL_PIXEL_FORMAT_HSV_888; |
| *dataspace = HAL_DATASPACE_UNKNOWN; |
| *supported_components = HWC2_FORMAT_COMPONENT_2; |
| return HWC2::Error::None; |
| } |
| |
| void histogram::HistogramCollector::start() { |
| start(implementation_defined_max_frame_ringbuffer); |
| } |
| |
| void histogram::HistogramCollector::start(uint64_t max_frames) { |
| std::unique_lock<decltype(thread_control)> lk(thread_control); |
| if (started) { |
| return; |
| } |
| |
| if (pipe2(selfpipe, O_CLOEXEC | O_NONBLOCK )) { |
| ALOGE("histogram thread not started, could not create control pipe."); |
| return; |
| } |
| histogram = histogram::Ringbuffer::create(max_frames, std::make_unique<histogram::DefaultTimeKeeper>()); |
| monitoring_thread = std::thread(&HistogramCollector::collecting_thread, this, selfpipe[0]); |
| started = true; |
| } |
| |
| void histogram::HistogramCollector::stop() { |
| std::unique_lock<decltype(thread_control)> lk(thread_control); |
| if (!started) { |
| return; |
| } |
| |
| char dummy = 's'; |
| write(selfpipe[1], &dummy, 1); |
| if (monitoring_thread.joinable()) |
| monitoring_thread.join(); |
| close(selfpipe[0]); |
| close(selfpipe[1]); |
| started = false; |
| } |
| |
| void histogram::HistogramCollector::collecting_thread(int selfpipe) { |
| if (prctl(PR_SET_NAME, "histogram-collector", 0, 0, 0)) |
| ALOGW("could not set thread name for histogram collector."); |
| |
| int const control_minor_version { 64 }; |
| auto drm = ManagedFd::create(drmOpenControl(control_minor_version)); |
| if (!drm) { |
| ALOGW("could not find DRM control node. Histogram collection disabled."); |
| return; |
| } |
| auto drm_resources = DrmResources::create(*drm); |
| if (!drm_resources) { |
| ALOGW("could not get DRM resources. Histogram collection disabled."); |
| return; |
| } |
| |
| //Find the connector and encoder on the DSI. Check the possible CRTCs for support |
| //for the histogram property. |
| auto connector = drm_resources->find_first_connector_of_type(DRM_MODE_CONNECTOR_DSI); |
| if (!connector) { |
| ALOGE("Could not find connector. Histogram collection disabled."); |
| return; |
| } |
| |
| auto encoder = drm_resources->find_encoder_by_connector_and_type( |
| connector, DRM_MODE_ENCODER_DSI); |
| if (!encoder) { |
| ALOGE("Could not find encoder. Histogram collection disabled."); |
| return; |
| } |
| |
| auto histogram_property = -1; |
| auto histogram_irq = -1; |
| drmModeCrtcPtr crtc = nullptr; |
| if (!drm_resources->find_histogram_supporting_crtc( |
| *drm, encoder, &crtc, &histogram_property, &histogram_irq)) { |
| ALOGE("Could not find CRTC that supports color sampling. Histogram collection disabled."); |
| return; |
| } |
| |
| // Set up event loop. |
| // Event loop will listen to 1) FD that exposes color sampling events (1 per displayed frame), |
| // and 2) a self-pipe that will indicate when this thread should shut down. |
| enum class EventType |
| { |
| DRM, |
| CTL, |
| NUM_EVENT_TYPES |
| }; |
| |
| struct epoll_event ev, events[static_cast<int>(EventType::NUM_EVENT_TYPES)]; |
| auto epollfd = ManagedFd::create(epoll_create1(EPOLL_CLOEXEC)); |
| if (!epollfd) { |
| ALOGE("Error creating epoll loop. Histogram collection disabled."); |
| return; |
| } |
| |
| ev.events = EPOLLIN; |
| ev.data.u32 = static_cast<uint32_t>(EventType::DRM); |
| if (epoll_ctl(*epollfd, EPOLL_CTL_ADD, *drm, &ev) == -1) { |
| ALOGE("Error adding drm fd to epoll. Histogram collection disabled."); |
| return; |
| } |
| |
| ev.events = EPOLLIN; |
| ev.data.u32 = static_cast<uint32_t>(EventType::CTL); |
| if (epoll_ctl(*epollfd, EPOLL_CTL_ADD, selfpipe, &ev) == -1) { |
| ALOGE("Error adding control fd to epoll. Histogram collection disabled."); |
| return; |
| } |
| |
| if (fcntl(*drm, F_SETFL, fcntl(*drm, F_GETFL) | O_NONBLOCK)) { |
| ALOGE("Error making drm read nonblocking. Histogram collection disabled."); |
| return; |
| } |
| |
| /* Attempting to set SDE_DSPP_HIST_CTRL_V1, SDE_DSPP_HIST_IRQ_V1, or DRM_EVENT_HISTOGRAM |
| * while the screen is off will result in an error. |
| * |
| * Since we have to wait on events (or poll the connector for power state), and then issue |
| * based on that info, there's no 100% certain way to know if enabling those histogram events |
| * are done when the screen is actually on. We work around this by retrying when those |
| * events fail, and not trying to enable those when we know the screen is off. |
| */ |
| std::unique_ptr<EventRegistration> hist_registration = nullptr; |
| CrtcPowerState state = CrtcPowerState::UNKNOWN; |
| bool collecting = true; |
| |
| auto power_registration = PowerEventRegistration::create(*drm, crtc->crtc_id); |
| if (!power_registration) { |
| ALOGE("could not register event to monitor power events. Histogram collection disabled."); |
| return; |
| } |
| |
| while (collecting) { |
| if (state != CrtcPowerState::OFF) { |
| if (!hist_registration) { |
| hist_registration = EventRegistration::create( |
| *drm, crtc->crtc_id, histogram_property); |
| } |
| |
| if (drmModeObjectSetProperty(*drm, |
| crtc->crtc_id, DRM_MODE_OBJECT_CRTC, histogram_irq, 1)) { |
| ALOGI("Failed to enable histogram property on crtc, will retry"); |
| state = CrtcPowerState::OFF; |
| hist_registration = nullptr; |
| } |
| } |
| |
| int nfds = epoll_wait(*epollfd, events, static_cast<int>(EventType::NUM_EVENT_TYPES), -1); |
| if (nfds == -1) { |
| if (errno != EINTR) |
| collecting = false; |
| continue; |
| } |
| |
| for (auto i = 0; i < nfds; i++) { |
| if (events[i].data.u32 == static_cast<uint32_t>(EventType::CTL)) { |
| collecting = false; |
| } else if (events[i].data.u32 == static_cast<uint32_t>(EventType::DRM)) { |
| //VLA has a single int as blob id, or power mode |
| char buffer[sizeof(drm_msm_event_resp) + sizeof(uint32_t)]; |
| auto size_read = read(*drm, buffer, sizeof(buffer)); |
| if (size_read != sizeof(buffer)) { |
| ALOGW("Histogram event wrong size (%zu bytes, errno: %X). Skipping event.", |
| size_read, errno); |
| continue; |
| } |
| |
| struct drm_msm_event_resp* response = |
| reinterpret_cast<struct drm_msm_event_resp*>(buffer); |
| if (response->base.type == DRM_EVENT_HISTOGRAM) { |
| uint32_t blob_id = *reinterpret_cast<uint32_t*>(response->data); |
| drmModePropertyBlobPtr blob = drmModeGetPropertyBlob(*drm, blob_id); |
| histogram->insert(*static_cast<struct drm_msm_hist*>(blob->data)); |
| drmModeFreePropertyBlob(blob); |
| } |
| |
| if (response->base.type == DRM_EVENT_CRTC_POWER) { |
| uint32_t state_raw = *reinterpret_cast<uint32_t*>(response->data); |
| state = (state_raw) ? CrtcPowerState::ON : CrtcPowerState::OFF; |
| } |
| } |
| } |
| } |
| } |