| /* |
| * PAL/SAL call delegation |
| * |
| * Copyright (c) 2004 Li Susie <susie.li@intel.com> |
| * Copyright (c) 2005 Yu Ke <ke.yu@intel.com> |
| * Copyright (c) 2007 Xiantao Zhang <xiantao.zhang@intel.com> |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope 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., 59 Temple |
| * Place - Suite 330, Boston, MA 02111-1307 USA. |
| */ |
| |
| #include <linux/kvm_host.h> |
| #include <linux/smp.h> |
| |
| #include "vti.h" |
| #include "misc.h" |
| |
| #include <asm/pal.h> |
| #include <asm/sal.h> |
| #include <asm/tlb.h> |
| |
| /* |
| * Handy macros to make sure that the PAL return values start out |
| * as something meaningful. |
| */ |
| #define INIT_PAL_STATUS_UNIMPLEMENTED(x) \ |
| { \ |
| x.status = PAL_STATUS_UNIMPLEMENTED; \ |
| x.v0 = 0; \ |
| x.v1 = 0; \ |
| x.v2 = 0; \ |
| } |
| |
| #define INIT_PAL_STATUS_SUCCESS(x) \ |
| { \ |
| x.status = PAL_STATUS_SUCCESS; \ |
| x.v0 = 0; \ |
| x.v1 = 0; \ |
| x.v2 = 0; \ |
| } |
| |
| static void kvm_get_pal_call_data(struct kvm_vcpu *vcpu, |
| u64 *gr28, u64 *gr29, u64 *gr30, u64 *gr31) { |
| struct exit_ctl_data *p; |
| |
| if (vcpu) { |
| p = &vcpu->arch.exit_data; |
| if (p->exit_reason == EXIT_REASON_PAL_CALL) { |
| *gr28 = p->u.pal_data.gr28; |
| *gr29 = p->u.pal_data.gr29; |
| *gr30 = p->u.pal_data.gr30; |
| *gr31 = p->u.pal_data.gr31; |
| return ; |
| } |
| } |
| printk(KERN_DEBUG"Failed to get vcpu pal data!!!\n"); |
| } |
| |
| static void set_pal_result(struct kvm_vcpu *vcpu, |
| struct ia64_pal_retval result) { |
| |
| struct exit_ctl_data *p; |
| |
| p = kvm_get_exit_data(vcpu); |
| if (p && p->exit_reason == EXIT_REASON_PAL_CALL) { |
| p->u.pal_data.ret = result; |
| return ; |
| } |
| INIT_PAL_STATUS_UNIMPLEMENTED(p->u.pal_data.ret); |
| } |
| |
| static void set_sal_result(struct kvm_vcpu *vcpu, |
| struct sal_ret_values result) { |
| struct exit_ctl_data *p; |
| |
| p = kvm_get_exit_data(vcpu); |
| if (p && p->exit_reason == EXIT_REASON_SAL_CALL) { |
| p->u.sal_data.ret = result; |
| return ; |
| } |
| printk(KERN_WARNING"Failed to set sal result!!\n"); |
| } |
| |
| struct cache_flush_args { |
| u64 cache_type; |
| u64 operation; |
| u64 progress; |
| long status; |
| }; |
| |
| cpumask_t cpu_cache_coherent_map; |
| |
| static void remote_pal_cache_flush(void *data) |
| { |
| struct cache_flush_args *args = data; |
| long status; |
| u64 progress = args->progress; |
| |
| status = ia64_pal_cache_flush(args->cache_type, args->operation, |
| &progress, NULL); |
| if (status != 0) |
| args->status = status; |
| } |
| |
| static struct ia64_pal_retval pal_cache_flush(struct kvm_vcpu *vcpu) |
| { |
| u64 gr28, gr29, gr30, gr31; |
| struct ia64_pal_retval result = {0, 0, 0, 0}; |
| struct cache_flush_args args = {0, 0, 0, 0}; |
| long psr; |
| |
| gr28 = gr29 = gr30 = gr31 = 0; |
| kvm_get_pal_call_data(vcpu, &gr28, &gr29, &gr30, &gr31); |
| |
| if (gr31 != 0) |
| printk(KERN_ERR"vcpu:%p called cache_flush error!\n", vcpu); |
| |
| /* Always call Host Pal in int=1 */ |
| gr30 &= ~PAL_CACHE_FLUSH_CHK_INTRS; |
| args.cache_type = gr29; |
| args.operation = gr30; |
| smp_call_function(remote_pal_cache_flush, |
| (void *)&args, 1); |
| if (args.status != 0) |
| printk(KERN_ERR"pal_cache_flush error!," |
| "status:0x%lx\n", args.status); |
| /* |
| * Call Host PAL cache flush |
| * Clear psr.ic when call PAL_CACHE_FLUSH |
| */ |
| local_irq_save(psr); |
| result.status = ia64_pal_cache_flush(gr29, gr30, &result.v1, |
| &result.v0); |
| local_irq_restore(psr); |
| if (result.status != 0) |
| printk(KERN_ERR"vcpu:%p crashed due to cache_flush err:%ld" |
| "in1:%lx,in2:%lx\n", |
| vcpu, result.status, gr29, gr30); |
| |
| #if 0 |
| if (gr29 == PAL_CACHE_TYPE_COHERENT) { |
| cpus_setall(vcpu->arch.cache_coherent_map); |
| cpu_clear(vcpu->cpu, vcpu->arch.cache_coherent_map); |
| cpus_setall(cpu_cache_coherent_map); |
| cpu_clear(vcpu->cpu, cpu_cache_coherent_map); |
| } |
| #endif |
| return result; |
| } |
| |
| struct ia64_pal_retval pal_cache_summary(struct kvm_vcpu *vcpu) |
| { |
| |
| struct ia64_pal_retval result; |
| |
| PAL_CALL(result, PAL_CACHE_SUMMARY, 0, 0, 0); |
| return result; |
| } |
| |
| static struct ia64_pal_retval pal_freq_base(struct kvm_vcpu *vcpu) |
| { |
| |
| struct ia64_pal_retval result; |
| |
| PAL_CALL(result, PAL_FREQ_BASE, 0, 0, 0); |
| |
| /* |
| * PAL_FREQ_BASE may not be implemented in some platforms, |
| * call SAL instead. |
| */ |
| if (result.v0 == 0) { |
| result.status = ia64_sal_freq_base(SAL_FREQ_BASE_PLATFORM, |
| &result.v0, |
| &result.v1); |
| result.v2 = 0; |
| } |
| |
| return result; |
| } |
| |
| static struct ia64_pal_retval pal_freq_ratios(struct kvm_vcpu *vcpu) |
| { |
| |
| struct ia64_pal_retval result; |
| |
| PAL_CALL(result, PAL_FREQ_RATIOS, 0, 0, 0); |
| return result; |
| } |
| |
| static struct ia64_pal_retval pal_logical_to_physica(struct kvm_vcpu *vcpu) |
| { |
| struct ia64_pal_retval result; |
| |
| INIT_PAL_STATUS_UNIMPLEMENTED(result); |
| return result; |
| } |
| |
| static struct ia64_pal_retval pal_platform_addr(struct kvm_vcpu *vcpu) |
| { |
| |
| struct ia64_pal_retval result; |
| |
| INIT_PAL_STATUS_SUCCESS(result); |
| return result; |
| } |
| |
| static struct ia64_pal_retval pal_proc_get_features(struct kvm_vcpu *vcpu) |
| { |
| |
| struct ia64_pal_retval result = {0, 0, 0, 0}; |
| long in0, in1, in2, in3; |
| |
| kvm_get_pal_call_data(vcpu, &in0, &in1, &in2, &in3); |
| result.status = ia64_pal_proc_get_features(&result.v0, &result.v1, |
| &result.v2, in2); |
| |
| return result; |
| } |
| |
| static struct ia64_pal_retval pal_cache_info(struct kvm_vcpu *vcpu) |
| { |
| |
| pal_cache_config_info_t ci; |
| long status; |
| unsigned long in0, in1, in2, in3, r9, r10; |
| |
| kvm_get_pal_call_data(vcpu, &in0, &in1, &in2, &in3); |
| status = ia64_pal_cache_config_info(in1, in2, &ci); |
| r9 = ci.pcci_info_1.pcci1_data; |
| r10 = ci.pcci_info_2.pcci2_data; |
| return ((struct ia64_pal_retval){status, r9, r10, 0}); |
| } |
| |
| #define GUEST_IMPL_VA_MSB 59 |
| #define GUEST_RID_BITS 18 |
| |
| static struct ia64_pal_retval pal_vm_summary(struct kvm_vcpu *vcpu) |
| { |
| |
| pal_vm_info_1_u_t vminfo1; |
| pal_vm_info_2_u_t vminfo2; |
| struct ia64_pal_retval result; |
| |
| PAL_CALL(result, PAL_VM_SUMMARY, 0, 0, 0); |
| if (!result.status) { |
| vminfo1.pvi1_val = result.v0; |
| vminfo1.pal_vm_info_1_s.max_itr_entry = 8; |
| vminfo1.pal_vm_info_1_s.max_dtr_entry = 8; |
| result.v0 = vminfo1.pvi1_val; |
| vminfo2.pal_vm_info_2_s.impl_va_msb = GUEST_IMPL_VA_MSB; |
| vminfo2.pal_vm_info_2_s.rid_size = GUEST_RID_BITS; |
| result.v1 = vminfo2.pvi2_val; |
| } |
| |
| return result; |
| } |
| |
| static struct ia64_pal_retval pal_vm_info(struct kvm_vcpu *vcpu) |
| { |
| struct ia64_pal_retval result; |
| |
| INIT_PAL_STATUS_UNIMPLEMENTED(result); |
| |
| return result; |
| } |
| |
| static u64 kvm_get_pal_call_index(struct kvm_vcpu *vcpu) |
| { |
| u64 index = 0; |
| struct exit_ctl_data *p; |
| |
| p = kvm_get_exit_data(vcpu); |
| if (p && (p->exit_reason == EXIT_REASON_PAL_CALL)) |
| index = p->u.pal_data.gr28; |
| |
| return index; |
| } |
| |
| int kvm_pal_emul(struct kvm_vcpu *vcpu, struct kvm_run *run) |
| { |
| |
| u64 gr28; |
| struct ia64_pal_retval result; |
| int ret = 1; |
| |
| gr28 = kvm_get_pal_call_index(vcpu); |
| /*printk("pal_call index:%lx\n",gr28);*/ |
| switch (gr28) { |
| case PAL_CACHE_FLUSH: |
| result = pal_cache_flush(vcpu); |
| break; |
| case PAL_CACHE_SUMMARY: |
| result = pal_cache_summary(vcpu); |
| break; |
| case PAL_HALT_LIGHT: |
| { |
| vcpu->arch.timer_pending = 1; |
| INIT_PAL_STATUS_SUCCESS(result); |
| if (kvm_highest_pending_irq(vcpu) == -1) |
| ret = kvm_emulate_halt(vcpu); |
| |
| } |
| break; |
| |
| case PAL_FREQ_RATIOS: |
| result = pal_freq_ratios(vcpu); |
| break; |
| |
| case PAL_FREQ_BASE: |
| result = pal_freq_base(vcpu); |
| break; |
| |
| case PAL_LOGICAL_TO_PHYSICAL : |
| result = pal_logical_to_physica(vcpu); |
| break; |
| |
| case PAL_VM_SUMMARY : |
| result = pal_vm_summary(vcpu); |
| break; |
| |
| case PAL_VM_INFO : |
| result = pal_vm_info(vcpu); |
| break; |
| case PAL_PLATFORM_ADDR : |
| result = pal_platform_addr(vcpu); |
| break; |
| case PAL_CACHE_INFO: |
| result = pal_cache_info(vcpu); |
| break; |
| case PAL_PTCE_INFO: |
| INIT_PAL_STATUS_SUCCESS(result); |
| result.v1 = (1L << 32) | 1L; |
| break; |
| case PAL_VM_PAGE_SIZE: |
| result.status = ia64_pal_vm_page_size(&result.v0, |
| &result.v1); |
| break; |
| case PAL_RSE_INFO: |
| result.status = ia64_pal_rse_info(&result.v0, |
| (pal_hints_u_t *)&result.v1); |
| break; |
| case PAL_PROC_GET_FEATURES: |
| result = pal_proc_get_features(vcpu); |
| break; |
| case PAL_DEBUG_INFO: |
| result.status = ia64_pal_debug_info(&result.v0, |
| &result.v1); |
| break; |
| case PAL_VERSION: |
| result.status = ia64_pal_version( |
| (pal_version_u_t *)&result.v0, |
| (pal_version_u_t *)&result.v1); |
| |
| break; |
| case PAL_FIXED_ADDR: |
| result.status = PAL_STATUS_SUCCESS; |
| result.v0 = vcpu->vcpu_id; |
| break; |
| default: |
| INIT_PAL_STATUS_UNIMPLEMENTED(result); |
| printk(KERN_WARNING"kvm: Unsupported pal call," |
| " index:0x%lx\n", gr28); |
| } |
| set_pal_result(vcpu, result); |
| return ret; |
| } |
| |
| static struct sal_ret_values sal_emulator(struct kvm *kvm, |
| long index, unsigned long in1, |
| unsigned long in2, unsigned long in3, |
| unsigned long in4, unsigned long in5, |
| unsigned long in6, unsigned long in7) |
| { |
| unsigned long r9 = 0; |
| unsigned long r10 = 0; |
| long r11 = 0; |
| long status; |
| |
| status = 0; |
| switch (index) { |
| case SAL_FREQ_BASE: |
| status = ia64_sal_freq_base(in1, &r9, &r10); |
| break; |
| case SAL_PCI_CONFIG_READ: |
| printk(KERN_WARNING"kvm: Not allowed to call here!" |
| " SAL_PCI_CONFIG_READ\n"); |
| break; |
| case SAL_PCI_CONFIG_WRITE: |
| printk(KERN_WARNING"kvm: Not allowed to call here!" |
| " SAL_PCI_CONFIG_WRITE\n"); |
| break; |
| case SAL_SET_VECTORS: |
| if (in1 == SAL_VECTOR_OS_BOOT_RENDEZ) { |
| if (in4 != 0 || in5 != 0 || in6 != 0 || in7 != 0) { |
| status = -2; |
| } else { |
| kvm->arch.rdv_sal_data.boot_ip = in2; |
| kvm->arch.rdv_sal_data.boot_gp = in3; |
| } |
| printk("Rendvous called! iip:%lx\n\n", in2); |
| } else |
| printk(KERN_WARNING"kvm: CALLED SAL_SET_VECTORS %lu." |
| "ignored...\n", in1); |
| break; |
| case SAL_GET_STATE_INFO: |
| /* No more info. */ |
| status = -5; |
| r9 = 0; |
| break; |
| case SAL_GET_STATE_INFO_SIZE: |
| /* Return a dummy size. */ |
| status = 0; |
| r9 = 128; |
| break; |
| case SAL_CLEAR_STATE_INFO: |
| /* Noop. */ |
| break; |
| case SAL_MC_RENDEZ: |
| printk(KERN_WARNING |
| "kvm: called SAL_MC_RENDEZ. ignored...\n"); |
| break; |
| case SAL_MC_SET_PARAMS: |
| printk(KERN_WARNING |
| "kvm: called SAL_MC_SET_PARAMS.ignored!\n"); |
| break; |
| case SAL_CACHE_FLUSH: |
| if (1) { |
| /*Flush using SAL. |
| This method is faster but has a side |
| effect on other vcpu running on |
| this cpu. */ |
| status = ia64_sal_cache_flush(in1); |
| } else { |
| /*Maybe need to implement the method |
| without side effect!*/ |
| status = 0; |
| } |
| break; |
| case SAL_CACHE_INIT: |
| printk(KERN_WARNING |
| "kvm: called SAL_CACHE_INIT. ignored...\n"); |
| break; |
| case SAL_UPDATE_PAL: |
| printk(KERN_WARNING |
| "kvm: CALLED SAL_UPDATE_PAL. ignored...\n"); |
| break; |
| default: |
| printk(KERN_WARNING"kvm: called SAL_CALL with unknown index." |
| " index:%ld\n", index); |
| status = -1; |
| break; |
| } |
| return ((struct sal_ret_values) {status, r9, r10, r11}); |
| } |
| |
| static void kvm_get_sal_call_data(struct kvm_vcpu *vcpu, u64 *in0, u64 *in1, |
| u64 *in2, u64 *in3, u64 *in4, u64 *in5, u64 *in6, u64 *in7){ |
| |
| struct exit_ctl_data *p; |
| |
| p = kvm_get_exit_data(vcpu); |
| |
| if (p) { |
| if (p->exit_reason == EXIT_REASON_SAL_CALL) { |
| *in0 = p->u.sal_data.in0; |
| *in1 = p->u.sal_data.in1; |
| *in2 = p->u.sal_data.in2; |
| *in3 = p->u.sal_data.in3; |
| *in4 = p->u.sal_data.in4; |
| *in5 = p->u.sal_data.in5; |
| *in6 = p->u.sal_data.in6; |
| *in7 = p->u.sal_data.in7; |
| return ; |
| } |
| } |
| *in0 = 0; |
| } |
| |
| void kvm_sal_emul(struct kvm_vcpu *vcpu) |
| { |
| |
| struct sal_ret_values result; |
| u64 index, in1, in2, in3, in4, in5, in6, in7; |
| |
| kvm_get_sal_call_data(vcpu, &index, &in1, &in2, |
| &in3, &in4, &in5, &in6, &in7); |
| result = sal_emulator(vcpu->kvm, index, in1, in2, in3, |
| in4, in5, in6, in7); |
| set_sal_result(vcpu, result); |
| } |