blob: 6bc10f5b76a05dcec07202407c417d4176099ca7 [file] [log] [blame] [raw]
/* Copyright 2015-2026 Rivoreo
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/imgact.h>
#include <sys/imgact_elf.h>
#include <sys/proc.h>
#include <sys/sysent.h>
#include <sys/sysctl.h>
#include <sys/fbio.h>
#include <sys/malloc.h>
#include <dev/fb/fbreg.h>
#include <vm/pmap.h>
#include <vm/vm_param.h>
#include <vm/vm_map.h>
#include <vm/vm_page.h>
extern struct sysentvec elf64_freebsd_sysvec;
struct sysentvec elf64_freebsd_sysvec_fb_stack;
static Elf64_Brandinfo freebsd_brand_info_fb_stack = {
.brand = ELFOSABI_FREEBSD,
.machine = EM_X86_64,
.compat_3_brand = "FreeBSD",
.emul_path = NULL,
.interp_path = "/libexec/ld-elf-amd64-stackatfb.so.1",
.sysvec = &elf64_freebsd_sysvec_fb_stack,
.interp_newpath = NULL,
.brand_note = &elf64_freebsd_brandnote,
.flags = BI_CAN_EXEC_DYN | BI_BRAND_NOTE
};
static int (*orig_freebsd_fixup)(register_t **, struct image_params *);
static struct proc *requesting_process;
static eventhandler_tag process_exit_tag;
static int video_adapter_index = -1;
static vm_page_t *fb_fake_pages;
static unsigned long int n_fb_fake_pages;
// External reference to frame buffer list
struct fb_list_entry {
struct fb_info *fb_info;
LIST_ENTRY(fb_list_entry) fb_list;
struct cdev *fb_si;
};
LIST_HEAD(fb_list_head_t, fb_list_entry);
extern struct fb_list_head_t fb_list_head;
static int query_frame_buffer_from_video_adapter(int unit, vm_paddr_t *addr, vm_size_t *size) {
video_adapter_t *adp = vid_get_adapter(unit);
if(!adp) {
printf("stackatfb: video adapter %d not found\n", unit);
return ENODEV;
}
if(!(adp->va_flags & V_ADP_INITIALIZED)) {
printf("stackatfb: video adapter %d not initialized\n", unit);
return ENXIO;
}
video_info_t vinfo;
int e = vidd_get_info(adp, adp->va_mode, &vinfo);
if(e) {
printf("stackatfb: vidd_get_info: error %d\n", e);
return e;
}
if(!vinfo.vi_buffer || !vinfo.vi_buffer_size) {
printf("stackatfb: no frame buffer configured at video adapter %d\n",
unit);
return ENXIO;
}
*addr = vinfo.vi_buffer;
*size = vinfo.vi_buffer_size;
return 0;
}
static int query_frame_buffer_from_fb_device(int unit, vm_paddr_t *addr, vm_size_t *size) {
// TODO: use unit
struct fb_list_entry *entry;
LIST_FOREACH(entry, &fb_list_head, fb_list) {
struct fb_info *info = NULL;
if(entry->fb_info) {
info = entry->fb_info;
} else if(entry->fb_si && entry->fb_si->si_drv1) {
info = (struct fb_info *)entry->fb_si->si_drv1;
}
if(info && info->fb_pbase != 0 && info->fb_size > 0) {
*addr = info->fb_pbase;
*size = info->fb_size;
return 0;
}
}
return ENODEV;
}
static int query_frame_buffer(int unit, vm_paddr_t *addr, vm_size_t *size) {
int e = query_frame_buffer_from_video_adapter(unit, addr, size);
if(!e) return 0;
return query_frame_buffer_from_fb_device(unit, addr, size);
}
static void free_fb_fake_pages() {
while(n_fb_fake_pages > 0) {
vm_page_putfake(fb_fake_pages[--n_fb_fake_pages]);
}
free(fb_fake_pages, M_TEMP);
fb_fake_pages = NULL;
}
static int stackatfb_fixup(register_t **stack_base, struct image_params *imgp) {
uprintf("function: stackatfb_fixup(%p<%p>, %p)\n", stack_base, *stack_base, imgp);
struct proc *p = imgp->proc;
if(p == requesting_process && !n_fb_fake_pages) {
// With initializations to silence 'maybe-uninitialized' warning
vm_paddr_t fb_paddr = 0;
vm_size_t fb_size = 0;
int e = query_frame_buffer(video_adapter_index, &fb_paddr, &fb_size);
if(e) goto cleanup;
if(fb_size < PAGE_SIZE) {
printf("Frame buffer too small to use as stack memory\n");
return ENOMEM;
}
fb_size = rounddown(fb_size, PAGE_SIZE);
#if 1
vm_offset_t new_stack_top = 0x7fffff000000;
#else
vm_offset_t new_stack_top = roundup((vm_offset_t)*stack_base, PAGE_SIZE);
#endif
vm_offset_t new_stack_bottom = new_stack_top - fb_size;
// Remove any existing mapping in the new range
vm_map_remove(&p->p_vmspace->vm_map, new_stack_bottom, new_stack_top);
// Create an anonymous mapping first (reserve the address space)
e = vm_map_find(&p->p_vmspace->vm_map, NULL, 0, &new_stack_bottom, fb_size, 0,
VMFS_NO_SPACE, VM_PROT_READ | VM_PROT_WRITE, VM_PROT_ALL, 0);
if(e != KERN_SUCCESS) {
uprintf("stackatfb_fixup: vm_map_find failed: %d\n", e);
e = EFAULT;
goto cleanup;
}
unsigned long int i;
n_fb_fake_pages = atop(fb_size);
printf("stackatfb: mapping %lu pages from paddr 0x%lx to vaddr 0x%lx\n",
n_fb_fake_pages, (unsigned long int)fb_paddr, (unsigned long int)new_stack_bottom);
fb_fake_pages = malloc(sizeof(vm_page_t) * n_fb_fake_pages, M_TEMP, M_WAITOK);
for(i = 0; i < n_fb_fake_pages; i++) {
vm_offset_t va = new_stack_bottom + ptoa(i);
vm_paddr_t pa = fb_paddr + ptoa(i);
vm_page_t page = vm_page_getfake(pa, VM_MEMATTR_WRITE_COMBINING);
if(!page) {
printf("stackatfb: vm_page_getfake failed for pa=0x%lx\n", pa);
e = EFAULT;
n_fb_fake_pages = i;
goto cleanup;
}
pmap_enter(vmspace_pmap(p->p_vmspace), va, page,
VM_PROT_READ | VM_PROT_WRITE, PMAP_ENTER_WIRED, 0);
fb_fake_pages[i] = page;
}
// The old stack contains important startup data, copy them
vm_offset_t old_stack_top = roundup((vm_offset_t)*stack_base, PAGE_SIZE);
vm_size_t old_data_len = old_stack_top - (vm_offset_t)*stack_base;
void *buffer = malloc(old_data_len, M_TEMP, M_WAITOK);
e = copyin(*stack_base, buffer, old_data_len);
if(e) {
uprintf("stackatfb_fixup: copyin: error %d\n", e);
free(buffer, M_TEMP);
goto cleanup;
}
*stack_base = (register_t *)(new_stack_top - old_data_len);
e = copyout(buffer, *stack_base, old_data_len);
free(buffer, M_TEMP);
if(e) {
uprintf("stackatfb_fixup: copyout: error %d\n", e);
goto cleanup;
}
cleanup:
elf64_freebsd_sysvec.sv_fixup = orig_freebsd_fixup;
if(e) {
free_fb_fake_pages();
return e;
}
}
return orig_freebsd_fixup(stack_base, imgp);
}
static int exec_stackatfb_imgact_try(struct image_params *imgp) {
if(imgp->proc != requesting_process) return -1;
elf64_freebsd_sysvec.sv_fixup = stackatfb_fixup;
return -1;
}
static void process_exit_handler(void *arg, struct proc *p) {
if(p == requesting_process) {
requesting_process = NULL;
free_fb_fake_pages();
}
}
static int set_stack_map_sysctl_handler(SYSCTL_HANDLER_ARGS) {
int e;
if(requesting_process && requesting_process == req->td->td_proc) {
e = SYSCTL_OUT(req, &video_adapter_index, sizeof video_adapter_index);
if(e) return e;
} else {
e = req->oldptr ? ENODATA : EINVAL;
}
if(!req->newptr) return e;
if(requesting_process) return EBUSY;
if(n_fb_fake_pages) return EBUSY;
int unit;
e = SYSCTL_IN(req, &unit, sizeof unit);
if(e != 0) return e;
if(unit < 0) return EINVAL;
video_adapter_index = unit;
req->td->td_proc->p_sysent = &elf64_freebsd_sysvec_fb_stack;
requesting_process = req->td->td_proc;
return 0;
}
SYSCTL_OID(_vm, OID_AUTO, stackatfb, CTLTYPE_INT | CTLFLAG_WR, SYSCTL_NULL_INT_PTR, 0, set_stack_map_sysctl_handler, "I", "Set video adapter index for stack on framebuffer for next execve(2)");
static int stackatfb_module_event(module_t mod, int type, void *data) {
switch(type) {
case MOD_LOAD:
memcpy(&elf64_freebsd_sysvec_fb_stack, &elf64_freebsd_sysvec, sizeof elf64_freebsd_sysvec_fb_stack);
elf64_freebsd_sysvec_fb_stack.sv_flags &= ~SV_ABI_FREEBSD;
exec_sysvec_init(&elf64_freebsd_sysvec_fb_stack);
if(elf64_insert_brand_entry(&freebsd_brand_info_fb_stack) < 0) return E2BIG;
elf64_freebsd_sysvec_fb_stack.sv_imgact_try = exec_stackatfb_imgact_try;
elf64_freebsd_sysvec_fb_stack.sv_fixup = stackatfb_fixup;
orig_freebsd_fixup = elf64_freebsd_sysvec.sv_fixup;
process_exit_tag = EVENTHANDLER_REGISTER(process_exit, process_exit_handler, NULL, EVENTHANDLER_PRI_ANY);
break;
case MOD_UNLOAD:
if(requesting_process) return EBUSY;
if(elf64_brand_inuse(&freebsd_brand_info_fb_stack)) return EBUSY;
elf64_remove_brand_entry(&freebsd_brand_info_fb_stack);
if(process_exit_tag) EVENTHANDLER_DEREGISTER(process_exit, process_exit_tag);
elf64_freebsd_sysvec.sv_fixup = orig_freebsd_fixup;
break;
case MOD_SHUTDOWN:
break;
default:
return EOPNOTSUPP;
}
return 0;
}
static moduledata_t module_data = { "stackatfb", stackatfb_module_event };
DECLARE_MODULE(stackatfb, module_data, SI_SUB_EXEC, SI_ORDER_ANY);
MODULE_VERSION(stackatfb, 1);