בדף הזה מוסבר איך מטמיעים מודול ספק של מכונה וירטואלית מוגנת מבוססת-ליבה (pKVM).
ב-Android 16-6.12 ואילך, אחרי שמסיימים את השלבים האלה, אמורה להיות לכם היררכיית ספריות שדומה לזו:
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
דוגמה מלאה זמינה במאמר איך בונים מודול pKVM באמצעות DDK.
בגרסה android15-6.6 ובגרסאות קודמות:
Makefile
el1.c
hyp/
Makefile
el2.c
מוסיפים את קוד ההיפר-ויזור EL2 (
el2.c
). לכל הפחות, הקוד הזה צריך להצהיר על פונקציית init שמקבלת הפניה למבנהpkvm_module_ops
:#include <asm/kvm_pkvm_module.h> int pkvm_driver_hyp_init(const struct pkvm_module_ops *ops) { /* Init the EL2 code */ return 0; }
ה-API של מודול הספק pKVM הוא מבנה שמכיל קריאות חוזרות (callbacks) ל-hypervisor של pKVM. המבנה הזה פועל לפי אותם כללי ABI כמו ממשקי GKI.
יוצרים את
hyp/Makefile
כדי לבנות את קוד ההיפר-ויזור:hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
מוסיפים את קוד הליבה EL1 (
el1.c
). קטע ה-init של הקוד הזה חייב להכיל קריאה ל-pkvm_load_el2 module
כדי לטעון את קוד ההיפר-ויז'ר EL2 משלב 1.#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <asm/kvm_pkvm_module.h> int __kvm_nvhe_pkvm_driver_hyp_init(const struct pkvm_module_ops *ops); static int __init pkvm_driver_init(void) { unsigned long token; return pkvm_load_el2_module(__kvm_nvhe_pkvm_driver_hyp_init, &token); } module_init(pkvm_driver_init);
לבסוף, יוצרים את כללי הבנייה.
ב-android16-6.12 ואילך, אפשר לעיין במאמר Build a pKVM module with DDK (יצירת מודול pKVM באמצעות DDK) כדי ליצור
ddk_library()
עבור EL2 ו-ddk_module()
עבור EL1.ב-android15-6.6 ובגרסאות קודמות, יוצרים את קובץ ה-Makefile הבסיסי כדי לקשר בין הקודים EL1 ו-EL2:
ifneq ($(KERNELRELEASE),) clean-files := hyp/hyp.lds hyp/hyp-reloc.S obj-m := pkvm_module.o pkvm_module-y := el1.o hyp/kvm_nvhe.o $(PWD)/hyp/kvm_nvhe.o: FORCE $(Q)$(MAKE) $(build)=$(obj)/hyp $(obj)/hyp/kvm_nvhe.o else all: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean endif
טעינת מודול pKVM
בדומה למודולים של ספקים ב-GKI, אפשר לטעון מודולים של ספקים ב-pKVM באמצעות modprobe.
עם זאת, מטעמי אבטחה, הטעינה חייבת להתבצע לפני ביטול ההרשאות.
כדי לטעון מודול pKVM, צריך לוודא שהמודולים כלולים במערכת הקבצים הבסיסית (initramfs
) ולהוסיף את הפקודה הבאה לשורת הפקודה של ליבת המערכת:
kvm-arm.protected_modules=mod1,mod2,mod3,...
מודולים של ספק pKVM שמאוחסנים ב-initramfs
מקבלים בירושה את החתימה וההגנה של initramfs
.
אם אחד ממודולי הספק של pKVM לא נטען, המערכת נחשבת לא מאובטחת ולא ניתן להפעיל מכונה וירטואלית מוגנת.
קריאה לפונקציה EL2 (hypervisor) מ-EL1 (מודול ליבה)
קריאה ל-hypervisor (HVC) היא הוראה שמאפשרת לליבת מערכת ההפעלה לקרוא ל-hypervisor. עם ההשקה של מודולי ספקים של pKVM, אפשר להשתמש ב-HVC כדי לקרוא לפונקציה שתפעל ב-EL2 (במודול ההיפר-ויזור) מ-EL1 (מודול הליבה):
- בקוד EL2 (
el2.c
), מכריזים על ה-handler של EL2:
Android-14
void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
{
/* Handle the call */
cpu_reg(ctx, 1) = 0;
}
Android-15
void pkvm_driver_hyp_hvc(struct user_pt_regs *regs)
{
/* Handle the call */
regs->regs[0] = SMCCC_RET_SUCCESS;
regs->regs[1] = 0;
}
בקוד EL1 (
el1.c
), רושמים את ה-handler של EL2 במודול הספק של pKVM:int __kvm_nvhe_pkvm_driver_hyp_init(const struct pkvm_module_ops *ops); void __kvm_nvhe_pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx); // Android14 void __kvm_nvhe_pkvm_driver_hyp_hvc(struct user_pt_regs *regs); // Android15 static int hvc_number; static int __init pkvm_driver_init(void) { long token; int ret; ret = pkvm_load_el2_module(__kvm_nvhe_pkvm_driver_hyp_init,token); if (ret) return ret; ret = pkvm_register_el2_mod_call(__kvm_nvhe_pkvm_driver_hyp_hvc, token) if (ret < 0) return ret; hvc_number = ret; return 0; } module_init(pkvm_driver_init);
בקוד EL1 (מספר
el1.c
), קוראים ל-HVC:pkvm_el2_mod_call(hvc_number);