2
0

const-gen.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import os
  4. import re
  5. import subprocess
  6. import sys
  7. from collections import defaultdict
  8. from collections import namedtuple
  9. Name = namedtuple('Name', 'name desc')
  10. Device = namedtuple('Device', 'name bus alias desc')
  11. Architecture = namedtuple('Architecture', 'name items default')
  12. TARGETS = [
  13. Name("alpha", "Alpha"),
  14. Name("arm", "ARM (aarch32)"),
  15. Name("aarch64", "ARM64 (aarch64)"),
  16. Name("avr", "AVR"),
  17. Name("hppa", "HPPA"),
  18. Name("i386", "i386 (x86)"),
  19. Name("loongarch64", "LoongArch64"),
  20. Name("m68k", "m68k"),
  21. Name("microblaze", "Microblaze"),
  22. Name("microblazeel", "Microblaze (Little Endian)"),
  23. Name("mips", "MIPS"),
  24. Name("mipsel", "MIPS (Little Endian)"),
  25. Name("mips64", "MIPS64"),
  26. Name("mips64el", "MIPS64 (Little Endian)"),
  27. Name("or1k", "OpenRISC"),
  28. Name("ppc", "PowerPC"),
  29. Name("ppc64", "PowerPC64"),
  30. Name("riscv32", "RISC-V32"),
  31. Name("riscv64", "RISC-V64"),
  32. Name("rx", "RX"),
  33. Name("s390x", "S390x (zSeries)"),
  34. Name("sh4", "SH4"),
  35. Name("sh4eb", "SH4 (Big Endian)"),
  36. Name("sparc", "SPARC"),
  37. Name("sparc64", "SPARC64"),
  38. Name("tricore", "TriCore"),
  39. Name("x86_64", "x86_64"),
  40. Name("xtensa", "Xtensa"),
  41. Name("xtensaeb", "Xtensa (Big Endian)")
  42. ]
  43. DEFAULTS = {
  44. "aarch64": "virt",
  45. "arm": "virt",
  46. "avr": "mega",
  47. "i386": "q35",
  48. "riscv64": "virt",
  49. "rx": "gdbsim-r5f562n7",
  50. "tricore": "tricore_testboard",
  51. "x86_64": "q35"
  52. }
  53. AUDIO_SCREAMER = Device('screamer', 'macio', '', 'Screamer (Mac99 only)')
  54. AUDIO_PCSPK = Device('pcspk', 'none', '', 'PC Speaker')
  55. AUDIO_ASC = Device('asc', 'none', '', 'Apple Sound Chip (Q800 only)')
  56. DISPLAY_TCX = Device('tcx', 'none', '', 'Sun TCX')
  57. DISPLAY_CG3 = Device('cg3', 'none', '', 'Sun cgthree')
  58. NETWORK_LANCE = Device('lance', 'none', '', 'Lance (Am7990)')
  59. NETWORK_DP8393X = Device('dp8393x', 'none', '', 'SONIC DP8393x (Q800 only)')
  60. ADD_DEVICES = {
  61. "ppc": {
  62. "Sound devices": set([
  63. AUDIO_SCREAMER
  64. ])
  65. },
  66. "ppc64": {
  67. "Sound devices": set([
  68. AUDIO_SCREAMER
  69. ])
  70. },
  71. "sparc": {
  72. "Display devices": set([
  73. DISPLAY_TCX,
  74. DISPLAY_CG3
  75. ]),
  76. "Network devices": set([
  77. NETWORK_LANCE
  78. ])
  79. },
  80. "i386": {
  81. "Sound devices": set([
  82. AUDIO_PCSPK
  83. ])
  84. },
  85. "x86_64": {
  86. "Sound devices": set([
  87. AUDIO_PCSPK
  88. ])
  89. },
  90. "m68k": {
  91. "Sound devices": set([
  92. AUDIO_ASC
  93. ]),
  94. "Network devices": set([
  95. NETWORK_DP8393X
  96. ])
  97. }
  98. }
  99. HEADER = '''//
  100. // Copyright © 2022 osy. All rights reserved.
  101. //
  102. // Licensed under the Apache License, Version 2.0 (the "License");
  103. // you may not use this file except in compliance with the License.
  104. // You may obtain a copy of the License at
  105. //
  106. // http://www.apache.org/licenses/LICENSE-2.0
  107. //
  108. // Unless required by applicable law or agreed to in writing, software
  109. // distributed under the License is distributed on an "AS IS" BASIS,
  110. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  111. // See the License for the specific language governing permissions and
  112. // limitations under the License.
  113. //
  114. // !! THIS FILE IS GENERATED FROM const-gen.py, DO NOT MODIFY MANUALLY !!
  115. import Foundation
  116. '''
  117. def parseListing(listing):
  118. output = listing.splitlines()[1:]
  119. result = set()
  120. names = set()
  121. for line in output:
  122. idx = line.find(' ')
  123. if idx < 0:
  124. break
  125. name = line[0:idx]
  126. description = line[idx:].strip()
  127. if name in names:
  128. continue # duplicate
  129. names.add(name)
  130. result.add(Name(name, '{} ({})'.format(description, name)))
  131. return result
  132. def parseDeviceListing(defaults, listing):
  133. output = listing.splitlines()
  134. group = ''
  135. result = defaultdict(set, defaults)
  136. for line in output:
  137. if not line:
  138. continue
  139. if not line.startswith('name '):
  140. group = line.rstrip(':')
  141. continue
  142. search = re.search('^name "(?P<name>[^"]*)"(?:, bus (?P<bus>[^\\s]+))?(?:, alias "(?P<alias>[^"]+)")?(?:, desc "(?P<desc>[^"]+)")?$', line)
  143. name = search.group('name')
  144. desc = search.group('desc')
  145. if not desc:
  146. desc = name
  147. else:
  148. desc = '{} ({})'.format(desc, name)
  149. item = Device(name, search.group('bus'), search.group('alias'), desc)
  150. result[group].add(item)
  151. return result
  152. def parseCpu(target, listing):
  153. def parseSingle(line):
  154. name = line.strip()
  155. return Name(name, name)
  156. def parseSparc(line):
  157. search = re.search('^\\s+(?P<name>.+)\\s+\\(IU\\s+(?P<iu>\\S+)\\s+FPU\\s+(?P<fpu>\\S+)\\s+MMU\\s+(?P<mmu>\\S+)\\s+NWINS\\s+(?P<nwins>\\d+)\\).*$', line)
  158. return Name(search.group('name'), search.group('name'))
  159. def parseStandard(line):
  160. search = re.search('^\\s+(?P<name>\\S+)\\s+(?P<desc>.*)?$', line)
  161. name = search.group('name')
  162. desc = search.group('desc').strip()
  163. desc = ' '.join(desc.split())
  164. if not desc or desc.startswith('(alias'):
  165. desc = name
  166. else:
  167. desc = '{} ({})'.format(desc, name)
  168. return Name(name, desc)
  169. def parseSparcFlags(line):
  170. if line.startswith('Default CPU feature flags') or line.startswith('Available CPU feature flags'):
  171. flags = line.split(':')[1].strip()
  172. return [Name(flag, flag) for flag in flags.split(' ')]
  173. elif line.startswith('Numerical features'):
  174. return []
  175. else:
  176. return None
  177. def parseS390Flags(line):
  178. if line.endswith(':'):
  179. return []
  180. else:
  181. flag = line.split(' ')[0]
  182. return [Name(flag, flag)]
  183. def parseX86Flags(line):
  184. flags = []
  185. for flag in line.split(' '):
  186. if flag:
  187. flags.append(Name(flag, flag))
  188. return flags
  189. output = enumerate(listing.splitlines())
  190. cpus = [Name('default', 'Default')]
  191. flags = []
  192. if next(output, None) == None:
  193. return (cpus, flags)
  194. for (index, line) in output:
  195. if not line:
  196. break
  197. if len(line.strip().split(' ')) == 1:
  198. cpu = parseSingle(line)
  199. elif parseSparcFlags(line) != None:
  200. flags += parseSparcFlags(line)
  201. continue
  202. elif target.name.startswith('sparc'):
  203. cpu = parseSparc(line)
  204. else:
  205. cpu = parseStandard(line)
  206. if cpu.name != 'default':
  207. cpus.append(cpu)
  208. header = next(output, None)
  209. if header == None:
  210. return (cpus, flags)
  211. for (index, line) in output:
  212. if header[1] == 'Recognized CPUID flags:':
  213. flags += parseX86Flags(line)
  214. elif header[1] == 'Recognized feature flags:':
  215. flags += parseS390Flags(line)
  216. flags = set(flags) # de-duplicate
  217. return (cpus, flags)
  218. def sortItems(items):
  219. return sorted(items, key=lambda item: item.desc if item.desc else item.name)
  220. def getMachines(target, qemu_path):
  221. output = subprocess.check_output([qemu_path, '-machine', 'help']).decode('utf-8')
  222. return parseListing(output)
  223. def getDefaultMachine(target, machines):
  224. if target in DEFAULTS:
  225. return DEFAULTS[target]
  226. for machine in machines:
  227. if "default" in machine.desc:
  228. return machine.name
  229. return machines[0].name
  230. def getDevices(target, qemu_path):
  231. output = subprocess.check_output([qemu_path, '-device', 'help']).decode('utf-8')
  232. devices = parseDeviceListing(ADD_DEVICES[target.name] if target.name in ADD_DEVICES else {}, output)
  233. return devices
  234. def getCpus(target, qemu_path):
  235. output = subprocess.check_output([qemu_path, '-cpu', 'help']).decode('utf-8')
  236. return parseCpu(target, output)
  237. def sanitizeName(name):
  238. sanitized = re.sub('[^0-9a-zA-Z]+', '_', name)
  239. if len(sanitized) == 0:
  240. sanitized = '_empty'
  241. if sanitized[0].isdigit():
  242. sanitized = '_' + sanitized
  243. if sanitized in ['default']:
  244. sanitized = '`' + sanitized + '`'
  245. return sanitized
  246. def generateEmptyEnum(name):
  247. output = f'typealias {name} = AnyQEMUConstant\n'
  248. output += f'\n'
  249. return output
  250. def generateEnum(name, values, prettyValues, baseName='QEMUConstant', defaultValue=None):
  251. if len(values) == 0:
  252. return generateEmptyEnum(name)
  253. output = f'enum {name}: String, CaseIterable, {baseName} {{\n'
  254. for value in values:
  255. sanitized = sanitizeName(value)
  256. if sanitized == value:
  257. output += f' case {value}\n'
  258. else:
  259. output += f' case {sanitized} = "{value}"\n'
  260. output += '\n'
  261. if defaultValue:
  262. sanitized = sanitizeName(defaultValue)
  263. output += f' static var `default`: {name} {{\n'
  264. output += f' .{sanitized}\n'
  265. output += f' }}\n'
  266. output += f'\n'
  267. output += f' var prettyValue: String {{\n'
  268. output += f' switch self {{\n'
  269. for value, valuePretty in zip(values, prettyValues):
  270. sanitized = sanitizeName(value)
  271. if value in ['default']:
  272. output += f' case .{sanitized}: return NSLocalizedString("{valuePretty}", comment: "QEMUConstantGenerated")\n'
  273. else:
  274. output += f' case .{sanitized}: return "{valuePretty}"\n'
  275. output += f' }}\n'
  276. output += f' }}\n'
  277. output += f'}}\n'
  278. output += f'\n'
  279. return output
  280. def generateArchitectureAtlas(architectures, types):
  281. output = f'extension QEMUArchitecture {{\n'
  282. for k, v in types.items():
  283. output += f' var {v}: any {k}.Type {{\n'
  284. output += f' switch self {{\n'
  285. for a in architectures:
  286. a = sanitizeName(a)
  287. output += f' case .{a}: return {k}_{a}.self\n'
  288. output += f' }}\n'
  289. output += f' }}\n'
  290. output += f'\n'
  291. output += f'}}\n'
  292. output += f'\n'
  293. return output
  294. def generateEnumForeachArchitecture(name, targetItems, defaults={}):
  295. output = ''
  296. for target in targetItems:
  297. arch = target.name
  298. className = name + '_' + arch
  299. sortedItems = sortItems(target.items)
  300. values = [item.name for item in sortedItems]
  301. prettyValues = [item.desc for item in sortedItems]
  302. default = defaults[arch] if arch in defaults else None
  303. output += generateEnum(className, values, prettyValues, name, default)
  304. return output
  305. def generate(targets, cpus, cpuFlags, machines, displayDevices, networkDevices, soundDevices, serialDevices):
  306. targetKeys = [item.name for item in targets]
  307. output = HEADER
  308. output += generateEnum('QEMUArchitecture', targetKeys, [item.desc for item in targets])
  309. output += generateEnumForeachArchitecture('QEMUCPU', cpus)
  310. output += generateEnumForeachArchitecture('QEMUCPUFlag', cpuFlags)
  311. output += generateEnumForeachArchitecture('QEMUTarget', machines, {machine.name: machine.default for machine in machines})
  312. output += generateEnumForeachArchitecture('QEMUDisplayDevice', displayDevices)
  313. output += generateEnumForeachArchitecture('QEMUNetworkDevice', networkDevices)
  314. output += generateEnumForeachArchitecture('QEMUSoundDevice', soundDevices)
  315. output += generateEnumForeachArchitecture('QEMUSerialDevice', serialDevices)
  316. output += generateArchitectureAtlas(targetKeys, {
  317. 'QEMUCPU': 'cpuType',
  318. 'QEMUCPUFlag': 'cpuFlagType',
  319. 'QEMUTarget': 'targetType',
  320. 'QEMUDisplayDevice': 'displayDeviceType',
  321. 'QEMUNetworkDevice': 'networkDeviceType',
  322. 'QEMUSoundDevice': 'soundDeviceType',
  323. 'QEMUSerialDevice': 'serialDeviceType',
  324. })
  325. return output
  326. def transformDisplayCards(displayCards):
  327. def transform(item):
  328. if item.name.endswith('-gl') or '-gl-' in item.name:
  329. item = Device(item.name, item.bus, item.alias, item.desc + ' (GPU Supported)')
  330. return item
  331. return set(map(transform, displayCards))
  332. def main(argv):
  333. base = argv[1]
  334. allMachines = []
  335. allCpus = []
  336. allCpuFlags = []
  337. allDisplayCards = []
  338. allSoundCards = []
  339. allNetworkCards = []
  340. allSerialCards = []
  341. # parse outputs
  342. for target in TARGETS:
  343. path = '{}/qemu-system-{}-unsigned'.format(base, target.name)
  344. if not os.path.exists(path):
  345. raise "Invalid path."
  346. machines = sortItems(getMachines(target, path))
  347. default = getDefaultMachine(target.name, machines)
  348. allMachines.append(Architecture(target.name, machines, default))
  349. devices = getDevices(target, path)
  350. displayCards = transformDisplayCards(devices["Display devices"])
  351. allDisplayCards.append(Architecture(target.name, displayCards, None))
  352. allNetworkCards.append(Architecture(target.name, devices["Network devices"], None))
  353. nonHdaDevices = [device for device in devices["Sound devices"] if device.bus != 'HDA']
  354. allSoundCards.append(Architecture(target.name, nonHdaDevices, None))
  355. serialDevices = [device for device in devices["Input devices"] if 'serial' in device.name]
  356. allSerialCards.append(Architecture(target.name, serialDevices, None))
  357. cpus, flags = getCpus(target, path)
  358. allCpus.append(Architecture(target.name, cpus, 0))
  359. allCpuFlags.append(Architecture(target.name, flags, 0))
  360. # generate constants
  361. print(generate(TARGETS, allCpus, allCpuFlags, allMachines, allDisplayCards, allNetworkCards, allSoundCards, allSerialCards))
  362. if __name__ == "__main__":
  363. main(sys.argv)