twm/src/modules/drm.c

229 lines
6.6 KiB
C

#define MODULE_NAME "drm"
#include "../twm.h"
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm_fourcc.h>
#define max(a,b) (((a)>(b))?(a):(b))
typedef struct {
uint32_t id; // Unknown use
uint32_t width;
uint32_t height;
uint32_t pitch;
uint32_t handle;
uint64_t size;
uint8_t *data;
} DRMFb;
typedef struct _DRMConnector {
uint32_t id;
bool enabled;
drmModeCrtcPtr old_mode;
uint32_t crtc_id;
drmModeModeInfo mode;
Vec2 size;
DRMFb frame;
struct _DRMConnector *next;
} DRMConnector;
typedef struct {
int drm_fd;
DRMFb frameBuffer;
DRMConnector *connectors;
} DRMState;
static DRMState *data;
uint32_t find_crtc(int fd, drmModeResPtr resources, drmModeConnectorPtr connector, uint32_t *taken) {
for (int i = 0; i < connector->count_encoders; i++) {
drmModeEncoderPtr encoder = drmModeGetEncoder(fd, connector->encoders[i]);
if (!encoder) continue;
for (int j = 0; j < resources->count_crtcs; j++) {
if ((encoder->possible_crtcs & BIT(j)) == 0) continue; // Not compatible
if ((*taken & BIT(j)) != 0) continue; // Already taken
drmModeFreeEncoder(encoder);
*taken |= BIT(j);
return resources->crtcs[j];
}
drmModeFreeEncoder(encoder);
}
LOG_WARNING("Failed to find CRTC for connector %d", connector->connector_id);
return 0;
}
bool create_fb(int fd, DRMFb *out, Vec2 size) {
if(drmModeCreateDumbBuffer(fd, size.x, size.y, 32, 0, &out->handle, &out->pitch, &out->size) < 0) return false;
uint32_t handles[4] = { out->handle };
uint32_t pitches[4] = { out->pitch };
uint32_t offsets[4] = { 0 };
if (drmModeAddFB2(fd, size.x, size.y, DRM_FORMAT_XRGB8888, handles, pitches, offsets, &out->id, 0) < 0) goto err_dumb;
uint64_t offset;
if(drmModeMapDumbBuffer(fd,out->handle, &offset) < 0) goto err_dumb;
out->data = mmap(0, out->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset);
if (out->data == MAP_FAILED) goto err_fb;
return true;
err_fb:
drmModeRmFB(fd, out->id);
err_dumb:
drmModeDestroyDumbBuffer(fd, out->handle);
return false;
}
void destroy_fb(int fd, DRMFb *fb) {
munmap(fb->data, fb->size);
drmModeRmFB(fd, fb->id);
drmModeDestroyDumbBuffer(fd, fb->handle);
}
void display_put_pixel(Vec2 position, Color col) {
DRMConnector *conn = data->connectors;
while (conn) {
if (conn->enabled) {
if (position.x < 0 || position.x >= conn->size.x || position.y < 0 || position.y >= conn->size.y) continue;
uint8_t *row = conn->frame.data + position.y*conn->frame.pitch;
row[position.x * 4 + 0] = col.blue;
row[position.x * 4 + 1] = col.green;
row[position.x * 4 + 2] = col.red;
row[position.x * 4 + 3] = 0x00;
}
conn = conn->next;
}
}
void display_blit(Vec2 pos, Image image) {
for (int x = 0; x < image.size.x; x++) {
for (int y = 0; y < image.size.y; y++) {
display_put_pixel((Vec2){x+pos.x,y+pos.y}, image.data[y][x]);
}
}
}
void display_flip() {
DRMConnector *conn = data->connectors;
while (conn) {
if (conn->enabled) {
drmModeClip clip = (drmModeClip){.x1 = 0, .y1 = 0, .x2 = conn->frame.width, .y2 = conn->frame.height};
drmModeDirtyFB(data->drm_fd, conn->frame.id, &clip, 1);
}
conn = conn->next;
}
}
Vec2 display_get_size() {
int maxX = 0;
int maxY = 0;
DRMConnector *conn = data->connectors;
while (conn) {
if (conn->enabled) {
maxX = max(maxX, conn->size.x);
maxY = max(maxY, conn->size.y);
}
conn = conn->next;
}
return (Vec2){maxX, maxY};
}
//--------------------------------------------------------
const uint64_t FEATURE_LISTS[] = {
BIT(BASIC_RENDERING) | BIT(ADVANCED_RENDERING),
0
};
const FeatureFunc FUNCTIONS[] = {
(FeatureFunc){ DISPLAY_PUT_PIXEL, display_put_pixel },
(FeatureFunc){ DISPLAY_BLIT, display_blit },
(FeatureFunc){ DISPLAY_GET_SIZE, display_get_size },
(FeatureFunc){ DISPLAY_FLIP, display_flip },
(FeatureFunc){ FUNC_END, NULL }
};
FeatureFunc* module_init(size_t features_selected) {
data = malloc(sizeof(DRMState));
if (data == NULL) return NULL;
data->drm_fd = open("/dev/dri/card0", O_RDWR);
drmModeResPtr resources = drmModeGetResources(data->drm_fd);
twm_assert_custom(resources, "Failed to find drm resources", return NULL);
data->connectors = NULL;
uint32_t taken_crtcs = 0;
for (int i = 0; i < resources->count_connectors; i++) {
drmModeConnectorPtr drm_connector = drmModeGetConnector(data->drm_fd, resources->connectors[i]);
twm_assert_custom(drm_connector != NULL, "Failed to open connector", continue);
DRMConnector *connector = malloc(sizeof(DRMConnector));
twm_assert_custom(connector != NULL, "Failed to allocate connector data", goto cleanup);
connector->next = data->connectors;
data->connectors = connector;
connector->id = drm_connector->connector_id;
connector->enabled = drm_connector->connection == DRM_MODE_CONNECTED;
LOG_INFO("Found connector %d (%s)", connector->id, connector->enabled?"Enabled":"Disabled");
if (!connector->enabled) goto cleanup;
twm_assert_custom(drm_connector->modes != NULL, "Connector has no modes", connector->enabled = false; goto cleanup);
connector->crtc_id = find_crtc(data->drm_fd, resources, drm_connector, &taken_crtcs);
twm_assert_custom(connector->crtc_id != 0, "Failed to find crtc", connector->enabled = false; goto cleanup);
LOG_INFO("\tFound CRTC %d", connector->crtc_id);
connector->mode = drm_connector->modes[0];
connector->size = (Vec2){connector->mode.hdisplay, connector->mode.vdisplay};
LOG_INFO("\tUsing Mode %dx%d", connector->size.x, connector->size.y);
twm_assert_custom(create_fb(data->drm_fd, &connector->frame, connector->size), "Failed to make framebuffer", connector->enabled = false; goto cleanup);
LOG_INFO("\tMade framebuffer with id %d", connector->frame.id);
connector->old_mode = drmModeGetCrtc(data->drm_fd, connector->crtc_id);
twm_assert_custom(drmModeSetCrtc(data->drm_fd, connector->crtc_id, connector->frame.id, 0, 0, &connector->id, 1, &connector->mode) >= 0, "Failed to set CRTC mode", connector->enabled = false; goto cleanup);
cleanup:
drmModeFreeConnector(drm_connector);
}
drmModeFreeResources(resources);
return (FeatureFunc*)FUNCTIONS;
}
void module_close() {
while (data->connectors) {
if (data->connectors->enabled) {
destroy_fb(data->drm_fd, &data->connectors->frame);
drmModeCrtcPtr crtc = data->connectors->old_mode;
if (crtc) {
drmModeSetCrtc(data->drm_fd, crtc->crtc_id, crtc->buffer_id,
crtc->x, crtc->y, &data->connectors->id, 1, &crtc->mode);
drmModeFreeCrtc(crtc);
}
}
DRMConnector *tmp = data->connectors->next;
free(data->connectors);
data->connectors = tmp;
}
close(data->drm_fd);
free(data);
}