1 /*
2  * Copyright (c) 2015, 2016 Oracle and/or its affiliates. All rights reserved.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  * strlen(), strncmp(), strchr(), strspn() and strcspn() were copied from
18  * Linux kernel source (linux/lib/string.c).
19  */
20 
21 /*
22  * This entry point is entered from xen/arch/x86/boot/head.S with:
23  *   - 0x4(%esp) = &cmdline,
24  *   - 0x8(%esp) = &early_boot_opts.
25  */
26 asm (
27     "    .text                         \n"
28     "    .globl _start                 \n"
29     "_start:                           \n"
30     "    jmp  cmdline_parse_early      \n"
31     );
32 
33 #include <xen/kconfig.h>
34 #include "defs.h"
35 #include "video.h"
36 
37 /* Keep in sync with trampoline.S:early_boot_opts label! */
38 typedef struct __packed {
39     u8 skip_realmode;
40     u8 opt_edd;
41     u8 opt_edid;
42     u8 padding;
43     u16 boot_vid_mode;
44     u16 vesa_width;
45     u16 vesa_height;
46     u16 vesa_depth;
47 } early_boot_opts_t;
48 
49 /*
50  * Space and TAB are obvious delimiters. However, I am
51  * adding "\n" and "\r" here too. Just in case when
52  * crazy bootloader/user puts them somewhere.
53  */
54 static const char delim_chars_comma[] = ", \n\r\t";
55 
56 #define delim_chars	(delim_chars_comma + 1)
57 
strlen(const char * s)58 static size_t strlen(const char *s)
59 {
60     const char *sc;
61 
62     for ( sc = s; *sc != '\0'; ++sc )
63         /* nothing */;
64     return sc - s;
65 }
66 
strncmp(const char * cs,const char * ct,size_t count)67 static int strncmp(const char *cs, const char *ct, size_t count)
68 {
69     unsigned char c1, c2;
70 
71     while ( count )
72     {
73         c1 = *cs++;
74         c2 = *ct++;
75         if ( c1 != c2 )
76             return c1 < c2 ? -1 : 1;
77         if ( !c1 )
78             break;
79         count--;
80     }
81     return 0;
82 }
83 
strchr(const char * s,int c)84 static char *strchr(const char *s, int c)
85 {
86     for ( ; *s != (char)c; ++s )
87         if ( *s == '\0' )
88             return NULL;
89     return (char *)s;
90 }
91 
strspn(const char * s,const char * accept)92 static size_t strspn(const char *s, const char *accept)
93 {
94     const char *p;
95     const char *a;
96     size_t count = 0;
97 
98     for ( p = s; *p != '\0'; ++p )
99     {
100         for ( a = accept; *a != '\0'; ++a )
101         {
102             if ( *p == *a )
103                 break;
104         }
105         if ( *a == '\0' )
106             return count;
107         ++count;
108     }
109     return count;
110 }
111 
strcspn(const char * s,const char * reject)112 static size_t strcspn(const char *s, const char *reject)
113 {
114     const char *p;
115     const char *r;
116     size_t count = 0;
117 
118     for ( p = s; *p != '\0'; ++p )
119     {
120         for ( r = reject; *r != '\0'; ++r )
121         {
122             if ( *p == *r )
123                 return count;
124         }
125         ++count;
126     }
127     return count;
128 }
129 
strtoui(const char * s,const char * stop,const char ** next)130 static unsigned int strtoui(const char *s, const char *stop, const char **next)
131 {
132     char base = 10, l;
133     unsigned long long res = 0;
134 
135     if ( *s == '0' )
136       base = (tolower(*++s) == 'x') ? (++s, 16) : 8;
137 
138     for ( ; *s != '\0'; ++s )
139     {
140         if ( stop && strchr(stop, *s) )
141             goto out;
142 
143         if ( *s < '0' || (*s > '7' && base == 8) )
144         {
145             res = UINT_MAX;
146             goto out;
147         }
148 
149         l = tolower(*s);
150 
151         if ( *s > '9' && (base != 16 || l < 'a' || l > 'f') )
152         {
153             res = UINT_MAX;
154             goto out;
155         }
156 
157         res *= base;
158         res += (l >= 'a') ? (l - 'a' + 10) : (*s - '0');
159 
160         if ( res >= UINT_MAX )
161         {
162             res = UINT_MAX;
163             goto out;
164         }
165     }
166 
167  out:
168     if ( next )
169       *next = s;
170 
171     return res;
172 }
173 
strmaxcmp(const char * cs,const char * ct,const char * _delim_chars)174 static int strmaxcmp(const char *cs, const char *ct, const char *_delim_chars)
175 {
176     return strncmp(cs, ct, max(strcspn(cs, _delim_chars), strlen(ct)));
177 }
178 
strsubcmp(const char * cs,const char * ct)179 static int strsubcmp(const char *cs, const char *ct)
180 {
181     return strncmp(cs, ct, strlen(ct));
182 }
183 
find_opt(const char * cmdline,const char * opt,bool arg)184 static const char *find_opt(const char *cmdline, const char *opt, bool arg)
185 {
186     size_t lc, lo;
187 
188     lo = strlen(opt);
189 
190     for ( ; ; )
191     {
192         cmdline += strspn(cmdline, delim_chars);
193 
194         if ( *cmdline == '\0' )
195             return NULL;
196 
197         if ( !strmaxcmp(cmdline, "--", delim_chars) )
198             return NULL;
199 
200         lc = strcspn(cmdline, delim_chars);
201 
202         if ( !strncmp(cmdline, opt, arg ? lo : max(lc, lo)) )
203             return cmdline + lo;
204 
205         cmdline += lc;
206     }
207 }
208 
skip_realmode(const char * cmdline)209 static bool skip_realmode(const char *cmdline)
210 {
211     return find_opt(cmdline, "no-real-mode", false) || find_opt(cmdline, "tboot=", true);
212 }
213 
edd_parse(const char * cmdline)214 static u8 edd_parse(const char *cmdline)
215 {
216     const char *c;
217 
218     c = find_opt(cmdline, "edd=", true);
219 
220     if ( !c )
221         return 0;
222 
223     if ( !strmaxcmp(c, "off", delim_chars) )
224         return 2;
225 
226     return !strmaxcmp(c, "skipmbr", delim_chars);
227 }
228 
edid_parse(const char * cmdline)229 static u8 edid_parse(const char *cmdline)
230 {
231     const char *c;
232 
233     c = find_opt(cmdline, "edid=", true);
234 
235     if ( !c )
236         return 0;
237 
238     if ( !strmaxcmp(c, "force", delim_chars) )
239         return 2;
240 
241     return !strmaxcmp(c, "no", delim_chars);
242 }
243 
rows2vmode(unsigned int rows)244 static u16 rows2vmode(unsigned int rows)
245 {
246     switch ( rows )
247     {
248     case 25:
249         return VIDEO_80x25;
250 
251     case 28:
252         return VIDEO_80x28;
253 
254     case 30:
255         return VIDEO_80x30;
256 
257     case 34:
258         return VIDEO_80x34;
259 
260     case 43:
261         return VIDEO_80x43;
262 
263     case 50:
264         return VIDEO_80x50;
265 
266     case 60:
267         return VIDEO_80x60;
268 
269     default:
270         return ASK_VGA;
271     }
272 }
273 
vga_parse(const char * cmdline,early_boot_opts_t * ebo)274 static void vga_parse(const char *cmdline, early_boot_opts_t *ebo)
275 {
276     const char *c;
277     unsigned int tmp, vesa_depth, vesa_height, vesa_width;
278 
279     c = find_opt(cmdline, "vga=", true);
280 
281     if ( !c )
282         return;
283 
284     ebo->boot_vid_mode = ASK_VGA;
285 
286     if ( !strmaxcmp(c, "current", delim_chars_comma) )
287         ebo->boot_vid_mode = VIDEO_CURRENT_MODE;
288     else if ( !strsubcmp(c, "text-80x") )
289     {
290         c += strlen("text-80x");
291         ebo->boot_vid_mode = rows2vmode(strtoui(c, delim_chars_comma, NULL));
292     }
293     else if ( !strsubcmp(c, "gfx-") )
294     {
295         vesa_width = strtoui(c + strlen("gfx-"), "x", &c);
296 
297         if ( vesa_width > U16_MAX )
298             return;
299 
300         /*
301          * Increment c outside of strtoui() because otherwise some
302          * compiler may complain with following message:
303          * warning: operation on 'c' may be undefined.
304          */
305         ++c;
306         vesa_height = strtoui(c, "x", &c);
307 
308         if ( vesa_height > U16_MAX )
309             return;
310 
311         vesa_depth = strtoui(++c, delim_chars_comma, NULL);
312 
313         if ( vesa_depth > U16_MAX )
314             return;
315 
316         ebo->vesa_width = vesa_width;
317         ebo->vesa_height = vesa_height;
318         ebo->vesa_depth = vesa_depth;
319         ebo->boot_vid_mode = VIDEO_VESA_BY_SIZE;
320     }
321     else if ( !strsubcmp(c, "mode-") )
322     {
323         tmp = strtoui(c + strlen("mode-"), delim_chars_comma, NULL);
324 
325         if ( tmp > U16_MAX )
326             return;
327 
328         ebo->boot_vid_mode = tmp;
329     }
330 }
331 
cmdline_parse_early(const char * cmdline,early_boot_opts_t * ebo)332 void __stdcall cmdline_parse_early(const char *cmdline, early_boot_opts_t *ebo)
333 {
334     if ( !cmdline )
335         return;
336 
337     ebo->skip_realmode = skip_realmode(cmdline);
338     ebo->opt_edd = edd_parse(cmdline);
339     ebo->opt_edid = edid_parse(cmdline);
340 
341     if ( IS_ENABLED(CONFIG_VIDEO) )
342         vga_parse(cmdline, ebo);
343 }
344