1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
|
#include <assert.h>
#include <drm_fourcc.h>
#include <drm_mode.h>
#include <drm.h>
#include <libdisplay-info/cvt.h>
#include <libdisplay-info/edid.h>
#include <libdisplay-info/info.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wlr/util/log.h>
#include "backend/drm/drm.h"
#include "backend/drm/util.h"
int32_t calculate_refresh_rate(const drmModeModeInfo *mode) {
int32_t refresh = (mode->clock * 1000000LL / mode->htotal +
mode->vtotal / 2) / mode->vtotal;
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
refresh *= 2;
}
if (mode->flags & DRM_MODE_FLAG_DBLSCAN) {
refresh /= 2;
}
if (mode->vscan > 1) {
refresh /= mode->vscan;
}
return refresh;
}
enum wlr_output_mode_aspect_ratio get_picture_aspect_ratio(const drmModeModeInfo *mode) {
switch (mode->flags & DRM_MODE_FLAG_PIC_AR_MASK) {
case DRM_MODE_FLAG_PIC_AR_NONE:
return WLR_OUTPUT_MODE_ASPECT_RATIO_NONE;
case DRM_MODE_FLAG_PIC_AR_4_3:
return WLR_OUTPUT_MODE_ASPECT_RATIO_4_3;
case DRM_MODE_FLAG_PIC_AR_16_9:
return WLR_OUTPUT_MODE_ASPECT_RATIO_16_9;
case DRM_MODE_FLAG_PIC_AR_64_27:
return WLR_OUTPUT_MODE_ASPECT_RATIO_64_27;
case DRM_MODE_FLAG_PIC_AR_256_135:
return WLR_OUTPUT_MODE_ASPECT_RATIO_256_135;
default:
wlr_log(WLR_ERROR, "Unknown mode picture aspect ratio: %u",
mode->flags & DRM_MODE_FLAG_PIC_AR_MASK);
return WLR_OUTPUT_MODE_ASPECT_RATIO_NONE;
}
}
void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data) {
struct wlr_output *output = &conn->output;
free(output->make);
free(output->model);
free(output->serial);
output->make = NULL;
output->model = NULL;
output->serial = NULL;
struct di_info *info = di_info_parse_edid(data, len);
if (info == NULL) {
wlr_log(WLR_ERROR, "Failed to parse EDID");
return;
}
const struct di_edid *edid = di_info_get_edid(info);
const struct di_edid_vendor_product *vendor_product = di_edid_get_vendor_product(edid);
char pnp_id[] = {
vendor_product->manufacturer[0],
vendor_product->manufacturer[1],
vendor_product->manufacturer[2],
'\0',
};
const char *manu = get_pnp_manufacturer(vendor_product->manufacturer);
if (!manu) {
manu = pnp_id;
}
output->make = strdup(manu);
output->model = di_info_get_model(info);
output->serial = di_info_get_serial(info);
di_info_destroy(info);
}
const char *drm_connector_status_str(drmModeConnection status) {
switch (status) {
case DRM_MODE_CONNECTED:
return "connected";
case DRM_MODE_DISCONNECTED:
return "disconnected";
case DRM_MODE_UNKNOWNCONNECTION:
return "unknown";
}
return "<unsupported>";
}
static bool is_taken(size_t n, const uint32_t arr[static n], uint32_t key) {
for (size_t i = 0; i < n; ++i) {
if (arr[i] == key) {
return true;
}
}
return false;
}
/*
* Store all of the non-recursive state in a struct, so we aren't literally
* passing 12 arguments to a function.
*/
struct match_state {
const size_t num_conns;
const uint32_t *restrict conns;
const size_t num_crtcs;
size_t score;
size_t replaced;
uint32_t *restrict res;
uint32_t *restrict best;
const uint32_t *restrict orig;
bool exit_early;
};
/**
* Step to process a CRTC.
*
* This is a naive implementation of maximum bipartite matching.
*
* score: The number of connectors we've matched so far.
* replaced: The number of changes from the original solution.
* crtc_index: The index of the current CRTC.
*
* This tries to match a solution as close to st->orig as it can.
*
* Returns whether we've set a new best element with this solution.
*/
static bool match_connectors_with_crtcs_(struct match_state *st,
size_t score, size_t replaced, size_t crtc_index) {
// Finished
if (crtc_index >= st->num_crtcs) {
if (score > st->score ||
(score == st->score && replaced < st->replaced)) {
st->score = score;
st->replaced = replaced;
memcpy(st->best, st->res, sizeof(st->best[0]) * st->num_crtcs);
st->exit_early = (st->score == st->num_crtcs
|| st->score == st->num_conns)
&& st->replaced == 0;
return true;
} else {
return false;
}
}
bool has_best = false;
/*
* Attempt to use the current solution first, to try and avoid
* recalculating everything
*/
if (st->orig[crtc_index] != UNMATCHED && !is_taken(crtc_index, st->res, st->orig[crtc_index])) {
st->res[crtc_index] = st->orig[crtc_index];
size_t crtc_score = st->conns[st->res[crtc_index]] != 0 ? 1 : 0;
if (match_connectors_with_crtcs_(st, score + crtc_score, replaced, crtc_index + 1)) {
has_best = true;
}
}
if (st->exit_early) {
return true;
}
if (st->orig[crtc_index] != UNMATCHED) {
++replaced;
}
for (size_t candidate = 0; candidate < st->num_conns; ++candidate) {
// We tried this earlier
if (candidate == st->orig[crtc_index]) {
continue;
}
// Not compatible
if (!(st->conns[candidate] & (1 << crtc_index))) {
continue;
}
// Already taken
if (is_taken(crtc_index, st->res, candidate)) {
continue;
}
st->res[crtc_index] = candidate;
size_t crtc_score = st->conns[candidate] != 0 ? 1 : 0;
if (match_connectors_with_crtcs_(st, score + crtc_score, replaced, crtc_index + 1)) {
has_best = true;
}
if (st->exit_early) {
return true;
}
}
// Maybe this CRTC can't be matched
st->res[crtc_index] = UNMATCHED;
if (match_connectors_with_crtcs_(st, score, replaced, crtc_index + 1)) {
has_best = true;
}
return has_best;
}
void match_connectors_with_crtcs(size_t num_conns,
const uint32_t conns[static restrict num_conns],
size_t num_crtcs, const uint32_t prev_crtcs[static restrict num_crtcs],
uint32_t new_crtcs[static restrict num_crtcs]) {
uint32_t solution[num_crtcs];
for (size_t i = 0; i < num_crtcs; ++i) {
solution[i] = UNMATCHED;
}
struct match_state st = {
.num_conns = num_conns,
.num_crtcs = num_crtcs,
.score = 0,
.replaced = SIZE_MAX,
.conns = conns,
.res = solution,
.best = new_crtcs,
.orig = prev_crtcs,
.exit_early = false,
};
match_connectors_with_crtcs_(&st, 0, 0, 0);
}
void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay,
float vrefresh) {
// TODO: depending on capabilities advertised in the EDID, use reduced
// blanking if possible (and update sync polarity)
struct di_cvt_options options = {
.red_blank_ver = DI_CVT_REDUCED_BLANKING_NONE,
.h_pixels = hdisplay,
.v_lines = vdisplay,
.ip_freq_rqd = vrefresh ? vrefresh : 60,
};
struct di_cvt_timing timing;
di_cvt_compute(&timing, &options);
uint16_t hsync_start = hdisplay + timing.h_front_porch;
uint16_t vsync_start = timing.v_lines_rnd + timing.v_front_porch;
uint16_t hsync_end = hsync_start + timing.h_sync;
uint16_t vsync_end = vsync_start + timing.v_sync;
*mode = (drmModeModeInfo){
.clock = roundf(timing.act_pixel_freq * 1000),
.hdisplay = hdisplay,
.vdisplay = timing.v_lines_rnd,
.hsync_start = hsync_start,
.vsync_start = vsync_start,
.hsync_end = hsync_end,
.vsync_end = vsync_end,
.htotal = hsync_end + timing.h_back_porch,
.vtotal = vsync_end + timing.v_back_porch,
.vrefresh = roundf(timing.act_frame_rate),
.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC,
};
snprintf(mode->name, sizeof(mode->name), "%dx%d", hdisplay, vdisplay);
}
|