display: Reallocate framebuffer if required for wanted display mode

If an explicitly specified display mode exceeds the allocated
framebuffer allocate a new one from the top of RAM.

Note: macOS panics immediately with a realloced framebuffer.

Signed-off-by: Janne Grunau <j@jannau.net>
This commit is contained in:
Janne Grunau 2022-03-27 16:24:43 +02:00 committed by Hector Martin
parent 1ea31ae996
commit 7f4347ebe8
4 changed files with 123 additions and 30 deletions

View file

@ -17,12 +17,14 @@ dcp_dev_t *dcp_init(const char *dcp_path, const char *dcp_dart_path, const char
printf("dcp: failed to initialize DCP DART\n");
goto out_free;
}
dart_setup_pt_region(dcp->dart_dcp, dcp_dart_path, 0);
dcp->dart_disp = dart_init_adt(disp_dart_path, 0, 0, true);
if (!dcp->dart_disp) {
printf("dcp: failed to initialize DISP DART\n");
goto out_dart_dcp;
}
dart_setup_pt_region(dcp->dart_disp, disp_dart_path, 0);
dcp->iovad_dcp = iovad_init(0x10000000, 0x20000000);

View file

@ -6,6 +6,7 @@
#include "dcp.h"
#include "dcp_iboot.h"
#include "fb.h"
#include "memory.h"
#include "string.h"
#include "utils.h"
#include "xnuboot.h"
@ -41,13 +42,16 @@ static void display_choose_timing_mode(dcp_timing_mode_t *modes, int cnt, dcp_ti
for (int i = 1; i < cnt; i++) {
COMPARE(modes[i].valid, best->valid);
COMPARE(display_mode_fb_size(&modes[i]) <= fb_size, display_mode_fb_size(best) <= fb_size);
if (want && want->valid) {
COMPARE(modes[i].width == want->width && modes[i].height == want->height,
best->width == want->width && best->height == want->height);
COMPARE(-abs((long)modes[i].fps - (long)want->fps),
-abs((long)best->fps - (long)want->fps));
} else {
COMPARE(display_mode_fb_size(&modes[i]) <= fb_size,
display_mode_fb_size(best) <= fb_size);
}
COMPARE(modes[i].width <= 1920, best->width <= 1920);
COMPARE(modes[i].height <= 1200, best->height <= 1200);
COMPARE(modes[i].fps <= 60 << 16, best->fps <= 60 << 16);
@ -107,37 +111,37 @@ int display_get_vram(u64 *paddr, u64 *size)
return 0;
}
static uintptr_t display_map_fb(u64 paddr, u64 size)
static uintptr_t display_map_fb(uintptr_t iova, u64 paddr, u64 size)
{
int ret = 0;
if (iova == 0) {
s64 iova_disp0 = 0;
s64 iova_dcp = 0;
s64 iova_disp0 = 0;
s64 iova_dcp = 0;
iova_dcp = dart_find_iova(dcp->dart_dcp, iova_dcp, size);
if (iova_dcp < 0) {
printf("display: failed to find IOVA for fb of %06zx bytes (dcp)\n", size);
return 0;
}
iova_dcp = dart_find_iova(dcp->dart_dcp, iova_dcp, size);
if (iova_dcp < 0) {
printf("display: failed to find IOVA for fb of %06zx bytes (dcp)\n", size);
return 0;
// try to map the fb to the same IOVA on disp0
iova_disp0 = dart_find_iova(dcp->dart_dcp, iova_dcp, size);
if (iova_disp0 < 0) {
printf("display: failed to find IOVA for fb of %06zx bytes (disp0)\n", size);
return 0;
}
// assume this results in the same IOVA, not sure if this is required but matches what iboot
// does on other models.
if (iova_disp0 != iova_dcp) {
printf("display: IOVA mismatch for fb between dcp (%08lx) and disp0 (%08lx)\n",
(u64)iova_dcp, (u64)iova_disp0);
return 0;
}
iova = iova_dcp;
}
// try to map the fb to the same IOVA on disp0
iova_disp0 = dart_find_iova(dcp->dart_dcp, iova_dcp, size);
if (iova_disp0 < 0) {
printf("display: failed to find IOVA for fb of %06zx bytes (disp0)\n", size);
return 0;
}
// assume this results in the same IOVA, not sure if this is required but matches what iboot
// does on other models.
if (iova_disp0 != iova_dcp) {
printf("display: IOVA mismatch for fb between dcp (%08lx) and disp0 (%08lx)\n",
(u64)iova_dcp, (u64)iova_disp0);
return 0;
}
uintptr_t iova = iova_dcp;
ret = dart_map(dcp->dart_disp, iova, (void *)paddr, size);
int ret = dart_map(dcp->dart_disp, iova, (void *)paddr, size);
if (ret < 0) {
printf("display: failed to map fb to dart-disp0\n");
return 0;
@ -178,7 +182,7 @@ static int display_start_dcp(void)
fb_dva = dart_search(dcp->dart_disp, (void *)cur_boot_args.video.base);
// framebuffer is not mapped on the M1 Ultra Mac Studio
if (!fb_dva)
fb_dva = display_map_fb(pa, size);
fb_dva = display_map_fb(0, pa, size);
if (!fb_dva) {
printf("display: failed to find display DVA\n");
dcp_shutdown(dcp);
@ -333,6 +337,76 @@ int display_configure(const char *config)
return -1;
}
u64 fb_pa = cur_boot_args.video.base;
u64 tmp_dva = 0;
size_t size =
ALIGN_UP(tbest.width * tbest.height * ((cbest.bpp + 7) / 8) + 24 * SZ_16K, SZ_16K);
if (fb_size < size) {
printf("display: current framebuffer is too small for new mode\n");
/* rtkit uses 0x10000000 as DVA offset, FB starts in the first page */
if ((s64)size > 7 * SZ_32M) {
printf("display: not enough reserved L2 DVA space for fb size 0x%zx\n", size);
return -1;
}
cur_boot_args.mem_size -= size;
fb_pa = cur_boot_args.phys_base + cur_boot_args.mem_size;
/* add guard page between RAM and framebuffer */
// TODO: update mapping?
cur_boot_args.mem_size -= SZ_16K;
memset((void *)fb_pa, 0, size);
tmp_dva = iova_alloc(dcp->iovad_dcp, size);
ret = display_map_fb(tmp_dva, fb_pa, size);
if (ret < 0) {
printf("display: failed to map new fb\n");
return -1;
}
// Swap!
u32 stride = tbest.width * 4;
ret = display_swap(tmp_dva, stride, tbest.width, tbest.height);
if (ret < 0)
return ret;
/* wait for swap durations + 1ms */
u32 delay = (((1000 << 16) + tbest.fps - 1) / tbest.fps) + 1;
mdelay(delay);
dart_unmap(dcp->dart_disp, fb_dva, fb_size);
dart_unmap(dcp->dart_dcp, fb_dva, fb_size);
ret = display_map_fb(fb_dva, fb_pa, size);
if (ret < 0) {
printf("display: failed to map new fb\n");
return -1;
}
fb_size = size;
mmu_map_framebuffer(fb_pa, fb_size);
/* update ADT with the physical address of the new framebuffer */
u64 fb_reg[2] = {fb_pa, size};
int node = adt_path_offset(adt, "vram");
if (node >= 0) {
// TODO: adt_set_reg(adt, node, "vram", fb_pa, size);?
ret = adt_setprop(adt, node, "reg", &fb_reg, sizeof(fb_reg));
if (ret < 0)
printf("display: failed to update '/vram'\n");
}
node = adt_path_offset(adt, "/chosen/carveout-memory-map");
if (node >= 0) {
// TODO: adt_set_reg(adt, node, "vram", fb_pa, size);?
ret = adt_setprop(adt, node, "region-id-14", &fb_reg, sizeof(fb_reg));
if (ret < 0)
printf("display: failed to update '/chosen/carveout-memory-map/region-id-14'\n");
}
}
// Swap!
u32 stride = tbest.width * 4;
ret = display_swap(fb_dva, stride, tbest.width, tbest.height);
@ -341,8 +415,10 @@ int display_configure(const char *config)
printf("display: swapped! (swap_id=%d)\n", ret);
if (cur_boot_args.video.stride != stride || cur_boot_args.video.width != tbest.width ||
cur_boot_args.video.height != tbest.height || cur_boot_args.video.depth != 30) {
if (fb_pa != cur_boot_args.video.base || cur_boot_args.video.stride != stride ||
cur_boot_args.video.width != tbest.width || cur_boot_args.video.height != tbest.height ||
cur_boot_args.video.depth != 30) {
cur_boot_args.video.base = fb_pa;
cur_boot_args.video.stride = stride;
cur_boot_args.video.width = tbest.width;
cur_boot_args.video.height = tbest.height;
@ -353,6 +429,13 @@ int display_configure(const char *config)
/* Update for python / subsequent stages */
memcpy((void *)boot_args_addr, &cur_boot_args, sizeof(cur_boot_args));
if (tmp_dva) {
// unmap / free temporary dva
dart_unmap(dcp->dart_disp, tmp_dva, size);
dart_unmap(dcp->dart_dcp, tmp_dva, size);
iova_free(dcp->iovad_dcp, tmp_dva, size);
}
return 1;
}

View file

@ -379,6 +379,13 @@ static void mmu_remap_ranges(void)
}
}
void mmu_map_framebuffer(u64 addr, size_t size)
{
printf("MMU: Adding UC mapping at 0x%lx (0x%zx) for framebuffer\n", addr, size);
dc_civac_range((void *)addr, size);
mmu_add_mapping(addr, addr, size, MAIR_IDX_FRAMEBUFFER, PERM_RW_EL0);
}
static void mmu_add_default_mappings(void)
{
mmu_map_mmio();

View file

@ -71,6 +71,7 @@ void mmu_init_secondary(int cpu);
void mmu_shutdown(void);
void mmu_add_mapping(u64 from, u64 to, size_t size, u8 attribute_index, u64 perms);
void mmu_rm_mapping(u64 from, size_t size);
void mmu_map_framebuffer(u64 addr, size_t size);
u64 mmu_disable(void);
void mmu_restore(u64 state);