C++程序  |  557行  |  19.04 KB

/*
 * 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, &reg->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;
                }
            }
        }
    }
}