1 /*
2  * arch/x86/pv/descriptor-tables.c
3  *
4  * Descriptor table manipulation code for PV guests
5  *
6  * Copyright (c) 2002-2005 K A Fraser
7  * Copyright (c) 2004 Christian Limpach
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms and conditions of the GNU General Public
11  * License, version 2, as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public
19  * License along with this program; If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <xen/guest_access.h>
23 #include <xen/hypercall.h>
24 
25 #include <asm/p2m.h>
26 #include <asm/pv/mm.h>
27 
28 /* Override macros from asm/page.h to make them work with mfn_t */
29 #undef mfn_to_page
30 #define mfn_to_page(mfn) __mfn_to_page(mfn_x(mfn))
31 #undef page_to_mfn
32 #define page_to_mfn(pg) _mfn(__page_to_mfn(pg))
33 
34 /*******************
35  * Descriptor Tables
36  */
37 
pv_destroy_gdt(struct vcpu * v)38 void pv_destroy_gdt(struct vcpu *v)
39 {
40     l1_pgentry_t *pl1e;
41     unsigned int i;
42     unsigned long pfn, zero_pfn = PFN_DOWN(__pa(zero_page));
43 
44     v->arch.pv_vcpu.gdt_ents = 0;
45     pl1e = pv_gdt_ptes(v);
46     for ( i = 0; i < FIRST_RESERVED_GDT_PAGE; i++ )
47     {
48         pfn = l1e_get_pfn(pl1e[i]);
49         if ( (l1e_get_flags(pl1e[i]) & _PAGE_PRESENT) && pfn != zero_pfn )
50             put_page_and_type(mfn_to_page(_mfn(pfn)));
51         l1e_write(&pl1e[i], l1e_from_pfn(zero_pfn, __PAGE_HYPERVISOR_RO));
52         v->arch.pv_vcpu.gdt_frames[i] = 0;
53     }
54 }
55 
pv_set_gdt(struct vcpu * v,unsigned long * frames,unsigned int entries)56 long pv_set_gdt(struct vcpu *v, unsigned long *frames, unsigned int entries)
57 {
58     struct domain *d = v->domain;
59     l1_pgentry_t *pl1e;
60     /* NB. There are 512 8-byte entries per GDT page. */
61     unsigned int i, nr_pages = (entries + 511) / 512;
62 
63     if ( entries > FIRST_RESERVED_GDT_ENTRY )
64         return -EINVAL;
65 
66     /* Check the pages in the new GDT. */
67     for ( i = 0; i < nr_pages; i++ )
68     {
69         struct page_info *page;
70 
71         page = get_page_from_gfn(d, frames[i], NULL, P2M_ALLOC);
72         if ( !page )
73             goto fail;
74         if ( !get_page_type(page, PGT_seg_desc_page) )
75         {
76             put_page(page);
77             goto fail;
78         }
79         frames[i] = mfn_x(page_to_mfn(page));
80     }
81 
82     /* Tear down the old GDT. */
83     pv_destroy_gdt(v);
84 
85     /* Install the new GDT. */
86     v->arch.pv_vcpu.gdt_ents = entries;
87     pl1e = pv_gdt_ptes(v);
88     for ( i = 0; i < nr_pages; i++ )
89     {
90         v->arch.pv_vcpu.gdt_frames[i] = frames[i];
91         l1e_write(&pl1e[i], l1e_from_pfn(frames[i], __PAGE_HYPERVISOR_RW));
92     }
93 
94     return 0;
95 
96  fail:
97     while ( i-- > 0 )
98     {
99         put_page_and_type(mfn_to_page(_mfn(frames[i])));
100     }
101     return -EINVAL;
102 }
103 
do_set_gdt(XEN_GUEST_HANDLE_PARAM (xen_ulong_t)frame_list,unsigned int entries)104 long do_set_gdt(XEN_GUEST_HANDLE_PARAM(xen_ulong_t) frame_list,
105                 unsigned int entries)
106 {
107     int nr_pages = (entries + 511) / 512;
108     unsigned long frames[16];
109     struct vcpu *curr = current;
110     long ret;
111 
112     /* Rechecked in set_gdt, but ensures a sane limit for copy_from_user(). */
113     if ( entries > FIRST_RESERVED_GDT_ENTRY )
114         return -EINVAL;
115 
116     if ( copy_from_guest(frames, frame_list, nr_pages) )
117         return -EFAULT;
118 
119     domain_lock(curr->domain);
120 
121     if ( (ret = pv_set_gdt(curr, frames, entries)) == 0 )
122         flush_tlb_local();
123 
124     domain_unlock(curr->domain);
125 
126     return ret;
127 }
128 
do_update_descriptor(uint64_t pa,uint64_t desc)129 long do_update_descriptor(uint64_t pa, uint64_t desc)
130 {
131     struct domain *currd = current->domain;
132     unsigned long gmfn = pa >> PAGE_SHIFT;
133     unsigned long mfn;
134     unsigned int  offset;
135     struct desc_struct *gdt_pent, d;
136     struct page_info *page;
137     long ret = -EINVAL;
138 
139     offset = ((unsigned int)pa & ~PAGE_MASK) / sizeof(struct desc_struct);
140 
141     *(uint64_t *)&d = desc;
142 
143     page = get_page_from_gfn(currd, gmfn, NULL, P2M_ALLOC);
144     if ( (((unsigned int)pa % sizeof(struct desc_struct)) != 0) ||
145          !page ||
146          !check_descriptor(currd, &d) )
147     {
148         if ( page )
149             put_page(page);
150         return -EINVAL;
151     }
152     mfn = mfn_x(page_to_mfn(page));
153 
154     /* Check if the given frame is in use in an unsafe context. */
155     switch ( page->u.inuse.type_info & PGT_type_mask )
156     {
157     case PGT_seg_desc_page:
158         if ( unlikely(!get_page_type(page, PGT_seg_desc_page)) )
159             goto out;
160         break;
161     default:
162         if ( unlikely(!get_page_type(page, PGT_writable_page)) )
163             goto out;
164         break;
165     }
166 
167     paging_mark_dirty(currd, _mfn(mfn));
168 
169     /* All is good so make the update. */
170     gdt_pent = map_domain_page(_mfn(mfn));
171     write_atomic((uint64_t *)&gdt_pent[offset], *(uint64_t *)&d);
172     unmap_domain_page(gdt_pent);
173 
174     put_page_type(page);
175 
176     ret = 0; /* success */
177 
178  out:
179     put_page(page);
180 
181     return ret;
182 }
183 
compat_set_gdt(XEN_GUEST_HANDLE_PARAM (uint)frame_list,unsigned int entries)184 int compat_set_gdt(XEN_GUEST_HANDLE_PARAM(uint) frame_list, unsigned int entries)
185 {
186     unsigned int i, nr_pages = (entries + 511) / 512;
187     unsigned long frames[16];
188     int ret;
189 
190     /* Rechecked in set_gdt, but ensures a sane limit for copy_from_user(). */
191     if ( entries > FIRST_RESERVED_GDT_ENTRY )
192         return -EINVAL;
193 
194     if ( !guest_handle_okay(frame_list, nr_pages) )
195         return -EFAULT;
196 
197     for ( i = 0; i < nr_pages; ++i )
198     {
199         unsigned int frame;
200 
201         if ( __copy_from_guest(&frame, frame_list, 1) )
202             return -EFAULT;
203         frames[i] = frame;
204         guest_handle_add_offset(frame_list, 1);
205     }
206 
207     domain_lock(current->domain);
208 
209     if ( (ret = pv_set_gdt(current, frames, entries)) == 0 )
210         flush_tlb_local();
211 
212     domain_unlock(current->domain);
213 
214     return ret;
215 }
216 
compat_update_descriptor(uint32_t pa_lo,uint32_t pa_hi,uint32_t desc_lo,uint32_t desc_hi)217 int compat_update_descriptor(uint32_t pa_lo, uint32_t pa_hi,
218                              uint32_t desc_lo, uint32_t desc_hi)
219 {
220     return do_update_descriptor(pa_lo | ((uint64_t)pa_hi << 32),
221                                 desc_lo | ((uint64_t)desc_hi << 32));
222 }
223 
224 /*
225  * Local variables:
226  * mode: C
227  * c-file-style: "BSD"
228  * c-basic-offset: 4
229  * tab-width: 4
230  * indent-tabs-mode: nil
231  * End:
232  */
233