| /* 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); |