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