/* * PPC CPU identification * * This is a very simple "host CPU info" struct to get us going. * For the little host information we need, I don't want to grub about * parsing stuff in /proc/device-tree so just match host PVR to differentiate * PPC970 and POWER7 (which is all that's currently supported). * * Qemu does something similar but this is MUCH simpler! * * Copyright 2012 Matt Evans , IBM Corporation. * * 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. */ #include #include #include "cpu_info.h" #include "kvm/util.h" /* POWER7 */ static struct cpu_info cpu_power7_info = { .name = "POWER7", .tb_freq = 512000000, .d_bsize = 128, .i_bsize = 128, .flags = CPUINFO_FLAG_DFP | CPUINFO_FLAG_VSX | CPUINFO_FLAG_VMX, .mmu_info = { .flags = KVM_PPC_PAGE_SIZES_REAL | KVM_PPC_1T_SEGMENTS, .slb_size = 32, }, }; /* POWER8 */ static struct cpu_info cpu_power8_info = { .name = "POWER8", .tb_freq = 512000000, .d_bsize = 128, .i_bsize = 128, .flags = CPUINFO_FLAG_DFP | CPUINFO_FLAG_VSX | CPUINFO_FLAG_VMX, .mmu_info = { .flags = KVM_PPC_PAGE_SIZES_REAL | KVM_PPC_1T_SEGMENTS, .slb_size = 32, }, }; /* PPC970/G5 */ static struct cpu_info cpu_970_info = { .name = "G5", .tb_freq = 33333333, .d_bsize = 128, .i_bsize = 128, .flags = CPUINFO_FLAG_VMX, }; /* This is a default catchall for 'no match' on PVR: */ static struct cpu_info cpu_dummy_info = { .name = "unknown" }; static struct pvr_info host_pvr_info[] = { { 0xffffffff, 0x0f000003, &cpu_power7_info }, { 0xffff0000, 0x003f0000, &cpu_power7_info }, { 0xffff0000, 0x004a0000, &cpu_power7_info }, { 0xffff0000, 0x004b0000, &cpu_power8_info }, { 0xffff0000, 0x00390000, &cpu_970_info }, { 0xffff0000, 0x003c0000, &cpu_970_info }, { 0xffff0000, 0x00440000, &cpu_970_info }, { 0xffff0000, 0x00450000, &cpu_970_info }, }; /* If we can't query the kernel for supported page sizes assume 4K and 16M */ static struct kvm_ppc_one_seg_page_size fallback_sps[] = { [0] = { .page_shift = 12, .slb_enc = 0, .enc = { [0] = { .page_shift = 12, .pte_enc = 0, }, }, }, [1] = { .page_shift = 24, .slb_enc = 0x100, .enc = { [0] = { .page_shift = 24, .pte_enc = 0, }, }, }, }; static void setup_mmu_info(struct kvm *kvm, struct cpu_info *cpu_info) { static struct kvm_ppc_smmu_info *mmu_info; struct kvm_ppc_one_seg_page_size *sps; int i, j, k, valid; if (!kvm__supports_extension(kvm, KVM_CAP_PPC_GET_SMMU_INFO)) { memcpy(&cpu_info->mmu_info.sps, fallback_sps, sizeof(fallback_sps)); } else if (ioctl(kvm->vm_fd, KVM_PPC_GET_SMMU_INFO, &cpu_info->mmu_info) < 0) { die_perror("KVM_PPC_GET_SMMU_INFO failed"); } mmu_info = &cpu_info->mmu_info; if (!(mmu_info->flags & KVM_PPC_PAGE_SIZES_REAL)) /* Guest pages are not restricted by the backing page size */ return; /* Filter based on backing page size */ for (i = 0; i < KVM_PPC_PAGE_SIZES_MAX_SZ; i++) { sps = &mmu_info->sps[i]; if (!sps->page_shift) break; if (kvm->ram_pagesize < (1ul << sps->page_shift)) { /* Mark the whole segment size invalid */ sps->page_shift = 0; continue; } /* Check each page size for the segment */ for (j = 0, valid = 0; j < KVM_PPC_PAGE_SIZES_MAX_SZ; j++) { if (!sps->enc[j].page_shift) break; if (kvm->ram_pagesize < (1ul << sps->enc[j].page_shift)) sps->enc[j].page_shift = 0; else valid++; } if (!valid) { /* Mark the whole segment size invalid */ sps->page_shift = 0; continue; } /* Mark any trailing entries invalid if we broke out early */ for (k = j; k < KVM_PPC_PAGE_SIZES_MAX_SZ; k++) sps->enc[k].page_shift = 0; /* Collapse holes */ for (j = 0; j < KVM_PPC_PAGE_SIZES_MAX_SZ; j++) { if (sps->enc[j].page_shift) continue; for (k = j + 1; k < KVM_PPC_PAGE_SIZES_MAX_SZ; k++) { if (sps->enc[k].page_shift) { sps->enc[j] = sps->enc[k]; sps->enc[k].page_shift = 0; break; } } } } /* Mark any trailing entries invalid if we broke out early */ for (j = i; j < KVM_PPC_PAGE_SIZES_MAX_SZ; j++) mmu_info->sps[j].page_shift = 0; /* Collapse holes */ for (i = 0; i < KVM_PPC_PAGE_SIZES_MAX_SZ; i++) { if (mmu_info->sps[i].page_shift) continue; for (j = i + 1; j < KVM_PPC_PAGE_SIZES_MAX_SZ; j++) { if (mmu_info->sps[j].page_shift) { mmu_info->sps[i] = mmu_info->sps[j]; mmu_info->sps[j].page_shift = 0; break; } } } } struct cpu_info *find_cpu_info(struct kvm *kvm) { struct cpu_info *info; unsigned int i; u32 pvr = kvm->arch.pvr; for (info = NULL, i = 0; i < ARRAY_SIZE(host_pvr_info); i++) { if ((pvr & host_pvr_info[i].pvr_mask) == host_pvr_info[i].pvr) { info = host_pvr_info[i].cpu_info; break; } } /* Didn't find anything? Rut-ro. */ if (!info) { pr_warning("Host CPU unsupported by kvmtool\n"); info = &cpu_dummy_info; } setup_mmu_info(kvm, info); return info; }