From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751439AbdCBVDC (ORCPT ); Thu, 2 Mar 2017 16:03:02 -0500 Received: from mail-bl2nam02on0069.outbound.protection.outlook.com ([104.47.38.69]:44800 "EHLO NAM02-BL2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1751153AbdCBVCx (ORCPT ); Thu, 2 Mar 2017 16:02:53 -0500 Authentication-Results: vger.kernel.org; dkim=none (message not signed) header.d=none;vger.kernel.org; dmarc=none action=none header.from=amd.com; Subject: [RFC PATCH v2 16/32] x86: kvm: Provide support to create Guest and HV shared per-CPU variables From: Brijesh Singh To: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , Date: Thu, 2 Mar 2017 10:15:36 -0500 Message-ID: <148846773666.2349.9492983018843773590.stgit@brijesh-build-machine> In-Reply-To: <148846752022.2349.13667498174822419498.stgit@brijesh-build-machine> References: <148846752022.2349.13667498174822419498.stgit@brijesh-build-machine> User-Agent: StGit/0.17.1-dirty MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit X-Originating-IP: [165.204.77.1] X-ClientProxiedBy: MWHPR21CA0070.namprd21.prod.outlook.com (10.172.93.160) To MWHPR12MB1613.namprd12.prod.outlook.com (10.172.56.14) X-MS-Office365-Filtering-Correlation-Id: 13badff4-1366-46f0-96b9-08d4617efe68 X-MS-Office365-Filtering-HT: Tenant X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:(22001)(48565401081);SRVR:MWHPR12MB1613; X-Microsoft-Exchange-Diagnostics: 1;MWHPR12MB1613;3:TIe+VKguO0X8EG2pJ8kW/ZeGvfHM9e2HncXqD8i24N94iVcfFR/KqLbe7SqGo4cBHL5gHsLAUlfANhmiKcwkpDRnpxSW8NPIIr55GFLm+XBc4KruFODNAGUeFzZr0biURBUqdytQJ1tqWJUycSujp1yn6K2d9DuoLfGpj4rhaZh7XNqy47Z4xoolZgqGlcGVsrrRqYt/gOfRYJN3vzx+y7sgZ69ayI0JjkygJP8eM9G1g43MkUwkd6ivIptjMrwRGUGL1OEB50t4PTnEF1xKN/cLK1v53JnWyO6Pus2sSd4=;25:bvJ3oul/c4Mb4/8ACbPbV6gwXaddM9eIjcdvylo7kIc3PsW6wrzNAoLjaU1zWLBKxTPhlDgmlCjKYSeaF6NkGUJ0LUvD7LkLlWCX5dCE3lIRZB2YQ04EkqRrC/XfShrELvQGSQXmsqWUcTwAIYXaZ59Hy4vq7l3cUeexES28tBx6FPkaxTmQHXjVtFokpspxMxzgv5TVQExKYC7p+lda4iyyS9TFW+A/cEQ+G/kRnFZnB7WgNAy9xs2rnJ6lLGT/ohJK+R5TcrEdalkgtio2IunIKfdXGLusDRx3+MOH6ympXtFC/kCZmFGg5d2Vo5yjUnLZrK2aXuvmcSRv8iXoVvYkPD1CmaccwxWe1+Nv+n0+lUYpJfFnynk96yI34yhhVpzcT9xTffSX4r7l2gbTeZlW/2bVQGmDzIkZqVV5iQkQhlgy82TkYoDXUsyWr+BWPIhK16XBbvWIvRTeqDQuLA== X-Microsoft-Exchange-Diagnostics: 1;MWHPR12MB1613;31:FGLjwmqYF3EkInPXc+W/WbEE+i23YwiDQNE3pPPBsV7rhRCwOqNVQ5uMGMG7lPdKvgIz/6yqEB6L+jYB2KFCTuMRCrGl0mMwjPWS9ywpW1c9tmJcSGFtIw29p8Dud+ibMzL7eNdfSMdYIKVqjJ7UGZnIt8Pg1bWREe/v8pQGXsTB69BM1eslTxlGPU1PcAwOOvaedo/GbwUQ3v1GmOXqf+Mn2/Xljfq4vZQrQ1qd1gkO16nEAGlNzwK3YdpJQqhg;20:ZZm7sidD6WCxI1Yl5snsjMdulzbv2ktKE8Mg9+TGkguK+SvWGFiyfMHMHTTpPir/ujgcteet3Y9FotRuDS4CmO3H2ZHufAvsWtEneJXLGAzVoZ3QbO3cQl/fJKC3PidYMVsXvedGxkkQNvX05T0SSg5ALQJ6z4XkOaZlxT20Ga1FYCbGZ4Sf8Tk/pH7p5aSjgQmk8xNX5EUEaNaMYXh1PeQV18t0Gkl/6iu5WY7yzn+5rgy18ZfU3mHaUuigfd1c1aKUB3hG/gxsTOYAdKhTypOhKdTXmxtYP5OSmTJcvzT8NNvCIKSJ9AHtyoI7cVAGZ0dQ6KVOVbpaOSwloBREBY0APT0Vy/HKj9h4IQAHt8y3zS1jshN70uXQ1WYk9cU3ru41EGONp295L5baCwXtYqNVXhcE4cwW6UtEUbcNLdguHIf8NBYUvkkyev69qSMunOV+eZUeFW4RKM2UBmTFOTs5spaFvVX6w6mvrUjczlq8FGq7mht8ufVe67cIGGz6 X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:(767451399110); X-Exchange-Antispam-Report-CFA-Test: BCL:0;PCL:0;RULEID:(6040375)(601004)(2401047)(8121501046)(5005006)(10201501046)(3002001)(6055026)(6041248)(20161123555025)(20161123564025)(20161123562025)(20161123558025)(20161123560025)(6072148);SRVR:MWHPR12MB1613;BCL:0;PCL:0;RULEID:;SRVR:MWHPR12MB1613; X-Microsoft-Exchange-Diagnostics: 1;MWHPR12MB1613;4:kk1lIrNDQ9C09rcnc6LvJmt3KhINTb5iyNO3ERQA7+YEt7KUfGYi1AnIg38u9pPpU4NGD5l3BDbYNym3RQOuKK0IBjJouU4DH6wsmtipP92URBzFiOvrzNMB8P2FNrZMrnRayvmL4mIwVs2KQzQYU9in5tlKkZGYTklZAVAKFozayO5cMA71tm54ougULecje0chtfPNm0sEZGgZGY6WOoA6v1b2P96uvTJk7QxV3hsifDlHiDEnHAFgDMxukQ/LiIlHKKnVkk1iu2D8S/AHz73rlNari7CpHB/JvlQKIq/XoyI66cjZB5Tw3vgfirdA2/9BNQk5fdKSxcy16y/s7PewZNFJc7vOni1+vFqjmIOdzcytI74SX+9TNg9w10s7pprtDNtzkjKULMNB7cNJ0tWur/q9kA5O/sNcnZEm+qkOnfIBWIRLMVcdYZrUZ8hHQlRflLy5nO8jNDw0HFPsSieMRH8XGY7oGT1g1M5i57sE2wZ27tcTrfgrolNLty6EIivC95mdPmSBSnmzGBqrydOmXzWHee1yeB97lYZqZqHE5/qlYqCY7o2FZ1CnGkrOGYD+ACkOIbzbqDZ679gvlb/OFL99YW92Qf1wuvjozN8+htya4tbfzc9rZJZ7NbYG2Trun/Aa6NjT/MSSrrItRZYQitpkhdptjoutHxC26nQ= X-Forefront-PRVS: 023495660C X-Forefront-Antispam-Report: SFV:NSPM;SFS:(10009020)(4630300001)(6009001)(6049001)(7916002)(39450400003)(39840400002)(39860400002)(39410400002)(39850400002)(54356999)(230700001)(42186005)(76176999)(3846002)(2950100002)(5660300001)(2906002)(33646002)(33716001)(50986999)(6666003)(83506001)(6116002)(66066001)(8676002)(9686003)(38730400002)(25786008)(53936002)(92566002)(77096006)(6486002)(189998001)(90366009)(2201001)(50466002)(23676002)(7406005)(1191002)(7366002)(103116003)(86362001)(7416002)(7736002)(305945005)(81166006)(47776003)(921003)(2101003)(1121003)(83996005)(217873001)(84006005);DIR:OUT;SFP:1101;SCL:1;SRVR:MWHPR12MB1613;H:[127.0.1.1];FPR:;SPF:None;MLV:sfv;LANG:en; X-Microsoft-Exchange-Diagnostics: =?utf-8?B?MTtNV0hQUjEyTUIxNjEzOzIzOlo2S0xkRjRRdTJXaUUxZ044MWlsRUQrR2p6?= =?utf-8?B?aS9NZnFwd2tVNmZ6K25xK0JoS3ZEdHFDanM4akRCWHl3aWVQK1A3dFU1V0NB?= =?utf-8?B?RU1IZTJRUDNFdGNXaHVaWWlyRFFETlNBTVBMZG9MK2xVWS96K0g3T2xHMlAr?= =?utf-8?B?OGE2QlN6QzJBaWhpZmZzRE54ZXZDRmdCSTZxL29FMFlFTjgvelBJbkhlZHln?= =?utf-8?B?dlN1eVF4eTBBL1hYS2E1RG0yNDFidUgzcFk1dW5QTTE4UXBXVFJNTlpkVUlI?= =?utf-8?B?bU1hb2ExWnNYUFAxcU1GNExMeHplTlIxVStKOEpUL1Q2NjdJY1pFZGkxd0pz?= =?utf-8?B?R2w3K05CSXZDd09oRnI5aWZwZ2tuYm43Q3pQYkFsYkNPQ1dxOWVFbk9ZdHI2?= =?utf-8?B?K1UxZW03RHNFZXFhQnFwNG14Tk9hYlVrKzVJWmRxS3JNelVta25STFV0Yzd1?= =?utf-8?B?Tmh1QXFDdFZvMEhQWWlvdGtNMkpROVhEenBmYnA2SzlsVXNmTzNvcUhXWXda?= =?utf-8?B?TUxubHkyd2VSMDEzSDVhOVB5eEZjN2toV0VLZFZmR1gySGhnZzl0SjN5WDd5?= =?utf-8?B?YnVrQ3ZES2QzWUZhNlZTYXQxdWdPOElSWERXT0ZCRHNBT21GUXlXd2szYlV4?= =?utf-8?B?MVFLdEc3ZGJTMENmdWpZOHlGWXA0OXp4amFWaGo2MjRJSTh6cEFmNklzZ09Q?= =?utf-8?B?TnFmcHBJOFBhTVdsUjA5WVFyMlJZNFZIbXVTMHhydVkwV2dERWJHbnI4Y1VM?= =?utf-8?B?YVFYQ0hVMWJyeXZmcVJPQlA2aHdRdUxldlRsV0hpaGNIMHRQcHEvN3dzZytT?= =?utf-8?B?MHZqWnJNejNuYTI4d09Qb1FicjR0bHY0alRpNjlrTkFuTEw4dkhITmNIT0tV?= =?utf-8?B?c3g0b0lmanVzQWN4TDE5Zk9TVUhMWDlNTjZuQnlKTnBZajZoaUUwdGlHdDFM?= =?utf-8?B?eENEUDJoQlIzYnlXN2FZcnptQlFyVm11WStlNzdzWFVpL0VtWldqK2pNVDg4?= =?utf-8?B?cWxwWGcwa3B0aVU1UE9ERU8rWFM2WHpEVGpGbVVZemR0bXliY21xd2dlcERO?= =?utf-8?B?bHpTZEgzeHdLbFNQd2lza1BpaFk1VXdKb2FuUWlTUGJxSFQzUFNtL1l3Q1ZB?= =?utf-8?B?YlNpTEZRdUtFbUVOSmhlRkErM1I4bGhuNHF2VjdvU3FPTjRleXQ5eXVVZ0RB?= =?utf-8?B?SzVtR2FqYXY1djlRckdTZk1CejBBQkgzc3lIKzlkK0pOZ0Z0b0x6aFQ1RXZJ?= =?utf-8?B?dkt0RmlKNCs1UlFmdVhNaVZVb3NJRmdoLyszQld0ZlgyZGpWRW1vRUU0RTRm?= =?utf-8?B?dU1hdk43b0RUdTNpSDM5bWJXa2hyNi93aHBBTm5sTTQwUmI3RHNPdDNuSkFG?= =?utf-8?B?aCtGaThhWlVXbHdIRDhmMjA3MlgyZ1MvMjBsSFgwQ0kvRFo4UlNUb3QydXRv?= =?utf-8?B?Z3BoVjZSR0xSOEZVVml0MkNTaVlVd0IxcVlNbVhpRUxXREg1cmxxQWE5OVJL?= =?utf-8?B?RFR1SnJYZVgyN1Y1WjM4OW1ySDQxS3pjRHRhN2ZYTmFGa2s4ZEU1cXhEeDRM?= =?utf-8?B?MVdJYVU2ZVlRNHM3NEx6ZWdIU05YM0xsdEVOYURpYUUvc1dZb3ZLalNOSTJl?= =?utf-8?B?UUdpRlBWbGFGZmVSTG9Ub0ZLL1p0akNXYVBjNG5QVWw3UmRsTGh2NDc4enlo?= =?utf-8?Q?Dj1+8tim7tpRXyH4wArzuLh7zRrXUYIW28rwoKY?= X-Microsoft-Exchange-Diagnostics: 1;MWHPR12MB1613;6:p/2PNvqenGBdgkU3ipDYAYgBtl+dBiuvv8IjRFQEem/v0ByQXl4PuNC3HutykI+zwDmqfebrb0v5Fx+p6+dlKS3X2AqSOh05nbKXSVzR2Ww7Rh0zgQL55DefrNOlcK3GBhAsof6p6xin5W6JlDuCKedNotMnZwEiaDC+foo9m3/GbOxibQDx2DQcKW6K9i8I/ulHr8zupz8k9Ex9mfI5oaTW7PV74XsY2Kfw+VS8biLKw2Ttntq/lx07Z7w3uK+629pJVFgUAcdWooaaGuJqcOfQA+m3mGbMwtfU0NZNlzqaqgf1kMjVkwYAgqMkAP6YPzJI/jTssZNQnvfIBZMBZrJs+9OKwRO93dcnNAaYhdc1qJC9h3SmS9qkuenCYRN6aQ8um+TTD/HoHq4nZL2osmW1PnxGsYB0E3pskSkn/Uc=;5:nrmMqYv3s1do907h/T+hYIW3/+wVOap/7bNsNzO0JVjNaLyuy3z+1OzbZsLUs2eBr67t8NRn1Jn9gCa2k31qaWthjRZg3ruA7TGytyrh+VrtinE4yPR2YfQR008MUOfQLBi1+BZh5Z3486XbPCR2Rg==;24:3uKlt0FZAa/71SOw4BwKeBub0A3+GwK+ogO4FdaNDWPVR33PzTK8mLJWkTN4spc+SbrH/JuyR2AEDO5+kGvrddiM/bJWb+KUxtYm93jVRCY= SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-Microsoft-Exchange-Diagnostics: 1;MWHPR12MB1613;7:ZPuqoNcHYbweFFkPYA0VmWrga2FnEzvLFChLaoB6dW2vxo64HPCSJ8qaAafP9e0K4uo/mXPHPYfQw0lozNrPgBgwx6i9h4MGWTD6/PKRizXzsWr5bYJ+3AhqjtH3erULS1RKoJPtKaOO6djdtCFYq/lrO2N+gsK0FCE2OhDaZXymcnTyorRnw8QGsFfhuztizSbgS6viOSP6bbhjTQb7bU0jRCae9mhtUtUxor4CZ5zpCqrI9YFva0baTYbx/Phq6Xt7k9v/SCIEpd+zuYDvm/bN/TCo1d5dYXOQwAGAqZE078zmc2dkm5oDMnBiDJUIicpZor7Ovko5iLWThW8fGg==;20:LUok6zZK10M7+W1YWUJCz2slCSwq3ZpI3kxLeICp6aa0uckCoMLGuht6O7ZmDWh9BHqB84xbKzqfs2+fJGdpJDFo80PQWzXjjiQCzfLswtL0vnsnXD6pFMzH5ogbInUo1iFr5dxYxVa1wu7aSBb1v5980XF2SMEhNlbe/SfIETUUckc+vS89faIERK5jppkAL45tEIhBj6UanY3KBeciauJDzaJg+ykXHYnFCmo7A4CizdfAXtvlTSAaAQS7qLPi X-OriginatorOrg: amd.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 02 Mar 2017 15:15:39.5466 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-Transport-CrossTenantHeadersStamped: MWHPR12MB1613 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Some KVM specific MSR's (steal-time, asyncpf, avic_eio) allocates per-CPU variable at compile time and share its physical address with hypervisor. It presents a challege when SEV is active in guest OS. When SEV is active, guest memory is encrypted with guest key and hypervisor will no longer able to modify the guest memory. When SEV is active, we need to clear the encryption attribute of shared physical addresses so that both guest and hypervisor can access the data. To solve this problem, I have tried these three options: 1) Convert the static per-CPU to dynamic per-CPU allocation. When SEV is detected then clear the encryption attribute. But while doing so I found that per-CPU dynamic allocator was not ready when kvm_guest_cpu_init was called. 2) Since the encryption attributes works on PAGE_SIZE hence add some extra padding to 'struct kvm-steal-time' to make it PAGE_SIZE and then at runtime clear the encryption attribute of the full PAGE. The downside of this was now we need to modify structure which may break the compatibility. 3) Define a new per-CPU section (.data..percpu.hv_shared) which will be used to hold the compile time shared per-CPU variables. When SEV is detected we map this section with encryption attribute cleared. This patch implements #3. It introduces a new DEFINE_PER_CPU_HV_SHAHRED macro to create a compile time per-CPU variable. When SEV is detected we map the per-CPU variable as decrypted (i.e with encryption attribute cleared). Signed-off-by: Brijesh Singh --- arch/x86/kernel/kvm.c | 43 +++++++++++++++++++++++++++++++------ include/asm-generic/vmlinux.lds.h | 3 +++ include/linux/percpu-defs.h | 9 ++++++++ 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/arch/x86/kernel/kvm.c b/arch/x86/kernel/kvm.c index 099fcba..706a08e 100644 --- a/arch/x86/kernel/kvm.c +++ b/arch/x86/kernel/kvm.c @@ -75,8 +75,8 @@ static int parse_no_kvmclock_vsyscall(char *arg) early_param("no-kvmclock-vsyscall", parse_no_kvmclock_vsyscall); -static DEFINE_PER_CPU(struct kvm_vcpu_pv_apf_data, apf_reason) __aligned(64); -static DEFINE_PER_CPU(struct kvm_steal_time, steal_time) __aligned(64); +static DEFINE_PER_CPU_HV_SHARED(struct kvm_vcpu_pv_apf_data, apf_reason) __aligned(64); +static DEFINE_PER_CPU_HV_SHARED(struct kvm_steal_time, steal_time) __aligned(64); static int has_steal_clock = 0; /* @@ -290,6 +290,22 @@ static void __init paravirt_ops_setup(void) #endif } +static int kvm_map_percpu_hv_shared(void *addr, unsigned long size) +{ + /* When SEV is active, the percpu static variables initialized + * in data section will contain the encrypted data so we first + * need to decrypt it and then map it as decrypted. + */ + if (sev_active()) { + unsigned long pa = slow_virt_to_phys(addr); + + sme_early_decrypt(pa, size); + return early_set_memory_decrypted(addr, size); + } + + return 0; +} + static void kvm_register_steal_time(void) { int cpu = smp_processor_id(); @@ -298,12 +314,17 @@ static void kvm_register_steal_time(void) if (!has_steal_clock) return; + if (kvm_map_percpu_hv_shared(st, sizeof(*st))) { + pr_err("kvm-stealtime: failed to map hv_shared percpu\n"); + return; + } + wrmsrl(MSR_KVM_STEAL_TIME, (slow_virt_to_phys(st) | KVM_MSR_ENABLED)); pr_info("kvm-stealtime: cpu %d, msr %llx\n", cpu, (unsigned long long) slow_virt_to_phys(st)); } -static DEFINE_PER_CPU(unsigned long, kvm_apic_eoi) = KVM_PV_EOI_DISABLED; +static DEFINE_PER_CPU_HV_SHARED(unsigned long, kvm_apic_eoi) = KVM_PV_EOI_DISABLED; static notrace void kvm_guest_apic_eoi_write(u32 reg, u32 val) { @@ -327,25 +348,33 @@ static void kvm_guest_cpu_init(void) if (kvm_para_has_feature(KVM_FEATURE_ASYNC_PF) && kvmapf) { u64 pa = slow_virt_to_phys(this_cpu_ptr(&apf_reason)); + if (kvm_map_percpu_hv_shared(this_cpu_ptr(&apf_reason), + sizeof(struct kvm_vcpu_pv_apf_data))) + goto skip_asyncpf; #ifdef CONFIG_PREEMPT pa |= KVM_ASYNC_PF_SEND_ALWAYS; #endif wrmsrl(MSR_KVM_ASYNC_PF_EN, pa | KVM_ASYNC_PF_ENABLED); __this_cpu_write(apf_reason.enabled, 1); - printk(KERN_INFO"KVM setup async PF for cpu %d\n", - smp_processor_id()); + printk(KERN_INFO"KVM setup async PF for cpu %d msr %llx\n", + smp_processor_id(), pa); } - +skip_asyncpf: if (kvm_para_has_feature(KVM_FEATURE_PV_EOI)) { unsigned long pa; /* Size alignment is implied but just to make it explicit. */ BUILD_BUG_ON(__alignof__(kvm_apic_eoi) < 4); + if (kvm_map_percpu_hv_shared(this_cpu_ptr(&kvm_apic_eoi), + sizeof(unsigned long))) + goto skip_pv_eoi; __this_cpu_write(kvm_apic_eoi, 0); pa = slow_virt_to_phys(this_cpu_ptr(&kvm_apic_eoi)) | KVM_MSR_ENABLED; wrmsrl(MSR_KVM_PV_EOI_EN, pa); + printk(KERN_INFO"KVM setup PV EOI for cpu %d msr %lx\n", + smp_processor_id(), pa); } - +skip_pv_eoi: if (has_steal_clock) kvm_register_steal_time(); } diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 0968d13..8d29910 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -773,6 +773,9 @@ . = ALIGN(cacheline); \ *(.data..percpu) \ *(.data..percpu..shared_aligned) \ + . = ALIGN(PAGE_SIZE); \ + *(.data..percpu..hv_shared) \ + . = ALIGN(PAGE_SIZE); \ VMLINUX_SYMBOL(__per_cpu_end) = .; /** diff --git a/include/linux/percpu-defs.h b/include/linux/percpu-defs.h index 8f16299..5af366e 100644 --- a/include/linux/percpu-defs.h +++ b/include/linux/percpu-defs.h @@ -172,6 +172,15 @@ #define DEFINE_PER_CPU_READ_MOSTLY(type, name) \ DEFINE_PER_CPU_SECTION(type, name, "..read_mostly") +/* Declaration/definition used for per-CPU variables that must be shared + * between hypervisor and guest OS. + */ +#define DECLARE_PER_CPU_HV_SHARED(type, name) \ + DECLARE_PER_CPU_SECTION(type, name, "..hv_shared") + +#define DEFINE_PER_CPU_HV_SHARED(type, name) \ + DEFINE_PER_CPU_SECTION(type, name, "..hv_shared") + /* * Intermodule exports for per-CPU variables. sparse forgets about * address space across EXPORT_SYMBOL(), change EXPORT_SYMBOL() to