diff --git a/so3/arch/arm64/asm-offsets.c b/so3/arch/arm64/asm-offsets.c index 7eee77e69..0ff10b146 100644 --- a/so3/arch/arm64/asm-offsets.c +++ b/so3/arch/arm64/asm-offsets.c @@ -86,6 +86,11 @@ int main(void) DEFINE(OFFSET_PC, offsetof(struct cpu_regs, pc)); DEFINE(OFFSET_PSTATE, offsetof(struct cpu_regs, pstate)); DEFINE(OFFSET_TLS_USR, offsetof(struct cpu_regs, tls_usr)); +#ifdef CONFIG_AVZ + DEFINE(OFFSET_ELR_EL1, offsetof(struct cpu_regs, elr_el1)); + DEFINE(OFFSET_SPSR_EL1, offsetof(struct cpu_regs, spsr_el1)); +#endif + DEFINE(S_FRAME_SIZE, sizeof(struct cpu_regs)); BLANK(); diff --git a/so3/arch/arm64/domain.c b/so3/arch/arm64/domain.c index 39632777d..814fdb060 100644 --- a/so3/arch/arm64/domain.c +++ b/so3/arch/arm64/domain.c @@ -30,6 +30,10 @@ #include #endif +#ifdef CONFIG_SOO +#include +#endif + /** * @brief Initialize the content of the EL2 stack associated to this domain. * @@ -131,6 +135,10 @@ void __setup_dom_pgtable(struct domain *d, addr_t paddr_start, unsigned long map d->grant_pfn[i].pfn = phys_to_pfn(memslot[slotID].ipa_addr + map_size + 2 * PAGE_SIZE) + i; d->grant_pfn[i].free = true; } + + /* Set staring framebuffer ipa address after the grant pfn area */ + d->fbdev_start_pfn = phys_to_pfn(memslot[slotID].ipa_addr + map_size + 2 * PAGE_SIZE) + NR_GRANT_PFN; + fbdev_ipamap_domain(d, slotID); #endif /* CONFIG_SOO */ } diff --git a/so3/arch/arm64/exception.S b/so3/arch/arm64/exception.S index ddee4bf02..d250d3b4b 100644 --- a/so3/arch/arm64/exception.S +++ b/so3/arch/arm64/exception.S @@ -303,7 +303,7 @@ ENTRY(pre_ret_to_el1) #endif /* CONFIG_CPU_PSCI */ -// Enter macro to jump into EL1 from EL0 *or* from EL1 +// Enter macro to jump into EL2 from EL0 or from EL1 .macro prepare_to_enter_to_el2 mrs x0, elr_el2 str x0, [sp, #OFFSET_PC] @@ -313,11 +313,23 @@ ENTRY(pre_ret_to_el1) mrs x0, spsr_el2 str x0, [sp, #OFFSET_PSTATE] + + // Save EL0 register. Use OFFSET_SP as it is not used on the stack frame. + mrs x0, sp_el0 + str x0, [sp, #OFFSET_SP] + mrs x0, tpidr_el0 + str x0, [sp, #OFFSET_TLS_USR] + + // Save EL1 exception register as they may not yet be saved. + mrs x0, spsr_el1 + str x0, [sp, #OFFSET_SPSR_EL1] + mrs x0, elr_el1 + str x0, [sp, #OFFSET_ELR_EL1] + .endm -// Exit macro at the end of an exception routine -// It restores the sp_el0 as well. +// Exit macro at the end of an exception routine to restore all saved registers .macro prepare_to_exit_to_el1 ldr x0, [sp, #OFFSET_PC] msr elr_el2, x0 @@ -327,6 +339,18 @@ ENTRY(pre_ret_to_el1) ldr x0, [sp, #OFFSET_PSTATE] msr spsr_el2, x0 + + // Restore EL0 register + ldr x0, [sp, #OFFSET_SP] + msr sp_el0, x0 + ldr x0, [sp, #OFFSET_TLS_USR] + msr tpidr_el0, x0 + + // Restore EL1 exception register as they may not yet be saved. + ldr x0, [sp, #OFFSET_SPSR_EL1] + msr spsr_el1, x0 + ldr x0, [sp, #OFFSET_ELR_EL1] + msr elr_el1, x0 .endm .align 5 diff --git a/so3/arch/arm64/include/asm/mmu.h b/so3/arch/arm64/include/asm/mmu.h index e5963f767..cac4fa304 100644 --- a/so3/arch/arm64/include/asm/mmu.h +++ b/so3/arch/arm64/include/asm/mmu.h @@ -606,6 +606,15 @@ static inline int pte_type(u64 *pte) ttbr; \ }) +#ifdef CONFIG_AVZ +#define cpu_get_vttbr() \ + ({ \ + unsigned long vttbr; \ + __asm__("mrs %0, vttbr_el2" : "=r"(vttbr) : : "cc"); \ + vttbr; \ + }) +#endif + /** * Check if a virtual address is within the user space range. * @@ -703,6 +712,7 @@ void *new_root_pgtable(void); void __create_mapping(void *pgtable, addr_t virt_base, addr_t phys_base, size_t size, bool nocache, mmu_stage_t stage); void __mmu_switch_kernel(void *pgtable, bool vttbr); +void mmu_get_current_domain_pgtable(addr_t *pgtable_paddr); #endif /* CONFIG_AVZ */ diff --git a/so3/arch/arm64/include/asm/processor.h b/so3/arch/arm64/include/asm/processor.h index d136b2330..fa63a3c64 100644 --- a/so3/arch/arm64/include/asm/processor.h +++ b/so3/arch/arm64/include/asm/processor.h @@ -871,10 +871,6 @@ /* Safe value for MPIDR_EL1: Bit31:RES1, Bit30:U:0, Bit24:MT:0 */ #define SYS_MPIDR_SAFE_VAL (BIT(31)) -/* The stack must be 16-byte aligned */ - -#define S_FRAME_SIZE (8 * 36) - #ifdef __ASSEMBLY__ .macro current_cpu reg @@ -1132,6 +1128,12 @@ typedef struct __attribute__((packed, aligned(8))) cpu_regs { /* TLS is used by userspace to store thread context */ u64 tls_usr; +#ifdef CONFIG_AVZ + /* Used to keep track of EL1 exception registers in case they haven't been saved by the domain yet */ + u64 elr_el1; + u64 spsr_el1; +#endif + /* Already aligned on 16 bytes no padding required */ } cpu_regs_t; diff --git a/so3/arch/arm64/mmu.c b/so3/arch/arm64/mmu.c index 31abf8ee7..6301018e9 100644 --- a/so3/arch/arm64/mmu.c +++ b/so3/arch/arm64/mmu.c @@ -46,13 +46,16 @@ void *current_pgtable(void) */ void mmu_get_current_pgtable(addr_t *pgtable_paddr) { - int cpu; - - cpu = smp_processor_id(); - *pgtable_paddr = cpu_get_ttbr1(); } +#ifdef CONFIG_AVZ +void mmu_get_current_domain_pgtable(addr_t *pgtable_paddr) +{ + *pgtable_paddr = cpu_get_vttbr(); +} +#endif + static void alloc_init_l3(u64 *l0pgtable, addr_t addr, addr_t end, addr_t phys, bool nocache, mmu_stage_t stage) { u64 *l1pte, *l2pte, *l3pte; diff --git a/so3/arch/arm64/thread.c b/so3/arch/arm64/thread.c index 9ad64bae3..ca4730ea3 100644 --- a/so3/arch/arm64/thread.c +++ b/so3/arch/arm64/thread.c @@ -18,6 +18,7 @@ #include #include +#include /** * Set the CPU registers with thread related information diff --git a/so3/arch/arm64/virt64/include/mach/ipamap.h b/so3/arch/arm64/virt64/include/mach/ipamap.h index b4b0a85a2..6bdc9ae07 100644 --- a/so3/arch/arm64/virt64/include/mach/ipamap.h +++ b/so3/arch/arm64/virt64/include/mach/ipamap.h @@ -27,6 +27,24 @@ ipamap_t agency_ipamap[] = { .phys_addr = 0x08000000, .size = 0x3000000, }, + { + /* PCIe configuration ranges */ + .ipa_addr = 0x4010000000, + .phys_addr = 0x4010000000, + .size = 0x10000000, + }, + { + /* PCIe devices IO and 32 bits memory ranges */ + .ipa_addr = 0x10000000, + .phys_addr = 0x10000000, + .size = 0x2f000000, + }, + { + /* PCIe devices 64 bits memory ranges */ + .ipa_addr = 0x8000000000, + .phys_addr = 0x8000000000, + .size = 0x8000000000, + }, }; /** diff --git a/so3/avz/include/avz/domain.h b/so3/avz/include/avz/domain.h index 27e1ebd9e..d7d333ecc 100644 --- a/so3/avz/include/avz/domain.h +++ b/so3/avz/include/avz/domain.h @@ -117,6 +117,9 @@ struct domain { /* IPA reserved page frame numbers for mapping granted pages belonging to other domains */ grant_pfn_t grant_pfn[NR_GRANT_PFN]; + + /* IPA reserved starting page frame number for framebuffer mapping */ + addr_t fbdev_start_pfn; #endif /* CONFIG_SOO */ int processor; diff --git a/so3/avz/include/avz/fbdev_gnt.h b/so3/avz/include/avz/fbdev_gnt.h new file mode 100644 index 000000000..68a5a1457 --- /dev/null +++ b/so3/avz/include/avz/fbdev_gnt.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2026 Clément Dieperink + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef FBDEV_GNT_H +#define FBDEV_GNT_H + +#include + +/** + * IPA map for the framebuffer for the given domain, either on actual buffer + * or the fake one depending on currently shown slot. + * + * @param d pointer to the domain to map + * @param slotID Slot ID of the domain + */ +void fbdev_ipamap_domain(struct domain *d, int slotID); + +/** + * Set the actual framebuffer physical pages ranges for + * futur mapping. + * + * @param fbdev Framebuffer pfns informations. + */ +void fbdev_set_pfns(fbdev_pfns_t *fbdev); + +/** + * Change the currently shown slot ID by remapping corresponding ipa. + * + * @param new_slotID to be shown. + */ +void fbdev_change_focus(int new_slotID); + +/** + * Get the frambuffer starting ipa for the current domain. + */ +addr_t fbdev_get_domain_ipa(void); + +#endif /* FBDEV_GNT_H */ diff --git a/so3/avz/include/avz/injector.h b/so3/avz/include/avz/injector.h index cb411ba72..ea5a4a5e0 100644 --- a/so3/avz/include/avz/injector.h +++ b/so3/avz/include/avz/injector.h @@ -55,6 +55,9 @@ struct dom_context { /* IPA reserved page frame numbers for granted pages */ grant_pfn_t grant_pfn[NR_GRANT_PFN]; + /* IPA reserved starting page frame number for framebuffer mapping */ + addr_t fbdev_start_pfn; + /* Stack frame of this domain */ struct cpu_regs stack_frame; diff --git a/so3/avz/kernel/Makefile b/so3/avz/kernel/Makefile index 02d2777c4..164cdf801 100644 --- a/so3/avz/kernel/Makefile +++ b/so3/avz/kernel/Makefile @@ -16,6 +16,7 @@ obj-y += sched_flip.o obj-y += schedule.o obj-y += hypercalls.o obj-${CONFIG_SOO} += gnttab.o +obj-${CONFIG_SOO} += fbdev_gnt.o obj-y += image-fit.o obj-y += domain_utils.o diff --git a/so3/avz/kernel/fbdev_gnt.c b/so3/avz/kernel/fbdev_gnt.c new file mode 100644 index 000000000..24e031cf4 --- /dev/null +++ b/so3/avz/kernel/fbdev_gnt.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2026 Clément Dieperink + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include + +typedef struct { + fbdev_pfns_t fbdev_pfns; + void *fake_fbdev; + int current_slotID; +} fbdev_priv_t; + +static fbdev_priv_t priv = {}; + +static void __map_fbdev(struct domain *d, const fbdev_pfns_t *pfn_info) +{ + size_t i; + addr_t phys_addr; + addr_t ipa_addr; + size_t size; + void *pgtable; + + pgtable = (void *) d->pagetable_vaddr; + ipa_addr = pfn_to_phys(d->fbdev_start_pfn); + + /* Map all distincts ranges of the framebuffer to the capsule */ + for (i = 0; i < pfn_info->pfn_count; i++) { + phys_addr = pfn_to_phys(pfn_info->pfn[i]); + size = pfn_info->page_count[i] * PAGE_SIZE; + + __create_mapping(pgtable, ipa_addr, phys_addr, size, true, S2); + + ipa_addr += size; + } +} + +static void __map_fake_fbdev(struct domain *d, const fbdev_pfns_t *real_fb) +{ + size_t i, j; + addr_t phys_addr; + addr_t ipa_addr; + void *pgtable; + + /* One time malloc of fake framebuffer page */ + if (priv.fake_fbdev == NULL) { + priv.fake_fbdev = malloc(PAGE_SIZE); + BUG_ON(!priv.fake_fbdev); + } + + pgtable = (void *) d->pagetable_vaddr; + ipa_addr = pfn_to_phys(d->fbdev_start_pfn); + phys_addr = __pa(priv.fake_fbdev); + + /* Map the capsule framebuffer to the fake one */ + for (i = 0; i < real_fb->pfn_count; i++) { + for (j = 0; j < real_fb->page_count[i]; j++) { + __create_mapping(pgtable, ipa_addr, phys_addr, PAGE_SIZE, true, S2); + + ipa_addr += PAGE_SIZE; + } + } +} + +void fbdev_ipamap_domain(struct domain *d, int slotID) +{ + /* Only capsules have virtual framebuffer */ + if ((slotID < MEMSLOT_BASE) && !memslot[slotID].busy) + return; + + if (slotID == priv.current_slotID) + __map_fbdev(d, &priv.fbdev_pfns); + else + __map_fake_fbdev(d, &priv.fbdev_pfns); +} + +void fbdev_set_pfns(fbdev_pfns_t *fbdev) +{ + int slotID; + + memcpy(&priv.fbdev_pfns, fbdev, sizeof(*fbdev)); + + /* Map framebuffer to all capsules. */ + for (slotID = MEMSLOT_BASE; slotID < MEMSLOT_NR; slotID++) + if (memslot[slotID].busy) + fbdev_ipamap_domain(domains[slotID], slotID); +} + +void fbdev_change_focus(int new_slotID) +{ + /* Remap old capsule to fake framebuffer */ + if ((priv.current_slotID >= MEMSLOT_BASE) && memslot[priv.current_slotID].busy) + __map_fake_fbdev(domains[priv.current_slotID], &priv.fbdev_pfns); + + /* Map the new capsule to the framebuffer */ + if ((new_slotID >= MEMSLOT_BASE) && memslot[new_slotID].busy) + __map_fbdev(domains[new_slotID], &priv.fbdev_pfns); + + priv.current_slotID = new_slotID; +} + +addr_t fbdev_get_domain_ipa(void) +{ + return pfn_to_phys(current_domain->fbdev_start_pfn); +} diff --git a/so3/avz/kernel/hypercalls.c b/so3/avz/kernel/hypercalls.c index 263b11f07..30af35cf8 100644 --- a/so3/avz/kernel/hypercalls.c +++ b/so3/avz/kernel/hypercalls.c @@ -41,6 +41,7 @@ #ifdef CONFIG_SOO #include +#include /** * Return the state of the ME corresponding to the ME_slotID. @@ -201,6 +202,18 @@ void do_avz_hypercall(void *__args) break; } + case AVZ_FBDEV_SET_PFNS: + fbdev_set_pfns(&args->u.avz_fbdev_pfns_args.fbdev); + break; + + case AVZ_FBDEV_CHANGE_FOCUS: + fbdev_change_focus(args->u.avz_fbdev_focus_args.new_slotID); + break; + + case AVZ_FBDEV_GET_ME_ADDR: + args->u.avz_fbdev_addr_args.paddr = fbdev_get_domain_ipa(); + break; + #endif /* CONFIG_SOO */ default: diff --git a/so3/avz/kernel/injector.c b/so3/avz/kernel/injector.c index f8bb7b7f0..93c12b6fa 100644 --- a/so3/avz/kernel/injector.c +++ b/so3/avz/kernel/injector.c @@ -165,6 +165,7 @@ static void build_domain_context(unsigned int ME_slotID, struct domain *me, stru domctxt->pause_flags = me->pause_flags; memcpy(&domctxt->grant_pfn, &me->grant_pfn, sizeof(me->grant_pfn)); + memcpy(&domctxt->fbdev_start_pfn, &me->fbdev_start_pfn, sizeof(me->fbdev_start_pfn)); memcpy(&(domctxt->pause_count), &(me->pause_count), sizeof(me->pause_count)); @@ -281,6 +282,7 @@ void restore_domain_context(unsigned int ME_slotID, struct domain *me, struct do me->pause_flags = domctxt->pause_flags; memcpy(&me->grant_pfn, &domctxt->grant_pfn, sizeof(me->grant_pfn)); + memcpy(&me->fbdev_start_pfn, &domctxt->fbdev_start_pfn, sizeof(me->fbdev_start_pfn)); memcpy(&(me->pause_count), &(domctxt->pause_count), sizeof(me->pause_count)); diff --git a/so3/avz/mm/memory.c b/so3/avz/mm/memory.c index e54c0abbc..32c5af8f3 100644 --- a/so3/avz/mm/memory.c +++ b/so3/avz/mm/memory.c @@ -101,7 +101,7 @@ void switch_mm_domain(struct domain *d) { addr_t current_pgtable_paddr; - mmu_get_current_pgtable(¤t_pgtable_paddr); + mmu_get_current_domain_pgtable(¤t_pgtable_paddr); if (current_pgtable_paddr == d->pagetable_paddr) /* Check if the current page table is identical to the next one. */ diff --git a/so3/configs/virt64_capsule_defconfig b/so3/configs/virt64_capsule_defconfig index 2aa7b87c4..789ff4081 100644 --- a/so3/configs/virt64_capsule_defconfig +++ b/so3/configs/virt64_capsule_defconfig @@ -104,3 +104,4 @@ CONFIG_VUART_FRONTEND=y # CONFIG_VSENSELED_FRONTEND is not set # CONFIG_VSENSEJ_FRONTEND is not set CONFIG_VLOGS_FRONTEND=y +CONFIG_VFBDEV_FRONTEND=y diff --git a/so3/devices/fb/pl111.c b/so3/devices/fb/pl111.c index 2e7135079..0c75e86d0 100644 --- a/so3/devices/fb/pl111.c +++ b/so3/devices/fb/pl111.c @@ -106,7 +106,7 @@ int fb_mmap(int fd, addr_t virt_addr, uint32_t page_count, off_t offset) create_mapping(pcb->pgtable, virt_addr + (i * PAGE_SIZE), page, PAGE_SIZE, false); } - return virt_addr; + return 0; } int fb_ioctl(int fd, unsigned long cmd, unsigned long args) diff --git a/so3/devices/fb/ramfb.c b/so3/devices/fb/ramfb.c index d046513c9..455439fbc 100644 --- a/so3/devices/fb/ramfb.c +++ b/so3/devices/fb/ramfb.c @@ -281,7 +281,7 @@ int fb_mmap(int fd, addr_t virt_addr, uint32_t page_count, off_t offset) } #endif - return virt_addr; + return 0; } int fb_ioctl(int fd, unsigned long cmd, unsigned long args) diff --git a/so3/devices/fb/soo_fb.c b/so3/devices/fb/soo_fb.c deleted file mode 100644 index df0870511..000000000 --- a/so3/devices/fb/soo_fb.c +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2020 Nikolaos Garanis - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#if 0 -#define DEBUG -#endif - -#include -#include -#include -#include - -#include -#include - -#include -#include - -void *fb_mmap(int fd, uint32_t virt_addr, uint32_t page_count, off_t offset); -int fb_ioctl(int fd, unsigned long cmd, unsigned long args); - -struct file_operations vfb_fops = { .mmap = fb_mmap, .ioctl = fb_ioctl }; - -struct devclass vfb_cdev = { - .class = DEV_CLASS_FB, - .type = VFS_TYPE_DEV_FB, - .fops = &vfb_fops, -}; - -/* Framebuffer's physical address */ -static uint32_t fb_base = 0; - -/* Framebuffer's resolution */ -static uint32_t fb_hres; -static uint32_t fb_vres; - -int fb_init(dev_t *dev) -{ - /* Register the framebuffer so it can be accessed from user space. */ - devclass_register(dev, &vfb_cdev); - return 0; -} - -int fb_mmap(int fd, uint32_t virt_addr, uint32_t page_count, off_t offset) -{ - uint32_t i; - pcb_t *pcb = current()->pcb; - - BUG_ON(!fb_base); - - for (i = 0; i < page_count; i++) { - /* Map the process' pages to physical ones. */ - create_mapping(pcb->pgtable, virt_addr + (i * PAGE_SIZE), fb_base + i * PAGE_SIZE, PAGE_SIZE, false, false); - } - - return virt_addr; -} - -int fb_ioctl(int fd, unsigned long cmd, unsigned long args) -{ - switch (cmd) { - case IOCTL_FB_HRES: - *((uint32_t *) args) = fb_hres; - return 0; - - case IOCTL_FB_VRES: - *((uint32_t *) args) = fb_vres; - return 0; - - case IOCTL_FB_SIZE: - *((uint32_t *) args) = fb_hres * fb_vres * 4; /* assume 24bpp */ - return 0; - - default: - /* Unknown command. */ - return -1; - } -} - -void soo_fb_set_info(uint32_t base, uint32_t hres, uint32_t vres) -{ - fb_base = base; - fb_hres = hres; - fb_vres = vres; -} - -REGISTER_DRIVER_POSTCORE("fb,soo_fb", fb_init); diff --git a/so3/devices/mem.c b/so3/devices/mem.c index 716c4214e..cc03dd0e0 100644 --- a/so3/devices/mem.c +++ b/so3/devices/mem.c @@ -72,7 +72,7 @@ static int mem_mmap(int fd, addr_t virt_addr, uint32_t page_count, off_t offset) if (remaining_size > 0) create_mapping(pcb->pgtable, next_virt_addr, next_phys_addr, remaining_size, true); - return virt_addr; + return 0; } static struct file_operations mem_fops = { diff --git a/so3/dts/virt64_capsule.dts b/so3/dts/virt64_capsule.dts index 3a48cfe8f..b90966b6e 100644 --- a/so3/dts/virt64_capsule.dts +++ b/so3/dts/virt64_capsule.dts @@ -114,6 +114,12 @@ compatible = "vlogs,frontend"; status = "ok"; }; + + /* Enabling framebuffer support */ + vfbdev { + compatible = "vfbdev,frontend"; + status = "ok"; + }; }; }; diff --git a/so3/fs/vfs.c b/so3/fs/vfs.c index 45328218c..47c5e1c42 100644 --- a/so3/fs/vfs.c +++ b/so3/fs/vfs.c @@ -470,6 +470,7 @@ static int do_write(int fd, const void *buffer, size_t count) static long do_mmap(int fd, addr_t virt_addr, uint32_t page_count, off_t offset) { int gfd; + int ret; struct file_operations *fops; /* Get the fops associated to the file descriptor. */ @@ -494,8 +495,14 @@ static long do_mmap(int fd, addr_t virt_addr, uint32_t page_count, off_t offset) return -EACCES; } + if (virt_addr == 0) { + virt_addr = current()->pcb->next_anon_start; + current()->pcb->next_anon_start += page_count * PAGE_SIZE; + } + /* Call the mmap fops that will do the actual mapping. */ - return (long) fops->mmap(fd, virt_addr, page_count, offset); + ret = fops->mmap(fd, virt_addr, page_count, offset); + return (ret == 0) ? virt_addr : (long) ret; } /* Low Level mmap - Anonymous case */ diff --git a/so3/soo/drivers/Kconfig b/so3/soo/drivers/Kconfig index 6fbe26ccb..72206c751 100644 --- a/so3/soo/drivers/Kconfig +++ b/so3/soo/drivers/Kconfig @@ -22,5 +22,8 @@ config VSENSEJ_FRONTEND config VLOGS_FRONTEND bool "vlogs serial driver" +config VFBDEV_FRONTEND + bool "vfbdev framebuffer driver" + endmenu diff --git a/so3/soo/drivers/Makefile b/so3/soo/drivers/Makefile index 312a9bdcb..3dca29327 100644 --- a/so3/soo/drivers/Makefile +++ b/so3/soo/drivers/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_VUIHANDLER_FRONTEND) += vuihandlerfront/ obj-$(CONFIG_VSENSELED_FRONTEND) += vsenseledfront/ obj-$(CONFIG_VSENSEJ_FRONTEND) += vsensejfront/ obj-$(CONFIG_VLOGS_FRONTEND) += vlogsfront/ +obj-$(CONFIG_VFBDEV_FRONTEND) += vfbdevfront/ diff --git a/so3/soo/drivers/vfbdevfront/Makefile b/so3/soo/drivers/vfbdevfront/Makefile new file mode 100644 index 000000000..b3e3a22f3 --- /dev/null +++ b/so3/soo/drivers/vfbdevfront/Makefile @@ -0,0 +1 @@ +obj-y := vfbdev.o diff --git a/so3/soo/drivers/vfbdevfront/vfbdev.c b/so3/soo/drivers/vfbdevfront/vfbdev.c new file mode 100644 index 000000000..30fa11eaf --- /dev/null +++ b/so3/soo/drivers/vfbdevfront/vfbdev.c @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2026 Clément Dieperink + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#if 0 +#define DEBUG +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +typedef struct { + /* Must be the first field */ + vfbdev_t vfbdev; + + completion_t reader_wait; + + uint32_t hres; + uint32_t vres; + size_t memory_size; + addr_t fb_paddr; +} vfbdev_priv_t; + +/* Our unique vfbdev instance. */ +static struct vbus_device *vfbdev_dev = NULL; + +irq_return_t vfbdev_interrupt(int irq, void *dev_id) +{ + struct vbus_device *vdev = (struct vbus_device *) dev_id; + vfbdev_priv_t *vfbdev_priv = (vfbdev_priv_t *) dev_get_drvdata(vdev->dev); + + complete(&vfbdev_priv->reader_wait); + + return IRQ_COMPLETED; +} + +void vfbdev_probe(struct vbus_device *vdev) +{ + unsigned int evtchn; + vfbdev_sring_t *sring; + struct vbus_transaction vbt; + vfbdev_priv_t *vfbdev_priv; + + DBG0("[vfbdev] Frontend probe\n"); + + if (vdev->state == VbusStateConnected) + return; + + vfbdev_priv = dev_get_drvdata(vdev->dev); + + /* Local instance */ + vfbdev_dev = vdev; + + init_completion(&vfbdev_priv->reader_wait); + + DBG("Frontend: Setup ring\n"); + + /* Prepare to set up the ring. */ + + vfbdev_priv->vfbdev.ring_ref = GRANT_INVALID_REF; + + /* Allocate an event channel associated to the ring */ + vbus_alloc_evtchn(vdev, &evtchn); + + vfbdev_priv->vfbdev.irq = bind_evtchn_to_irq_handler(evtchn, vfbdev_interrupt, NULL, vdev); + vfbdev_priv->vfbdev.evtchn = evtchn; + + /* Allocate a shared page for the ring */ + sring = (vfbdev_sring_t *) get_free_vpage(); + if (!sring) { + lprintk("%s - line %d: Allocating shared ring failed for device %s\n", __func__, __LINE__, vdev->nodename); + BUG(); + } + + SHARED_RING_INIT(sring); + FRONT_RING_INIT(&vfbdev_priv->vfbdev.ring, sring, PAGE_SIZE); + + /* Prepare the shared to page to be visible on the other end */ + + vfbdev_priv->vfbdev.ring_ref = + vbus_grant_ring(vdev, phys_to_pfn(virt_to_phys_pt((addr_t) vfbdev_priv->vfbdev.ring.sring))); + + vbus_transaction_start(&vbt); + + vbus_printf(vbt, vdev->nodename, "ring-ref", "%u", vfbdev_priv->vfbdev.ring_ref); + vbus_printf(vbt, vdev->nodename, "ring-evtchn", "%u", vfbdev_priv->vfbdev.evtchn); + + vbus_transaction_end(vbt); +} + +/* At this point, the FE is not connected. */ +void vfbdev_reconfiguring(struct vbus_device *vdev) +{ + int res; + struct vbus_transaction vbt; + vfbdev_priv_t *vfbdev_priv = dev_get_drvdata(vdev->dev); + + DBG0("[vfbdev] Frontend reconfiguring\n"); + /* The shared page already exists */ + /* Re-init */ + + gnttab_end_foreign_access(vfbdev_priv->vfbdev.ring_ref); + + DBG("Frontend: Setup ring\n"); + + /* Prepare to set up the ring. */ + + vfbdev_priv->vfbdev.ring_ref = GRANT_INVALID_REF; + + SHARED_RING_INIT(vfbdev_priv->vfbdev.ring.sring); + FRONT_RING_INIT(&vfbdev_priv->vfbdev.ring, vfbdev_priv->vfbdev.ring.sring, PAGE_SIZE); + + /* Prepare the shared to page to be visible on the other end */ + + res = vbus_grant_ring(vdev, phys_to_pfn(virt_to_phys_pt((addr_t) vfbdev_priv->vfbdev.ring.sring))); + if (res < 0) + BUG(); + + vfbdev_priv->vfbdev.ring_ref = res; + + vbus_transaction_start(&vbt); + + vbus_printf(vbt, vdev->nodename, "ring-ref", "%u", vfbdev_priv->vfbdev.ring_ref); + vbus_printf(vbt, vdev->nodename, "ring-evtchn", "%u", vfbdev_priv->vfbdev.evtchn); + + vbus_transaction_end(vbt); +} + +void vfbdev_shutdown(struct vbus_device *vdev) +{ + DBG0("[vfbdev] Frontend shutdown\n"); +} + +void vfbdev_closed(struct vbus_device *vdev) +{ + vfbdev_priv_t *vfbdev_priv = dev_get_drvdata(vdev->dev); + + DBG0("[vfbdev] Frontend close\n"); + + /** + * Free the ring and deallocate the proper data. + */ + + /* Free resources associated with old device channel. */ + if (vfbdev_priv->vfbdev.ring_ref != GRANT_INVALID_REF) { + gnttab_end_foreign_access(vfbdev_priv->vfbdev.ring_ref); + free_vpage((addr_t) vfbdev_priv->vfbdev.ring.sring); + + vfbdev_priv->vfbdev.ring_ref = GRANT_INVALID_REF; + vfbdev_priv->vfbdev.ring.sring = NULL; + } + + if (vfbdev_priv->vfbdev.irq) + unbind_from_irqhandler(vfbdev_priv->vfbdev.irq); + + vfbdev_priv->vfbdev.irq = 0; +} + +void vfbdev_suspend(struct vbus_device *vdev) +{ + DBG0("[vfbdev] Frontend suspend\n"); +} + +void vfbdev_resume(struct vbus_device *vdev) +{ + DBG0("[vfbdev] Frontend resume\n"); +} + +void vfbdev_connected(struct vbus_device *vdev) +{ + DBG0("[vfbdev] Frontend connected\n"); +} + +vdrvfront_t vfbdevdrv = { + .probe = vfbdev_probe, + .reconfiguring = vfbdev_reconfiguring, + .shutdown = vfbdev_shutdown, + .closed = vfbdev_closed, + .suspend = vfbdev_suspend, + .resume = vfbdev_resume, + .connected = vfbdev_connected, +}; + +/* Char device associated to the framebuffer */ + +/** + * Retrieve data (resolution, size, fb address) from peer and AVZ. + */ +static void retrieve_data(vfbdev_priv_t *priv) +{ + vfbdev_request_t *ring_req; + vfbdev_response_t *ring_rsp; + avz_hyp_t hyp_args; + + /* Data have already been retrieved */ + if ((priv->memory_size != 0) && (priv->fb_paddr != 0)) + return; + + /* Retrieve resolution and size from peer */ + vdevfront_processing_begin(vfbdev_dev); + + ring_req = vfbdev_new_ring_request(&priv->vfbdev.ring); + vfbdev_ring_request_ready(&priv->vfbdev.ring); + notify_remote_via_virq(priv->vfbdev.irq); + vdevfront_processing_end(vfbdev_dev); + + vdevfront_processing_begin(vfbdev_dev); + + while ((ring_rsp = vfbdev_get_ring_response(&priv->vfbdev.ring)) == NULL) { + vdevfront_processing_end(vfbdev_dev); + + wait_for_completion(&priv->reader_wait); + + vdevfront_processing_begin(vfbdev_dev); + } + + priv->hres = ring_rsp->hres; + priv->vres = ring_rsp->vres; + priv->memory_size = ring_rsp->memory_size; + + vdevfront_processing_end(vfbdev_dev); + + /* Retrieve fb address from AVZ */ + hyp_args.cmd = AVZ_FBDEV_GET_ME_ADDR; + avz_hypercall(&hyp_args); + priv->fb_paddr = hyp_args.u.avz_fbdev_addr_args.paddr; +} + +static int vfbdev_mmap(int fd, addr_t virt_addr, uint32_t page_count, off_t offset) +{ + pcb_t *pcb = current()->pcb; + struct devclass *dev; + vfbdev_priv_t *priv; + + dev = devclass_by_fd(fd); + BUG_ON(!dev); + priv = devclass_get_priv(dev); + BUG_ON(!priv); + + retrieve_data(priv); + + /* Ensure that we got a framebuffer */ + if ((priv->memory_size == 0) || (priv->fb_paddr == 0)) { + return -ENODEV; + } + + /* Ensure requested mapping size doesn't overflow actual framebuffer size */ + if (page_count > (priv->memory_size / PAGE_SIZE)) { + return -EINVAL; + } + + create_mapping(pcb->pgtable, virt_addr, priv->fb_paddr, page_count * PAGE_SIZE, true); + + return 0; +} + +static int vfbdev_ioctl(int fd, unsigned long cmd, unsigned long args) +{ + struct devclass *dev; + vfbdev_priv_t *priv; + + dev = devclass_by_fd(fd); + BUG_ON(!dev); + priv = devclass_get_priv(dev); + BUG_ON(!priv); + + retrieve_data(priv); + + /* Set returned value accordingly to the command */ + switch (cmd) { + case IOCTL_FB_HRES: + *((uint32_t *) args) = priv->hres; + return 0; + + case IOCTL_FB_VRES: + *((uint32_t *) args) = priv->vres; + return 0; + + case IOCTL_FB_SIZE: + *((uint32_t *) args) = priv->memory_size; + return 0; + + default: + /* Unknown command. */ + return -EINVAL; + } +} + +static struct file_operations vfbdev_fops = { + .mmap = vfbdev_mmap, + .ioctl = vfbdev_ioctl, +}; + +static struct devclass vfbdev_cdev = { + .class = DEV_CLASS_FB, + .type = VFS_TYPE_DEV_FB, + .fops = &vfbdev_fops, +}; + +static int vfbdev_init(dev_t *dev, int fdt_offset) +{ + vfbdev_priv_t *vfbdev_priv; + + vfbdev_priv = malloc(sizeof(vfbdev_priv_t)); + BUG_ON(!vfbdev_priv); + + memset(vfbdev_priv, 0, sizeof(vfbdev_priv_t)); + + devclass_register(dev, &vfbdev_cdev); + devclass_set_priv(&vfbdev_cdev, vfbdev_priv); + dev_set_drvdata(dev, vfbdev_priv); + + vdevfront_init(VFBDEV_NAME, &vfbdevdrv); + + return 0; +} + +REGISTER_DRIVER_POSTCORE("vfbdev,frontend", vfbdev_init); diff --git a/so3/soo/include/soo/dev/vfbdev.h b/so3/soo/include/soo/dev/vfbdev.h new file mode 100644 index 000000000..991cbf7b1 --- /dev/null +++ b/so3/soo/include/soo/dev/vfbdev.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2026 Clément Dieperink + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef VFBDEV_H +#define VFBDEV_H + +#include +#include +#include + +#define VFBDEV_NAME "vfbdev" +#define VFBDEV_PREFIX "[" VFBDEV_NAME "] " + +typedef struct { + /* Nothing */ +} vfbdev_request_t; + +typedef struct { + uint32_t hres; + uint32_t vres; + uint64_t memory_size; +} vfbdev_response_t; + +DEFINE_RING_TYPES(vfbdev, vfbdev_request_t, vfbdev_response_t); + +typedef struct { + /* Must be the first field */ + vdevfront_t vdevfront; + + vfbdev_front_ring_t ring; + unsigned int irq; + + grant_ref_t ring_ref; + uint32_t evtchn; +} vfbdev_t; + +#endif /* VFBDEV_H */ diff --git a/so3/soo/include/soo/uapi/soo.h b/so3/soo/include/soo/uapi/soo.h index e5f56463e..959906a81 100644 --- a/so3/soo/include/soo/uapi/soo.h +++ b/so3/soo/include/soo/uapi/soo.h @@ -72,6 +72,19 @@ typedef struct gnttab_op gnttab_op_t; void do_gnttab(gnttab_op_t *args); +/* + * Page frame number to set the framebuffer addresses. + * The physical addresses may be splitted in multiple parts, which requires to have + * multiple set of strating page frame and page count + */ + +#define MAX_FBDEV_PFN 8 +typedef struct fbdev_pfns { + size_t pfn_count; + addr_t pfn[MAX_FBDEV_PFN]; + size_t page_count[MAX_FBDEV_PFN]; +} fbdev_pfns_t; + #define AVZ_SCHEDULER_FLIP 0 /* @@ -301,6 +314,9 @@ typedef struct agency_ioctl_args { #define AVZ_SET_ME_STATE 11 #define AVZ_GET_DOM_DESC 12 #define AVZ_GRANT_TABLE_OP 13 +#define AVZ_FBDEV_SET_PFNS 14 +#define AVZ_FBDEV_CHANGE_FOCUS 15 +#define AVZ_FBDEV_GET_ME_ADDR 16 /* AVZ_INJECT_CAPSULE */ typedef struct { @@ -363,6 +379,21 @@ typedef struct { gnttab_op_t gnttab_op; } avz_gnttab_t; +/* AVZ_FBDEV_SET_PFNS */ +typedef struct { + fbdev_pfns_t fbdev; +} avz_fbdev_pfns_t; + +/* AVZ_FBDEV_CHANGE_FOCUS */ +typedef struct { + int new_slotID; +} avz_fbdev_focus_t; + +/* AVZ_FBDEV_GET_ME_ADDR */ +typedef struct { + addr_t paddr; +} avz_fbdev_addr_t; + /* * AVZ hypercall argument */ @@ -382,6 +413,9 @@ typedef struct { avz_console_io_t avz_console_io_args; avz_domctl_t avz_domctl_args; avz_gnttab_t avz_gnttab_args; + avz_fbdev_pfns_t avz_fbdev_pfns_args; + avz_fbdev_focus_t avz_fbdev_focus_args; + avz_fbdev_addr_t avz_fbdev_addr_args; } u; } avz_hyp_t; diff --git a/so3/soo/kernel/vbstore/vbstore_me.c b/so3/soo/kernel/vbstore/vbstore_me.c index 9d4345b42..fa88f2bea 100644 --- a/so3/soo/kernel/vbstore/vbstore_me.c +++ b/so3/soo/kernel/vbstore/vbstore_me.c @@ -247,6 +247,12 @@ void remove_vbstore_entries(void) DBG("%s: removing vsensej from vbstore...\n", __func__); vbstore_dev_remove(ME_domID(), "vsensej"); } + + fdt_node = fdt_find_compatible_node(__fdt_addr, "vfbdev,frontend"); + if (fdt_device_is_available(__fdt_addr, fdt_node)) { + DBG("%s: removing vfbdev from vbstore...\n", __func__); + vbstore_dev_remove(ME_domID(), "vfbdev"); + } } /* @@ -293,6 +299,12 @@ void vbstore_devices_populate(void) DBG("%s: init vlogs...\n", __func__); vbstore_dev_init(ME_domID(), "vlogs", false, "vlogs,frontend"); } + + fdt_node = fdt_find_compatible_node(__fdt_addr, "vfbdev,frontend"); + if (fdt_device_is_available(__fdt_addr, fdt_node)) { + DBG("%s: init vfbdev...\n", __func__); + vbstore_dev_init(ME_domID(), "vfbdev", false, "vfbdev,frontend"); + } } void vbstore_trigger_dev_probe(void)