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 #ifdef CONFIG_VIDEO
44     u16 boot_vid_mode;
45     u16 vesa_width;
46     u16 vesa_height;
47     u16 vesa_depth;
48 #endif
49 } early_boot_opts_t;
50 
51 /*
52  * Space and TAB are obvious delimiters. However, I am
53  * adding "\n" and "\r" here too. Just in case when
54  * crazy bootloader/user puts them somewhere.
55  */
56 static const char delim_chars_comma[] = ", \n\r\t";
57 
58 #define delim_chars	(delim_chars_comma + 1)
59 
strlen(const char * s)60 static size_t strlen(const char *s)
61 {
62     const char *sc;
63 
64     for ( sc = s; *sc != '\0'; ++sc )
65         /* nothing */;
66     return sc - s;
67 }
68 
strncmp(const char * cs,const char * ct,size_t count)69 static int strncmp(const char *cs, const char *ct, size_t count)
70 {
71     unsigned char c1, c2;
72 
73     while ( count )
74     {
75         c1 = *cs++;
76         c2 = *ct++;
77         if ( c1 != c2 )
78             return c1 < c2 ? -1 : 1;
79         if ( !c1 )
80             break;
81         count--;
82     }
83     return 0;
84 }
85 
strchr(const char * s,int c)86 static char *strchr(const char *s, int c)
87 {
88     for ( ; *s != (char)c; ++s )
89         if ( *s == '\0' )
90             return NULL;
91     return (char *)s;
92 }
93 
strspn(const char * s,const char * accept)94 static size_t strspn(const char *s, const char *accept)
95 {
96     const char *p;
97     const char *a;
98     size_t count = 0;
99 
100     for ( p = s; *p != '\0'; ++p )
101     {
102         for ( a = accept; *a != '\0'; ++a )
103         {
104             if ( *p == *a )
105                 break;
106         }
107         if ( *a == '\0' )
108             return count;
109         ++count;
110     }
111     return count;
112 }
113 
strcspn(const char * s,const char * reject)114 static size_t strcspn(const char *s, const char *reject)
115 {
116     const char *p;
117     const char *r;
118     size_t count = 0;
119 
120     for ( p = s; *p != '\0'; ++p )
121     {
122         for ( r = reject; *r != '\0'; ++r )
123         {
124             if ( *p == *r )
125                 return count;
126         }
127         ++count;
128     }
129     return count;
130 }
131 
strtoui(const char * s,const char * stop,const char ** next)132 static unsigned int __maybe_unused strtoui(
133     const char *s, const char *stop, const char **next)
134 {
135     char base = 10, l;
136     unsigned long long res = 0;
137 
138     if ( *s == '0' )
139       base = (tolower(*++s) == 'x') ? (++s, 16) : 8;
140 
141     for ( ; *s != '\0'; ++s )
142     {
143         if ( stop && strchr(stop, *s) )
144             goto out;
145 
146         if ( *s < '0' || (*s > '7' && base == 8) )
147         {
148             res = UINT_MAX;
149             goto out;
150         }
151 
152         l = tolower(*s);
153 
154         if ( *s > '9' && (base != 16 || l < 'a' || l > 'f') )
155         {
156             res = UINT_MAX;
157             goto out;
158         }
159 
160         res *= base;
161         res += (l >= 'a') ? (l - 'a' + 10) : (*s - '0');
162 
163         if ( res >= UINT_MAX )
164         {
165             res = UINT_MAX;
166             goto out;
167         }
168     }
169 
170  out:
171     if ( next )
172       *next = s;
173 
174     return res;
175 }
176 
strmaxcmp(const char * cs,const char * ct,const char * _delim_chars)177 static int strmaxcmp(const char *cs, const char *ct, const char *_delim_chars)
178 {
179     return strncmp(cs, ct, max(strcspn(cs, _delim_chars), strlen(ct)));
180 }
181 
strsubcmp(const char * cs,const char * ct)182 static int __maybe_unused strsubcmp(const char *cs, const char *ct)
183 {
184     return strncmp(cs, ct, strlen(ct));
185 }
186 
find_opt(const char * cmdline,const char * opt,bool arg)187 static const char *find_opt(const char *cmdline, const char *opt, bool arg)
188 {
189     size_t lc, lo;
190 
191     lo = strlen(opt);
192 
193     for ( ; ; )
194     {
195         cmdline += strspn(cmdline, delim_chars);
196 
197         if ( *cmdline == '\0' )
198             return NULL;
199 
200         if ( !strmaxcmp(cmdline, "--", delim_chars) )
201             return NULL;
202 
203         lc = strcspn(cmdline, delim_chars);
204 
205         if ( !strncmp(cmdline, opt, arg ? lo : max(lc, lo)) )
206             return cmdline + lo;
207 
208         cmdline += lc;
209     }
210 }
211 
skip_realmode(const char * cmdline)212 static bool skip_realmode(const char *cmdline)
213 {
214     return find_opt(cmdline, "no-real-mode", false) || find_opt(cmdline, "tboot=", true);
215 }
216 
edd_parse(const char * cmdline)217 static u8 edd_parse(const char *cmdline)
218 {
219     const char *c;
220 
221     c = find_opt(cmdline, "edd=", true);
222 
223     if ( !c )
224         return 0;
225 
226     if ( !strmaxcmp(c, "off", delim_chars) )
227         return 2;
228 
229     return !strmaxcmp(c, "skipmbr", delim_chars);
230 }
231 
edid_parse(const char * cmdline)232 static u8 edid_parse(const char *cmdline)
233 {
234     const char *c;
235 
236     c = find_opt(cmdline, "edid=", true);
237 
238     if ( !c )
239         return 0;
240 
241     if ( !strmaxcmp(c, "force", delim_chars) )
242         return 2;
243 
244     return !strmaxcmp(c, "no", delim_chars);
245 }
246 
247 #ifdef CONFIG_VIDEO
rows2vmode(unsigned int rows)248 static u16 rows2vmode(unsigned int rows)
249 {
250     switch ( rows )
251     {
252     case 25:
253         return VIDEO_80x25;
254 
255     case 28:
256         return VIDEO_80x28;
257 
258     case 30:
259         return VIDEO_80x30;
260 
261     case 34:
262         return VIDEO_80x34;
263 
264     case 43:
265         return VIDEO_80x43;
266 
267     case 50:
268         return VIDEO_80x50;
269 
270     case 60:
271         return VIDEO_80x60;
272 
273     default:
274         return ASK_VGA;
275     }
276 }
277 
vga_parse(const char * cmdline,early_boot_opts_t * ebo)278 static void vga_parse(const char *cmdline, early_boot_opts_t *ebo)
279 {
280     const char *c;
281     unsigned int tmp, vesa_depth, vesa_height, vesa_width;
282 
283     c = find_opt(cmdline, "vga=", true);
284 
285     if ( !c )
286         return;
287 
288     ebo->boot_vid_mode = ASK_VGA;
289 
290     if ( !strmaxcmp(c, "current", delim_chars_comma) )
291         ebo->boot_vid_mode = VIDEO_CURRENT_MODE;
292     else if ( !strsubcmp(c, "text-80x") )
293     {
294         c += strlen("text-80x");
295         ebo->boot_vid_mode = rows2vmode(strtoui(c, delim_chars_comma, NULL));
296     }
297     else if ( !strsubcmp(c, "gfx-") )
298     {
299         vesa_width = strtoui(c + strlen("gfx-"), "x", &c);
300 
301         if ( vesa_width > U16_MAX )
302             return;
303 
304         /*
305          * Increment c outside of strtoui() because otherwise some
306          * compiler may complain with following message:
307          * warning: operation on 'c' may be undefined.
308          */
309         ++c;
310         vesa_height = strtoui(c, "x", &c);
311 
312         if ( vesa_height > U16_MAX )
313             return;
314 
315         vesa_depth = strtoui(++c, delim_chars_comma, NULL);
316 
317         if ( vesa_depth > U16_MAX )
318             return;
319 
320         ebo->vesa_width = vesa_width;
321         ebo->vesa_height = vesa_height;
322         ebo->vesa_depth = vesa_depth;
323         ebo->boot_vid_mode = VIDEO_VESA_BY_SIZE;
324     }
325     else if ( !strsubcmp(c, "mode-") )
326     {
327         tmp = strtoui(c + strlen("mode-"), delim_chars_comma, NULL);
328 
329         if ( tmp > U16_MAX )
330             return;
331 
332         ebo->boot_vid_mode = tmp;
333     }
334 }
335 #endif
336 
cmdline_parse_early(const char * cmdline,early_boot_opts_t * ebo)337 void __stdcall cmdline_parse_early(const char *cmdline, early_boot_opts_t *ebo)
338 {
339     if ( !cmdline )
340         return;
341 
342     ebo->skip_realmode = skip_realmode(cmdline);
343     ebo->opt_edd = edd_parse(cmdline);
344     ebo->opt_edid = edid_parse(cmdline);
345 
346 #ifdef CONFIG_VIDEO
347     vga_parse(cmdline, ebo);
348 #endif
349 }
350