]> rtime.felk.cvut.cz Git - jailhouse.git/blob - tools/jailhouse-config-create
tools: config-create: make sure we are root when reading input files
[jailhouse.git] / tools / jailhouse-config-create
1 #!/usr/bin/env python
2 #
3 # Jailhouse, a Linux-based partitioning hypervisor
4 #
5 # Copyright (c) Siemens AG, 2014
6 #
7 # This work is licensed under the terms of the GNU GPL, version 2.  See
8 # the COPYING file in the top-level directory.
9 #
10 # This script should help to create a basic jailhouse configuration file.
11 # It needs to be executed on the target machine, where it will gather
12 # information about the system. For more advanced scenarios you will have
13 # to change the generated C-code.
14
15 from __future__ import print_function
16 import sys
17 import os
18 import re
19 import argparse
20 import struct
21 from mako.template import Template
22
23 abspath = os.path.abspath(os.path.dirname(sys.argv[0]))
24
25 # pretend to be part of the jailhouse tool
26 sys.argv[0] = sys.argv[0].replace('-', ' ')
27
28 parser = argparse.ArgumentParser()
29 parser.add_argument('-g', '--generate-collector',
30                     help='generate a script to collect input files on '
31                          'a remote machine',
32                     action='store_true')
33 parser.add_argument('-r', '--root',
34                     help='gather information in ROOT/, the default is "/" '
35                          'which means creating a config for localhost',
36                     default='/',
37                     action='store',
38                     type=str)
39 parser.add_argument('-t', '--template-dir',
40                     help='the directory where the templates are located,'
41                          'the default is "' + abspath + '"',
42                     default=abspath,
43                     action='store',
44                     type=str)
45
46 memargs = [['--mem-inmates', '2M', 'inmate'],
47            ['--mem-hv', '64M', 'hypervisor']]
48
49 for entry in memargs:
50     parser.add_argument(entry[0],
51                         help='the amount of ' + entry[2] +
52                              ' memory, default is "' + entry[1] +
53                              '", format "xxx[K|M|G]"',
54                         default=entry[1],
55                         action='store',
56                         type=str)
57
58 parser.add_argument('file', metavar='FILE',
59                     help='name of file to write out',
60                     type=str)
61
62 options = parser.parse_args()
63
64 inputs = {'files': set(), 'files_opt': set(), 'dirs': set()}
65
66
67 class PCICapability:
68     def __init__(self, id, start, len, flags):
69         self.id = id
70         self.start = start
71         self.len = len
72         self.flags = flags
73         self.comments = []
74
75     def __eq__(self, other):
76         return self.id == other.id and self.start == other.start and \
77             self.len == other.len and self.flags == other.flags
78
79     RD = '0'
80     RW = 'JAILHOUSE_PCICAPS_WRITE'
81
82     @staticmethod
83     def parse_pcicaps(dir):
84         caps = []
85         f = input_open(os.path.join(dir, 'config'), 'rb')
86         f.seek(0x06)
87         (status,) = struct.unpack('<H', f.read(2))
88         # capability list supported?
89         if (status & (1 << 4)) == 0:
90             f.close()
91             return caps
92         # walk capability list
93         f.seek(0x34)
94         (next,) = struct.unpack('B', f.read(1))
95         while next != 0:
96             cap = next
97             f.seek(cap)
98             (id, next) = struct.unpack('<BB', f.read(2))
99             if id == 0x01:  # Power Management
100                 # this cap can be handed out completely
101                 len = 8
102                 flags = PCICapability.RW
103             elif id == 0x05:  # MSI
104                 # access will be moderated by hypervisor
105                 len = 10
106                 (msgctl,) = struct.unpack('<H', f.read(2))
107                 if (msgctl & (1 << 7)) != 0:  # 64-bit support
108                     len += 4
109                 if (msgctl & (1 << 8)) != 0:  # per-vector masking support
110                     len += 10
111                 flags = PCICapability.RW
112             elif id == 0x11:  # MSI-X
113                 # access will be moderated by hypervisor
114                 len = 12
115                 flags = PCICapability.RW
116             else:
117                 # unknown/unhandled cap, mark its existence
118                 len = 2
119                 flags = PCICapability.RD
120             caps.append(PCICapability(id, cap, len, flags))
121         return caps
122
123
124 class PCIDevice:
125     def __init__(self, type, domain, bus, dev, fn, caps):
126         self.type = type
127         self.domain = domain
128         self.bus = bus
129         self.dev = dev
130         self.fn = fn
131         self.caps = caps
132         self.caps_start = 0
133         self.num_caps = len(caps)
134
135     def __str__(self):
136         return 'PCIDevice: %02x:%02x.%x' % (self.bus, self.dev, self.fn)
137
138     def bdf(self):
139         return self.bus << 8 | self.dev << 3 | self.fn
140
141     @staticmethod
142     def parse_pcidevice_sysfsdir(basedir, dir):
143         dpath = os.path.join(basedir, dir)
144         dclass = input_readline(os.path.join(dpath, 'class'))
145         if re.match(r'0x0604..', dclass):
146             type = 'JAILHOUSE_PCI_TYPE_BRIDGE'
147         else:
148             type = 'JAILHOUSE_PCI_TYPE_DEVICE'
149         a = dir.split(':')
150         domain = int(a[0], 16)
151         bus = int(a[1], 16)
152         df = a[2].split('.')
153         caps = PCICapability.parse_pcicaps(dpath)
154         return PCIDevice(type, domain, bus, int(df[0], 16), int(df[1], 16),
155                          caps)
156
157
158 class MemRegion:
159     def __init__(self, start, stop, typestr, comments=[]):
160         self.start = start
161         self.stop = stop
162         self.typestr = typestr
163         self.comments = comments
164
165     def __str__(self):
166         return 'MemRegion: %08x-%08x : %s' % \
167             (self.start, self.stop, self.typestr)
168
169     def size(self):
170         # round up to full PAGE_SIZE
171         return int((self.stop - self.start + 0xfff) / 0x1000) * 0x1000
172
173     def flagstr(self, p=''):
174         if (
175             self.typestr == 'ACPI Tables' or
176             self.typestr == 'ACPI Non-volatile Storage'
177         ):
178             return 'JAILHOUSE_MEM_READ'
179         if (
180             self.typestr == 'System RAM' or
181             self.typestr == 'RAM buffer' or
182             self.typestr == 'ACPI DMAR RMRR'
183         ):
184             s = 'JAILHOUSE_MEM_READ | JAILHOUSE_MEM_WRITE |\n'
185             s += p + '\t\tJAILHOUSE_MEM_EXECUTE | JAILHOUSE_MEM_DMA'
186             return s
187         return 'JAILHOUSE_MEM_READ | JAILHOUSE_MEM_WRITE'
188
189     @staticmethod
190     # return the first region with the given typestr
191     def find_region(regions, typestr):
192         for r in regions:
193             if (r.typestr == typestr):
194                 return r
195         return None
196
197     @staticmethod
198     def parse_iomem_line(line):
199         a = line.split(':', 1)
200         # HPET may be part of in reserved region
201         if a[0].startswith(' ') and a[1].find("HPET") < 0:
202             return None
203         region = a[0].split('-', 1)
204         a[1] = a[1].strip()
205         return MemRegion(int(region[0], 16), int(region[1], 16), a[1])
206
207
208 def parse_iomem():
209     regions = []
210     f = input_open('/proc/iomem')
211     for line in f:
212         r = MemRegion.parse_iomem_line(line)
213         ## XXX what else to ignore??
214         if (
215             r is not None and
216             r.typestr != 'Local APIC' and
217             r.typestr != 'reserved'
218         ):
219             regions.append(r)
220     f.close()
221
222     # newer Linux kernels will report the first page as reserved
223     # it is needed for CPU init so include it anyways
224     if (
225         regions[0].typestr == 'System RAM' and
226         regions[0].start == 0x1000
227     ):
228         regions[0].start = 0
229
230     return regions
231
232
233 def parse_pcidevices():
234     devices = []
235     caps = []
236     basedir = '/sys/bus/pci/devices'
237     list = input_listdir(basedir, ['*/class', '*/config'])
238     for dir in list:
239         d = PCIDevice.parse_pcidevice_sysfsdir(basedir, dir)
240         if d is not None:
241             if len(d.caps) > 0:
242                 duplicate = False
243                 # look for duplicate capability patterns
244                 for d2 in devices:
245                     if d2.caps == d.caps:
246                         # reused existing capability list, but record all users
247                         d2.caps[0].comments.append(str(d))
248                         d.caps_start = d2.caps_start
249                         duplicate = True
250                         break
251                 if not duplicate:
252                     d.caps[0].comments.append(str(d))
253                     d.caps_start = len(caps)
254                     caps.extend(d.caps)
255             devices.append(d)
256     return (devices, caps)
257
258
259 def kmg_multiply(value, kmg):
260     if (kmg == 'K' or kmg == 'k'):
261         return 1024 * value
262     if (kmg == 'M' or kmg == 'm'):
263         return 1024**2 * value
264     if (kmg == 'G' or kmg == 'g'):
265         return 1024**3 * value
266     return value
267
268
269 def kmg_multiply_str(str):
270     m = re.match(r'([0-9a-fA-FxX]+)([KMG]?)', str)
271     if m is not None:
272         return kmg_multiply(int(m.group(1)), m.group(2))
273     raise RuntimeError('kmg_multiply_str can not parse input "' + str + '"')
274     return 0
275
276
277 def input_open(name, mode='r', optional=False):
278     inputs['files_opt' if optional else 'files'].add(name)
279     try:
280         f = open(options.root + name, mode)
281     except Exception as e:
282         if optional or options.generate_collector:
283             return open("/dev/null", mode)
284         raise e
285     return f
286
287
288 def input_readline(name, optional=False):
289     f = input_open(name, optional=optional)
290     line = f.readline()
291     f.close()
292     return line
293
294
295 def input_listdir(dir, wildcards):
296     for w in wildcards:
297         inputs['dirs'].add(os.path.join(dir, w))
298     if options.generate_collector:
299         return []
300     dirs = os.listdir(options.root + dir)
301     dirs.sort()
302     return dirs
303
304
305 def parse_cmdline():
306     line = input_readline('/proc/cmdline')
307     m = re.match(r'.*memmap=([0-9a-fA-FxX]+)([KMG]?)\$'
308                  '([0-9a-fA-FxX]+)([KMG]?).*',
309                  line)
310     if m is not None:
311         size = kmg_multiply(int(m.group(1), 0), m.group(2))
312         start = kmg_multiply(int(m.group(3), 0), m.group(4))
313         return [start, size]
314     return None
315
316
317 def alloc_mem(regions, size):
318     mem = [0, size]
319     for r in reversed(regions):
320         if (r.typestr == 'System RAM' and r.size() >= mem[1]):
321             mem[0] = r.start
322             r.start += mem[1]
323             return mem
324     raise RuntimeError('failed to allocate memory')
325
326
327 def count_cpus():
328     list = input_listdir('/sys/devices/system/cpu', ['cpu*/uevent'])
329     count = 0
330     for f in list:
331         if re.match(r'cpu[0-9]+', f):
332             count += 1
333     return count
334
335
336 def parse_dmar_devscope(f):
337     offset = 0
338     (scope_type, scope_len, bus, dev, fn) = \
339         struct.unpack('<BBxxxBBB', f.read(8))
340     offset += 8
341     return (offset, scope_type, scope_len, bus, dev, fn)
342
343
344 # parsing of DMAR ACPI Table
345 # see Intel VT-d Spec chapter 8
346 def parse_dmar():
347     f = input_open('/sys/firmware/acpi/tables/DMAR', 'rb')
348     signature = f.read(4)
349     if signature != b'DMAR':
350         if options.generate_collector:
351             return [], 0, []
352         raise RuntimeError('incorrect input file format %s' % signature)
353     (length,) = struct.unpack('<I', f.read(4))
354     f.seek(48)
355     length -= 48
356     units = []
357     regions = []
358     ioapic_id = 0
359
360     while length > 0:
361         offset = 0
362         (struct_type, struct_len) = struct.unpack('<HH', f.read(4))
363         offset += 4
364         length -= struct_len
365
366         # DMA Remapping Hardware Unit Definition
367         if struct_type == 0:
368             (segment, base) = struct.unpack('<xxHQ', f.read(12))
369             if segment != 0:
370                 raise RuntimeError('We do not support multiple PCI segments')
371             if len(units) >= 8:
372                 raise RuntimeError('Too many DMAR units. '
373                                    'Raise JAILHOUSE_MAX_DMAR_UNITS.')
374             units.append(base)
375             offset += 16 - offset
376             while offset < struct_len:
377                 (off, scope_type, scope_len, bus, dev, fn) =\
378                     parse_dmar_devscope(f)
379                 offset += off
380                 if scope_type == 3:
381                     if ioapic_id != 0:
382                         raise RuntimeError('We do not support more '
383                                            'than 1 IOAPIC')
384                     ioapic_id = (bus << 8) | (dev << 3) | fn
385                 f.seek(scope_len - 8, os.SEEK_CUR)
386                 offset += scope_len - 8
387
388         # Reserved Memory Region Reporting Structure
389         if struct_type == 1:
390             f.seek(8 - offset, os.SEEK_CUR)
391             offset += 8 - offset
392             (base, limit) = struct.unpack('<QQ', f.read(16))
393             offset += 16
394
395             comments = []
396             while offset < struct_len:
397                 (off, scope_type, scope_len, bus, dev, fn) =\
398                     parse_dmar_devscope(f)
399                 offset += off
400                 npath = (scope_len - 6)/2
401                 if scope_type == 1 and npath == 1:
402                     comments.append('PCI device: %02x:%02x.%x' %
403                                     (bus, dev, fn))
404                 else:
405                     comments.append('DMAR parser could not decode device path')
406                 f.seek(scope_len - off, os.SEEK_CUR)
407                 offset += scope_len - off
408
409             reg = MemRegion(base, limit, 'ACPI DMAR RMRR', comments)
410             regions.append(reg)
411
412         f.seek(struct_len - offset, os.SEEK_CUR)
413
414     return units, ioapic_id, regions
415
416
417 def parse_ioports():
418     pm_timer_base = None
419     f = input_open('/proc/ioports')
420     for line in f:
421         if line.endswith('ACPI PM_TMR\n'):
422             pm_timer_base = int(line.split('-')[0], 16)
423             break
424     f.close()
425     return pm_timer_base
426
427
428 class MMConfig:
429     def __init__(self, base, end_bus):
430         self.base = base
431         self.end_bus = end_bus
432
433     @staticmethod
434     def parse():
435         f = input_open('/sys/firmware/acpi/tables/MCFG', 'rb')
436         signature = f.read(4)
437         if signature != b'MCFG':
438             if options.generate_collector:
439                 return MMConfig(0, 0)
440             raise RuntimeError('incorrect input file format %s' % signature)
441         (length,) = struct.unpack('<I', f.read(4))
442         if length > 60:
443             raise RuntimeError('Multiple MMCONFIG regions found! '
444                                'This is not supported')
445         f.seek(44)
446         (base, segment, start_bus, end_bus) = \
447             struct.unpack('<QHBB', f.read(12))
448         if segment != 0 or start_bus != 0:
449             raise RuntimeError('Invalid MCFG structure found')
450         return MMConfig(base, end_bus)
451
452
453 if (
454     (options.generate_collector is False) and (options.root is '/')
455     and (os.geteuid() is not 0)
456 ):
457     print('ERROR: You have to be root to work on "/"!', file=sys.stderr)
458     sys.exit(1)
459
460 (pcidevices, pcicaps) = parse_pcidevices()
461
462 product = [input_readline('/sys/class/dmi/id/sys_vendor',
463                           True).rstrip(),
464            input_readline('/sys/class/dmi/id/product_name',
465                           True).rstrip()
466            ]
467
468 inmatemem = kmg_multiply_str(options.mem_inmates)
469 hvmem = [0, kmg_multiply_str(options.mem_hv)]
470
471 regions = parse_iomem()
472 ourmem = parse_cmdline()
473 total = hvmem[1] + inmatemem
474
475 mmconfig = MMConfig.parse()
476
477 (dmar_units, ioapic_id, rmrr_regs) = parse_dmar()
478 regions += rmrr_regs
479
480 # kernel does not have memmap region, pick one
481 if ourmem is None:
482     ourmem = alloc_mem(regions, total)
483 elif (total > ourmem[1]):
484     raise RuntimeError('Your memmap reservation is too small you need >="' +
485                        hex(total) + '"')
486
487 hvmem[0] = ourmem[0]
488
489 inmatereg = MemRegion(ourmem[0] + hvmem[1],
490                       ourmem[0] + hvmem[1] + inmatemem - 1,
491                       'JAILHOUSE Inmate Memory')
492 regions.append(inmatereg)
493
494 cpucount = count_cpus()
495
496 pm_timer_base = parse_ioports()
497
498 jh_enabled = input_readline('/sys/devices/jailhouse/enabled',
499                             True).rstrip()
500 if options.generate_collector is False and jh_enabled == '1':
501     print('ERROR: Jailhouse was enabled when collecting input files! '
502           'Disable jailhouse and try again.',
503           file=sys.stderr)
504     sys.exit(1)
505
506 f = open(options.file, 'w')
507
508 if options.generate_collector:
509     filelist = ' '.join(inputs['files'].union(inputs['dirs']))
510     filelist_opt = ' '.join(inputs['files_opt'])
511
512     tmpl = Template(filename=os.path.join(options.template_dir,
513                                           'jailhouse-config-collect.tmpl'))
514     f.write(tmpl.render(filelist=filelist, filelist_opt=filelist_opt))
515 else:
516     tmpl = Template(filename=os.path.join(options.template_dir,
517                                           'root-cell-config.c.tmpl'))
518     f.write(tmpl.render(regions=regions,
519                         ourmem=ourmem,
520                         argstr=' '.join(sys.argv),
521                         hvmem=hvmem,
522                         product=product,
523                         pcidevices=pcidevices,
524                         pcicaps=pcicaps,
525                         cpucount=cpucount,
526                         ioapic_id=ioapic_id,
527                         pm_timer_base=pm_timer_base,
528                         mmconfig=mmconfig,
529                         dmar_units=dmar_units))
530
531 f.close()