1#!/usr/bin/python
2#
3# Copyright (C) 2020 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import json
19import os
20import sys
21
22# If this doesn't help use PYTHONPATH=${PYTHONPATH}:<berberis>/android_api
23sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), '..', '..', 'android_api'))
24
25import api_analysis
26
27
28def _merge_syscalls(all_syscalls, custom_syscalls):
29  arch_names = {'arm', 'arm64', 'x86', 'x86_64', 'riscv64'}
30
31  for name, custom_syscall in custom_syscalls.items():
32    # Top-level fields apply to all architecures
33    common_update = {k: v for k, v in custom_syscall.items() if k not in arch_names}
34
35    for arch in arch_names:
36      arch_update = custom_syscall.get(arch, {})
37
38      # Syscall for arch does not exist.
39      # Create it only if update for arch is not empty.
40      # Do not create syscall for arch from common update only!
41      if not arch_update:
42        if name not in all_syscalls or arch not in all_syscalls[name]:
43          continue
44
45      arch_syscall = all_syscalls.setdefault(name, {}).setdefault(arch, {})
46      arch_syscall.update(common_update)
47      arch_syscall.update(arch_update)
48
49
50def _get_syscall_params(arch_syscall, arch_api):
51  if 'params' in arch_syscall:
52    return arch_syscall['params']
53
54  if 'entry' not in arch_syscall:
55    return None
56
57  entry_symbol_name = '__do_' + arch_syscall['entry']
58  if entry_symbol_name not in arch_api['symbols']:
59    return None
60  entry_symbol = arch_api['symbols'][entry_symbol_name]
61
62  entry_type = arch_api['types'][entry_symbol['type']]
63  assert entry_type['kind'] == 'function'
64  return entry_type['params']
65
66
67def _is_syscall_api_compatible(src_syscall, src_api):
68  for param_type_name in _get_syscall_params(src_syscall, src_api):
69    assert param_type_name in src_api['types']
70    if not src_api['types'][param_type_name]['is_compatible']:
71      return False
72
73  return True
74
75
76def _is_64bit_arch(arch):
77  return arch == 'arm64' or arch == 'x86_64' or arch == 'riscv64'
78
79def _match_syscall_args(src_arch, dst_arch, src_api, params):
80  if _is_64bit_arch(src_arch) and _is_64bit_arch(dst_arch):
81    args = ['arg_%d' % (i + 1) for i in range(len(params))]
82    return args, args
83
84  assert src_arch == 'arm' and dst_arch == 'x86'
85
86  src_args = []
87  dst_args = []
88
89  for param_type_name in params:
90    param_type = src_api['types'][param_type_name]
91    param_size = int(param_type['size'])
92
93    i = len(src_args)
94    if param_size <= 32:
95      src_args.append('arg_%d' % (i + 1))
96      dst_args.append('arg_%d' % (i + 1))
97    else:
98      assert param_size == 64
99      if i % 2 != 0:
100        src_args.append('arg_%d' % (i + 1))
101        i += 1
102      src_args.append('arg_%d' % (i + 1))
103      src_args.append('arg_%d' % (i + 2))
104      dst_args.append('arg_%d' % (i + 1))
105      dst_args.append('arg_%d' % (i + 2))
106
107  return src_args, dst_args
108
109
110def _print_syscalls_translation(src_arch, dst_arch, all_syscalls, guest_api):
111  print("""\
112// This file automatically generated by gen_kernel_syscalls_translation.py
113// DO NOT EDIT!
114
115long RunGuestSyscallImpl(long guest_nr,
116                         long arg_1,
117                         long arg_2,
118                         long arg_3,
119                         long arg_4,
120                         long arg_5,
121                         long arg_6) {
122  switch (guest_nr) {""")
123
124  # Sort syscalls by name
125  for name, syscall in sorted(all_syscalls.items()):
126    # Filter syscalls by src_arch
127    if src_arch not in syscall:
128      continue
129    src_syscall = syscall[src_arch]
130
131    print('    case %s:  // %s' % (src_syscall['id'], name))
132
133    params = _get_syscall_params(src_syscall, guest_api)
134    if params is None:
135      print('      // missing prototype')
136      print('      TRACE("unsupported syscall %s");' % (name))
137      print('      errno = ENOSYS;')
138      print('      return -1;')
139      continue
140
141    # Guest args might differ from host args.
142    # For example, arm aligns register pairs for 64-bit args and x86 doesn't.
143    # For arm to x86, matching should remove padding - skip certain args.
144    # For x86 to arm, matching should insert padding - insert zero args.
145    # Host system call obiously receives _host_ args.
146    # For RunGuestSyscall_* we need a convention. Assume we compile same guest for multiple hosts,
147    # then guest args are stable while host args might vary. For better source compatibility, let
148    # RunGuestSyscall_* always receive _guest_ args.
149    src_args, dst_args = _match_syscall_args(src_arch, dst_arch, guest_api, params)
150
151    # Translate to syscall with the same name.
152    # This is a simplification, but it works for architectures of the same bitness.
153    if dst_arch not in syscall:
154      print('      // missing on %s' % (dst_arch))
155      print('      return RunGuestSyscall_%s(' % (name) + ', '.join(src_args) + ');')
156      continue
157
158    dst_syscall = syscall[dst_arch]
159
160    if 'custom_reason' in src_syscall:
161      print('      // %s' % (src_syscall['custom_reason']))
162      print('      return RunGuestSyscall_%s(' % (name) + ', '.join(src_args) + ');')
163      continue
164
165    if 'custom_reason' in dst_syscall:
166      print('      // %s' % (dst_syscall['custom_reason']))
167      print('      return RunGuestSyscall_%s(' % (name) + ', '.join(src_args) + ');')
168      continue
169
170    # If no custom reason given, syscall must have ids and entries.
171    assert 'id' in src_syscall
172    assert 'id' in dst_syscall
173    assert 'entry' in src_syscall
174    assert 'entry' in dst_syscall
175
176    if not _is_syscall_api_compatible(src_syscall, guest_api):
177      print('      // incompatible prototype')
178      print('      return RunGuestSyscall_%s(' % (name) + ', '.join(src_args) + ');')
179      continue
180
181    # Translate to syscall with the same entry.
182    if src_syscall['entry'] != dst_syscall['entry']:
183      print('      // %s on %s but %s on %s' % (src_syscall['entry'], src_arch, dst_syscall['entry'], dst_arch))
184      print('      return RunGuestSyscall_%s(' % (name) + ', '.join(src_args) + ');')
185      continue
186
187    # Translate!
188    print('      return syscall(' + ', '.join([dst_syscall['id']] + dst_args) + ');')
189
190  print("""\
191    default:
192      return RunUnknownGuestSyscall(guest_nr, arg_1, arg_2, arg_3, arg_4, arg_5, arg_6);
193  }
194}""")
195
196
197def main(argv):
198  if (len(argv) != 3):
199    print("Usage: " + argv[0] +" <src_arch> <dst_arch>")
200    return 1
201
202  workdir = os.path.dirname(argv[0])
203
204  src_arch = argv[1]
205  dst_arch = argv[2]
206
207  with open(os.path.join(workdir, 'kernel_api_%s.json' % (src_arch))) as json_file:
208    guest_api = json.load(json_file)
209
210  with open(os.path.join(workdir, 'kernel_api_%s.json' % (dst_arch))) as json_file:
211    host_api = json.load(json_file)
212
213  api_analysis.mark_incompatible_api(guest_api, host_api, False)
214
215  with open(os.path.join(workdir, 'kernel_syscalls.json')) as json_file:
216    all_syscalls = json.load(json_file)
217
218  with open(os.path.join(workdir, 'custom_syscalls.json')) as json_file:
219    custom_syscalls = json.load(json_file)
220
221  _merge_syscalls(all_syscalls, custom_syscalls)
222
223  _print_syscalls_translation(src_arch, dst_arch, all_syscalls, guest_api)
224
225  return 0
226
227
228if __name__ == '__main__':
229  sys.exit(main(sys.argv))
230