]> rtime.felk.cvut.cz Git - jailhouse.git/blob - tools/jailhouse-config-create
tooling: Install jailhouse binary under sbin
[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 datadir = None
24
25 if datadir:
26     template_default_dir = datadir + "/jailhouse"
27 else:
28     template_default_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
29
30 # pretend to be part of the jailhouse tool
31 sys.argv[0] = sys.argv[0].replace('-', ' ')
32
33 parser = argparse.ArgumentParser()
34 parser.add_argument('-g', '--generate-collector',
35                     help='generate a script to collect input files on '
36                          'a remote machine',
37                     action='store_true')
38 parser.add_argument('-r', '--root',
39                     help='gather information in ROOT/, the default is "/" '
40                          'which means creating a config for localhost',
41                     default='/',
42                     action='store',
43                     type=str)
44 parser.add_argument('-t', '--template-dir',
45                     help='the directory where the templates are located,'
46                          'the default is "' + template_default_dir + '"',
47                     default=template_default_dir,
48                     action='store',
49                     type=str)
50
51 memargs = [['--mem-inmates', '2M', 'inmate'],
52            ['--mem-hv', '64M', 'hypervisor']]
53
54 for entry in memargs:
55     parser.add_argument(entry[0],
56                         help='the amount of ' + entry[2] +
57                              ' memory, default is "' + entry[1] +
58                              '", format "xxx[K|M|G]"',
59                         default=entry[1],
60                         action='store',
61                         type=str)
62
63 parser.add_argument('file', metavar='FILE',
64                     help='name of file to write out',
65                     type=str)
66
67 options = parser.parse_args()
68
69 inputs = {'files': set(), 'files_opt': set(), 'dirs': set()}
70
71
72 def kmg_multiply(value, kmg):
73     if (kmg == 'K' or kmg == 'k'):
74         return 1024 * value
75     if (kmg == 'M' or kmg == 'm'):
76         return 1024**2 * value
77     if (kmg == 'G' or kmg == 'g'):
78         return 1024**3 * value
79     return value
80
81
82 def kmg_multiply_str(str):
83     m = re.match(r'([0-9a-fA-FxX]+)([KMG]?)', str)
84     if m is not None:
85         return kmg_multiply(int(m.group(1)), m.group(2))
86     raise RuntimeError('kmg_multiply_str can not parse input "' + str + '"')
87     return 0
88
89
90 def input_open(name, mode='r', optional=False):
91     inputs['files_opt' if optional else 'files'].add(name)
92     try:
93         f = open(options.root + name, mode)
94     except Exception as e:
95         if optional or options.generate_collector:
96             return open("/dev/null", mode)
97         raise e
98     return f
99
100
101 def input_readline(name, optional=False):
102     f = input_open(name, optional=optional)
103     line = f.readline()
104     f.close()
105     return line
106
107
108 def input_listdir(dir, wildcards):
109     for w in wildcards:
110         inputs['dirs'].add(os.path.join(dir, w))
111     if options.generate_collector:
112         return []
113     dirs = os.listdir(options.root + dir)
114     dirs.sort()
115     return dirs
116
117
118 class PCICapability:
119     def __init__(self, id, start, len, flags, content, msix_address):
120         self.id = id
121         self.start = start
122         self.len = len
123         self.flags = flags
124         self.content = content
125         self.msix_address = msix_address
126         self.comments = []
127
128     def __eq__(self, other):
129         return self.id == other.id and self.start == other.start and \
130             self.len == other.len and self.flags == other.flags
131
132     RD = '0'
133     RW = 'JAILHOUSE_PCICAPS_WRITE'
134
135     @staticmethod
136     def parse_pcicaps(dir):
137         caps = []
138         f = input_open(os.path.join(dir, 'config'), 'rb')
139         f.seek(0x06)
140         (status,) = struct.unpack('<H', f.read(2))
141         # capability list supported?
142         if (status & (1 << 4)) == 0:
143             f.close()
144             return caps
145         # walk capability list
146         f.seek(0x34)
147         (next,) = struct.unpack('B', f.read(1))
148         while next != 0:
149             cap = next
150             msix_address = 0
151             f.seek(cap)
152             (id, next) = struct.unpack('<BB', f.read(2))
153             if id == 0x01:  # Power Management
154                 # this cap can be handed out completely
155                 len = 8
156                 flags = PCICapability.RW
157             elif id == 0x05:  # MSI
158                 # access will be moderated by hypervisor
159                 len = 10
160                 (msgctl,) = struct.unpack('<H', f.read(2))
161                 if (msgctl & (1 << 7)) != 0:  # 64-bit support
162                     len += 4
163                 if (msgctl & (1 << 8)) != 0:  # per-vector masking support
164                     len += 10
165                 flags = PCICapability.RW
166             elif id == 0x11:  # MSI-X
167                 # access will be moderated by hypervisor
168                 len = 12
169                 (table,) = struct.unpack('<xxI', f.read(6))
170                 f.seek(0x10 + (table & 7) * 4)
171                 (bar,) = struct.unpack('<I', f.read(4))
172                 if (bar & 0x3) != 0:
173                     raise RuntimeError('Invalid MSI-X BAR found')
174                 if (bar & 0x4) != 0:
175                     bar |= struct.unpack('<I', f.read(4))[0] << 32
176                 msix_address = (bar & 0xfffffffffffffff0) + table & 0xfffffff8
177                 flags = PCICapability.RW
178             else:
179                 # unknown/unhandled cap, mark its existence
180                 len = 2
181                 flags = PCICapability.RD
182             f.seek(cap + 2)
183             content = f.read(len - 2)
184             caps.append(PCICapability(id, cap, len, flags, content,
185                                       msix_address))
186         return caps
187
188
189 class PCIDevice:
190     def __init__(self, type, domain, bus, dev, fn, caps):
191         self.type = type
192         self.iommu = None
193         self.domain = domain
194         self.bus = bus
195         self.dev = dev
196         self.fn = fn
197         self.caps = caps
198         self.caps_start = 0
199         self.num_caps = len(caps)
200         self.num_msi_vectors = 0
201         self.msi_64bits = 0
202         self.num_msix_vectors = 0
203         self.msix_region_size = 0
204         self.msix_address = 0
205         for c in caps:
206             if c.id in (0x05, 0x11):
207                 msg_ctrl = struct.unpack('<H', c.content[:2])[0]
208                 if c.id == 0x05:  # MSI
209                     self.num_msi_vectors = 1 << ((msg_ctrl >> 1) & 0x7)
210                     self.msi_64bits = (msg_ctrl >> 7) & 1
211                 else:  # MSI-X
212                     vectors = (msg_ctrl & 0x7ff) + 1
213                     self.num_msix_vectors = vectors
214                     self.msix_region_size = (vectors * 16 + 0xfff) & 0xf000
215                     self.msix_address = c.msix_address
216
217     def __str__(self):
218         return 'PCIDevice: %02x:%02x.%x' % (self.bus, self.dev, self.fn)
219
220     def bdf(self):
221         return self.bus << 8 | self.dev << 3 | self.fn
222
223     @staticmethod
224     def parse_pcidevice_sysfsdir(basedir, dir):
225         dpath = os.path.join(basedir, dir)
226         dclass = input_readline(os.path.join(dpath, 'class'))
227         if re.match(r'0x0604..', dclass):
228             type = 'JAILHOUSE_PCI_TYPE_BRIDGE'
229         else:
230             type = 'JAILHOUSE_PCI_TYPE_DEVICE'
231         a = dir.split(':')
232         domain = int(a[0], 16)
233         bus = int(a[1], 16)
234         df = a[2].split('.')
235         caps = PCICapability.parse_pcicaps(dpath)
236         return PCIDevice(type, domain, bus, int(df[0], 16), int(df[1], 16),
237                          caps)
238
239
240 class MemRegion:
241     def __init__(self, start, stop, typestr, comments=None):
242         self.start = start
243         self.stop = stop
244         self.typestr = typestr
245         if comments is None:
246             self.comments = []
247         else:
248             self.comments = comments
249
250     def __str__(self):
251         return 'MemRegion: %08x-%08x : %s' % \
252             (self.start, self.stop, self.typestr)
253
254     def size(self):
255         # round up to full PAGE_SIZE
256         return int((self.stop - self.start + 0xfff) / 0x1000) * 0x1000
257
258     def flagstr(self, p=''):
259         if (
260             self.typestr == 'System RAM' or
261             self.typestr == 'RAM buffer' or
262             self.typestr == 'ACPI DMAR RMRR'
263         ):
264             s = 'JAILHOUSE_MEM_READ | JAILHOUSE_MEM_WRITE |\n'
265             s += p + '\t\tJAILHOUSE_MEM_EXECUTE | JAILHOUSE_MEM_DMA'
266             return s
267         return 'JAILHOUSE_MEM_READ | JAILHOUSE_MEM_WRITE'
268
269
270 class IOMemRegionTree:
271     def __init__(self, region, level, linenum):
272         self.region = region
273         self.linenum = linenum
274         self.level = level
275         self.parent = None
276         self.children = set()
277
278     def __str__(self):
279         s = ''
280         if (self.region):
281             s = (' ' * (self.level - 1)) + str(self.region) + ' line %d' \
282                 % (self.linenum)
283             if self.parent and self.parent.region:
284                 s += '--> ' + self.parent.region.typestr + ' line %d' \
285                     % (self.parent.linenum)
286             s += '\n'
287         for c in self.children:
288             s += str(c)
289         return s
290
291     @staticmethod
292     def parse_iomem_line(line):
293         a = line.split(':', 1)
294         level = int(a[0].count(' ') / 2) + 1
295         region = a[0].split('-', 1)
296         a[1] = a[1].strip()
297         return level, MemRegion(int(region[0], 16), int(region[1], 16), a[1])
298
299     @staticmethod
300     def parse_iomem_file():
301         root = IOMemRegionTree(None, 0, -1)
302         f = input_open('/proc/iomem')
303         lastlevel = 0
304         lastnode = root
305         linenum = 0
306         for line in f:
307             (level, r) = IOMemRegionTree.parse_iomem_line(line)
308             t = IOMemRegionTree(r, level, linenum)
309             if (t.level > lastlevel):
310                 t.parent = lastnode
311             if (t.level == lastlevel):
312                 t.parent = lastnode.parent
313             if (t.level < lastlevel):
314                 p = lastnode.parent
315                 while(t.level < p.level):
316                     p = p.parent
317                 t.parent = p.parent
318
319             t.parent.children.add(t)
320             lastnode = t
321             lastlevel = t.level
322             linenum += 1
323         f.close()
324
325         return linenum, root
326
327     # recurse down the tree
328     @staticmethod
329     def parse_iomem_tree(tree):
330         regions = []
331         linenumbers = []
332
333         for tree in tree.children:
334             r = tree.region
335             s = r.typestr
336
337             # System RAM on first level will be added without digging deeper
338             if (tree.level == 1 and s == 'System RAM'):
339                 regions.append(r)
340                 linenumbers.append(tree.linenum)
341                 continue
342
343             # blacklisted on all levels
344             if (
345                 (s.find('PCI MMCONFIG') >= 0) or
346                 (s.find('APIC') >= 0) or  # covers both APIC and IOAPIC
347                 (s.find('dmar') >= 0)
348             ):
349                 continue
350
351             # generally blacklisted, unless we find an HPET right behind it
352             # on the next level
353             if (s == 'reserved'):
354                 for subtree in tree.children:
355                     r2 = subtree.region
356                     if (r2.typestr.find('HPET') >= 0):
357                         regions.append(r2)
358                         linenumbers.append(subtree.linenum)
359                 continue
360
361             # if the tree continues recurse further down ...
362             if (len(tree.children) > 0):
363                 ln2, r2 = IOMemRegionTree.parse_iomem_tree(tree)
364                 linenumbers.extend(ln2)
365                 regions.extend(r2)
366                 continue
367
368             # add all remaining leaves
369             regions.append(r)
370             linenumbers.append(tree.linenum)
371
372         return linenumbers, regions
373
374
375 def parse_iomem(pcidevices):
376     (maxsz, tree) = IOMemRegionTree.parse_iomem_file()
377
378     # create a spare array so we can easiely keep the order from the file
379     regions = [None for x in range(maxsz)]
380
381     lines, regs = IOMemRegionTree.parse_iomem_tree(tree)
382     i = 0
383     for l in lines:
384         regions[l] = regs[i]
385         i += 1
386
387     # now prepare a non-sparse array for a return value,
388     # also filtering out MSI-X pages
389     ret = []
390     for r in regions:
391         if r:
392             for d in pcidevices:
393                 if d.msix_address >= r.start and d.msix_address <= r.stop:
394                     if d.msix_address > r.start:
395                         head_r = MemRegion(r.start, d.msix_address - 1,
396                                            r.typestr, r.comments)
397                         ret.append(head_r)
398                     if d.msix_address + d.msix_region_size < r.stop:
399                         tail_r = MemRegion(d.msix_address + d.msix_region_size,
400                                            r.stop, r.typestr, r.comments)
401                         ret.append(tail_r)
402                     r = None
403                     break
404             if r:
405                 ret.append(r)
406
407     # newer Linux kernels will report the first page as reserved
408     # it is needed for CPU init so include it anyways
409     if (ret[0].typestr == 'System RAM' and ret[0].start == 0x1000):
410         ret[0].start = 0
411
412     return ret
413
414
415 def parse_pcidevices():
416     devices = []
417     caps = []
418     basedir = '/sys/bus/pci/devices'
419     list = input_listdir(basedir, ['*/class', '*/config'])
420     for dir in list:
421         d = PCIDevice.parse_pcidevice_sysfsdir(basedir, dir)
422         if d is not None:
423             if len(d.caps) > 0:
424                 duplicate = False
425                 # look for duplicate capability patterns
426                 for d2 in devices:
427                     if d2.caps == d.caps:
428                         # reused existing capability list, but record all users
429                         d2.caps[0].comments.append(str(d))
430                         d.caps_start = d2.caps_start
431                         duplicate = True
432                         break
433                 if not duplicate:
434                     d.caps[0].comments.append(str(d))
435                     d.caps_start = len(caps)
436                     caps.extend(d.caps)
437             devices.append(d)
438     return (devices, caps)
439
440
441 def parse_cmdline():
442     line = input_readline('/proc/cmdline')
443     m = re.match(r'.*memmap=([0-9a-fA-FxX]+)([KMG]?)\$'
444                  '([0-9a-fA-FxX]+)([KMG]?).*',
445                  line)
446     if m is not None:
447         size = kmg_multiply(int(m.group(1), 0), m.group(2))
448         start = kmg_multiply(int(m.group(3), 0), m.group(4))
449         return [start, size]
450     return None
451
452
453 def alloc_mem(regions, size):
454     mem = [0x3b000000, size]
455     for r in regions:
456         if (
457             r.typestr == 'System RAM' and
458             r.start <= mem[0] and
459             r.stop + 1 >= mem[0] + mem[1]
460            ):
461             if r.start < mem[0]:
462                 head_r = MemRegion(r.start, mem[0] - 1, r.typestr, r.comments)
463                 regions.insert(regions.index(r), head_r)
464             if r.stop + 1 > mem[0] + mem[1]:
465                 tail_r = MemRegion(mem[0] + mem[1], r.stop, r.typestr,
466                                    r.comments)
467                 regions.insert(regions.index(r), tail_r)
468             regions.remove(r)
469             return mem
470     for r in reversed(regions):
471         if (r.typestr == 'System RAM' and r.size() >= mem[1]):
472             mem[0] = r.start
473             r.start += mem[1]
474             return mem
475     raise RuntimeError('failed to allocate memory')
476
477
478 def count_cpus():
479     list = input_listdir('/sys/devices/system/cpu', ['cpu*/uevent'])
480     count = 0
481     for f in list:
482         if re.match(r'cpu[0-9]+', f):
483             count += 1
484     return count
485
486
487 def parse_dmar_devscope(f):
488     (scope_type, scope_len, bus, dev, fn) = \
489         struct.unpack('<BBxxxBBB', f.read(8))
490     if scope_len != 8:
491         raise RuntimeError('Unsupported DMAR Device Scope Structure')
492     return (scope_type, scope_len, bus, dev, fn)
493
494
495 # parsing of DMAR ACPI Table
496 # see Intel VT-d Spec chapter 8
497 def parse_dmar(pcidevices):
498     f = input_open('/sys/firmware/acpi/tables/DMAR', 'rb', True)
499     if get_cpu_vendor() == 'AuthenticAMD':
500         print('WARNING: AMD IOMMU support is not implemented yet')
501         return [], 0, []
502     signature = f.read(4)
503     if signature != b'DMAR':
504         if options.generate_collector:
505             return [], 0, []
506         raise RuntimeError('DMAR: incorrect input file format %s' % signature)
507     (length,) = struct.unpack('<I', f.read(4))
508     f.seek(48)
509     length -= 48
510     units = []
511     regions = []
512     ioapic_id = 0
513
514     while length > 0:
515         offset = 0
516         (struct_type, struct_len) = struct.unpack('<HH', f.read(4))
517         offset += 4
518         length -= struct_len
519
520         # DMA Remapping Hardware Unit Definition
521         if struct_type == 0:
522             (flags, segment, base) = struct.unpack('<BxHQ', f.read(12))
523             if segment != 0:
524                 raise RuntimeError('We do not support multiple PCI segments')
525             if len(units) >= 8:
526                 raise RuntimeError('Too many DMAR units. '
527                                    'Raise JAILHOUSE_MAX_DMAR_UNITS.')
528             units.append(base)
529             if flags & 1:
530                 for d in pcidevices:
531                     if d.iommu == None:
532                         d.iommu = len(units) - 1
533             offset += 16 - offset
534             while offset < struct_len:
535                 (scope_type, scope_len, bus, dev, fn) =\
536                     parse_dmar_devscope(f)
537                 if scope_type == 1:
538                     for d in pcidevices:
539                         if d.bus == bus and d.dev == dev and d.fn == fn:
540                             d.iommu = len(units) - 1
541                 elif scope_type == 2:
542                     raise RuntimeError('Unsupported DMAR Device Scope type')
543                 elif scope_type == 3:
544                     if ioapic_id != 0:
545                         raise RuntimeError('We do not support more '
546                                            'than 1 IOAPIC')
547                     # encode the DMAR unit number into the device ID
548                     ioapic_id = ((len(units) - 1) << 16) | \
549                         (bus << 8) | (dev << 3) | fn
550                 offset += scope_len
551
552         # Reserved Memory Region Reporting Structure
553         if struct_type == 1:
554             f.seek(8 - offset, os.SEEK_CUR)
555             offset += 8 - offset
556             (base, limit) = struct.unpack('<QQ', f.read(16))
557             offset += 16
558
559             comments = []
560             while offset < struct_len:
561                 (scope_type, scope_len, bus, dev, fn) =\
562                     parse_dmar_devscope(f)
563                 if scope_type == 1:
564                     comments.append('PCI device: %02x:%02x.%x' %
565                                     (bus, dev, fn))
566                 else:
567                     comments.append('DMAR parser could not decode device path')
568                 offset += scope_len
569
570             reg = MemRegion(base, limit, 'ACPI DMAR RMRR', comments)
571             regions.append(reg)
572
573         f.seek(struct_len - offset, os.SEEK_CUR)
574
575     return units, ioapic_id, regions
576
577
578 def parse_ioports():
579     pm_timer_base = None
580     f = input_open('/proc/ioports')
581     for line in f:
582         if line.endswith('ACPI PM_TMR\n'):
583             pm_timer_base = int(line.split('-')[0], 16)
584             break
585     f.close()
586     return pm_timer_base
587
588
589 class MMConfig:
590     def __init__(self, base, end_bus):
591         self.base = base
592         self.end_bus = end_bus
593
594     @staticmethod
595     def parse():
596         f = input_open('/sys/firmware/acpi/tables/MCFG', 'rb')
597         signature = f.read(4)
598         if signature != b'MCFG':
599             if options.generate_collector:
600                 return MMConfig(0, 0)
601             raise RuntimeError('MCFG: incorrect input file format %s' %
602                                signature)
603         (length,) = struct.unpack('<I', f.read(4))
604         if length > 60:
605             raise RuntimeError('Multiple MMCONFIG regions found! '
606                                'This is not supported')
607         f.seek(44)
608         (base, segment, start_bus, end_bus) = \
609             struct.unpack('<QHBB', f.read(12))
610         if segment != 0 or start_bus != 0:
611             raise RuntimeError('Invalid MCFG structure found')
612         return MMConfig(base, end_bus)
613
614
615 def get_cpu_vendor():
616     with input_open('/proc/cpuinfo', 'r') as f:
617         for line in f:
618             if not line.strip():
619                 continue
620             key, value = line.split(':')
621             if key.strip() == 'vendor_id':
622                 return value.strip()
623
624
625 if (
626     (options.generate_collector is False) and (options.root is '/')
627     and (os.geteuid() is not 0)
628 ):
629     print('ERROR: You have to be root to work on "/"!', file=sys.stderr)
630     sys.exit(1)
631
632 jh_enabled = input_readline('/sys/devices/jailhouse/enabled',
633                             True).rstrip()
634 if options.generate_collector is False and jh_enabled == '1':
635     print('ERROR: Jailhouse was enabled when collecting input files! '
636           'Disable jailhouse and try again.',
637           file=sys.stderr)
638     sys.exit(1)
639
640 (pcidevices, pcicaps) = parse_pcidevices()
641
642 product = [input_readline('/sys/class/dmi/id/sys_vendor',
643                           True).rstrip(),
644            input_readline('/sys/class/dmi/id/product_name',
645                           True).rstrip()
646            ]
647
648 inmatemem = kmg_multiply_str(options.mem_inmates)
649 hvmem = [0, kmg_multiply_str(options.mem_hv)]
650
651 regions = parse_iomem(pcidevices)
652 ourmem = parse_cmdline()
653 total = hvmem[1] + inmatemem
654
655 mmconfig = MMConfig.parse()
656
657 (dmar_units, ioapic_id, rmrr_regs) = parse_dmar(pcidevices)
658 regions += rmrr_regs
659
660 for d in pcidevices:
661     if get_cpu_vendor() == 'AuthenticAMD':
662         d.iommu = 0  # temporary workaround
663     if d.iommu == None:
664         raise RuntimeError('PCI device %02x:%02x.%x outside the scope of an '
665                            'IOMMU' % (d.bus, d.dev, d.fn))
666
667 # kernel does not have memmap region, pick one
668 if ourmem is None:
669     ourmem = alloc_mem(regions, total)
670 elif (total > ourmem[1]):
671     raise RuntimeError('Your memmap reservation is too small you need >="' +
672                        hex(total) + '"')
673
674 hvmem[0] = ourmem[0]
675
676 inmatereg = MemRegion(ourmem[0] + hvmem[1],
677                       ourmem[0] + hvmem[1] + inmatemem - 1,
678                       'JAILHOUSE Inmate Memory')
679 regions.append(inmatereg)
680
681 cpucount = count_cpus()
682
683 pm_timer_base = parse_ioports()
684
685 f = open(options.file, 'w')
686
687 if options.generate_collector:
688     filelist = ' '.join(inputs['files'].union(inputs['dirs']))
689     filelist_opt = ' '.join(inputs['files_opt'])
690
691     tmpl = Template(filename=os.path.join(options.template_dir,
692                                           'jailhouse-config-collect.tmpl'))
693     f.write(tmpl.render(filelist=filelist, filelist_opt=filelist_opt))
694 else:
695     tmpl = Template(filename=os.path.join(options.template_dir,
696                                           'root-cell-config.c.tmpl'))
697     f.write(tmpl.render(regions=regions,
698                         ourmem=ourmem,
699                         argstr=' '.join(sys.argv),
700                         hvmem=hvmem,
701                         product=product,
702                         pcidevices=pcidevices,
703                         pcicaps=pcicaps,
704                         cpucount=cpucount,
705                         ioapic_id=ioapic_id,
706                         pm_timer_base=pm_timer_base,
707                         mmconfig=mmconfig,
708                         dmar_units=dmar_units))
709
710 f.close()