xref: /openbmc/qemu/scripts/qapi/commands.py (revision cc67f28e)
1"""
2QAPI command marshaller generator
3
4Copyright IBM, Corp. 2011
5Copyright (C) 2014-2018 Red Hat, Inc.
6
7Authors:
8 Anthony Liguori <aliguori@us.ibm.com>
9 Michael Roth <mdroth@linux.vnet.ibm.com>
10 Markus Armbruster <armbru@redhat.com>
11
12This work is licensed under the terms of the GNU GPL, version 2.
13See the COPYING file in the top-level directory.
14"""
15
16from typing import (
17    Dict,
18    List,
19    Optional,
20    Set,
21)
22
23from .common import c_name, mcgen
24from .gen import (
25    QAPIGenC,
26    QAPISchemaModularCVisitor,
27    build_params,
28    ifcontext,
29    gen_special_features,
30)
31from .schema import (
32    QAPISchema,
33    QAPISchemaFeature,
34    QAPISchemaIfCond,
35    QAPISchemaObjectType,
36    QAPISchemaType,
37)
38from .source import QAPISourceInfo
39
40
41def gen_command_decl(name: str,
42                     arg_type: Optional[QAPISchemaObjectType],
43                     boxed: bool,
44                     ret_type: Optional[QAPISchemaType]) -> str:
45    return mcgen('''
46%(c_type)s qmp_%(c_name)s(%(params)s);
47''',
48                 c_type=(ret_type and ret_type.c_type()) or 'void',
49                 c_name=c_name(name),
50                 params=build_params(arg_type, boxed, 'Error **errp'))
51
52
53def gen_call(name: str,
54             arg_type: Optional[QAPISchemaObjectType],
55             boxed: bool,
56             ret_type: Optional[QAPISchemaType]) -> str:
57    ret = ''
58
59    argstr = ''
60    if boxed:
61        assert arg_type
62        argstr = '&arg, '
63    elif arg_type:
64        assert not arg_type.variants
65        for memb in arg_type.members:
66            if memb.optional:
67                argstr += 'arg.has_%s, ' % c_name(memb.name)
68            argstr += 'arg.%s, ' % c_name(memb.name)
69
70    lhs = ''
71    if ret_type:
72        lhs = 'retval = '
73
74    ret = mcgen('''
75
76    %(lhs)sqmp_%(c_name)s(%(args)s&err);
77    error_propagate(errp, err);
78''',
79                c_name=c_name(name), args=argstr, lhs=lhs)
80    if ret_type:
81        ret += mcgen('''
82    if (err) {
83        goto out;
84    }
85
86    qmp_marshal_output_%(c_name)s(retval, ret, errp);
87''',
88                     c_name=ret_type.c_name())
89    return ret
90
91
92def gen_marshal_output(ret_type: QAPISchemaType) -> str:
93    return mcgen('''
94
95static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in,
96                                QObject **ret_out, Error **errp)
97{
98    Visitor *v;
99
100    v = qobject_output_visitor_new_qmp(ret_out);
101    if (visit_type_%(c_name)s(v, "unused", &ret_in, errp)) {
102        visit_complete(v, ret_out);
103    }
104    visit_free(v);
105    v = qapi_dealloc_visitor_new();
106    visit_type_%(c_name)s(v, "unused", &ret_in, NULL);
107    visit_free(v);
108}
109''',
110                 c_type=ret_type.c_type(), c_name=ret_type.c_name())
111
112
113def build_marshal_proto(name: str) -> str:
114    return ('void qmp_marshal_%s(QDict *args, QObject **ret, Error **errp)'
115            % c_name(name))
116
117
118def gen_marshal_decl(name: str) -> str:
119    return mcgen('''
120%(proto)s;
121''',
122                 proto=build_marshal_proto(name))
123
124
125def gen_marshal(name: str,
126                arg_type: Optional[QAPISchemaObjectType],
127                boxed: bool,
128                ret_type: Optional[QAPISchemaType]) -> str:
129    have_args = boxed or (arg_type and not arg_type.is_empty())
130    if have_args:
131        assert arg_type is not None
132        arg_type_c_name = arg_type.c_name()
133
134    ret = mcgen('''
135
136%(proto)s
137{
138    Error *err = NULL;
139    bool ok = false;
140    Visitor *v;
141''',
142                proto=build_marshal_proto(name))
143
144    if ret_type:
145        ret += mcgen('''
146    %(c_type)s retval;
147''',
148                     c_type=ret_type.c_type())
149
150    if have_args:
151        ret += mcgen('''
152    %(c_name)s arg = {0};
153''',
154                     c_name=arg_type_c_name)
155
156    ret += mcgen('''
157
158    v = qobject_input_visitor_new_qmp(QOBJECT(args));
159    if (!visit_start_struct(v, NULL, NULL, 0, errp)) {
160        goto out;
161    }
162''')
163
164    if have_args:
165        ret += mcgen('''
166    if (visit_type_%(c_arg_type)s_members(v, &arg, errp)) {
167        ok = visit_check_struct(v, errp);
168    }
169''',
170                     c_arg_type=arg_type_c_name)
171    else:
172        ret += mcgen('''
173    ok = visit_check_struct(v, errp);
174''')
175
176    ret += mcgen('''
177    visit_end_struct(v, NULL);
178    if (!ok) {
179        goto out;
180    }
181''')
182
183    ret += gen_call(name, arg_type, boxed, ret_type)
184
185    ret += mcgen('''
186
187out:
188    visit_free(v);
189''')
190
191    ret += mcgen('''
192    v = qapi_dealloc_visitor_new();
193    visit_start_struct(v, NULL, NULL, 0, NULL);
194''')
195
196    if have_args:
197        ret += mcgen('''
198    visit_type_%(c_arg_type)s_members(v, &arg, NULL);
199''',
200                     c_arg_type=arg_type_c_name)
201
202    ret += mcgen('''
203    visit_end_struct(v, NULL);
204    visit_free(v);
205''')
206
207    ret += mcgen('''
208}
209''')
210    return ret
211
212
213def gen_register_command(name: str,
214                         features: List[QAPISchemaFeature],
215                         success_response: bool,
216                         allow_oob: bool,
217                         allow_preconfig: bool,
218                         coroutine: bool) -> str:
219    options = []
220
221    if not success_response:
222        options += ['QCO_NO_SUCCESS_RESP']
223    if allow_oob:
224        options += ['QCO_ALLOW_OOB']
225    if allow_preconfig:
226        options += ['QCO_ALLOW_PRECONFIG']
227    if coroutine:
228        options += ['QCO_COROUTINE']
229
230    ret = mcgen('''
231    qmp_register_command(cmds, "%(name)s",
232                         qmp_marshal_%(c_name)s, %(opts)s, %(feats)s);
233''',
234                name=name, c_name=c_name(name),
235                opts=' | '.join(options) or 0,
236                feats=gen_special_features(features))
237    return ret
238
239
240class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
241    def __init__(self, prefix: str):
242        super().__init__(
243            prefix, 'qapi-commands',
244            ' * Schema-defined QAPI/QMP commands', None, __doc__)
245        self._visited_ret_types: Dict[QAPIGenC, Set[QAPISchemaType]] = {}
246
247    def _begin_user_module(self, name: str) -> None:
248        self._visited_ret_types[self._genc] = set()
249        commands = self._module_basename('qapi-commands', name)
250        types = self._module_basename('qapi-types', name)
251        visit = self._module_basename('qapi-visit', name)
252        self._genc.add(mcgen('''
253#include "qemu/osdep.h"
254#include "qapi/compat-policy.h"
255#include "qapi/visitor.h"
256#include "qapi/qmp/qdict.h"
257#include "qapi/dealloc-visitor.h"
258#include "qapi/error.h"
259#include "%(visit)s.h"
260#include "%(commands)s.h"
261
262''',
263                             commands=commands, visit=visit))
264        self._genh.add(mcgen('''
265#include "%(types)s.h"
266
267''',
268                             types=types))
269
270    def visit_begin(self, schema: QAPISchema) -> None:
271        self._add_module('./init', ' * QAPI Commands initialization')
272        self._genh.add(mcgen('''
273#include "qapi/qmp/dispatch.h"
274
275void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
276''',
277                             c_prefix=c_name(self._prefix, protect=False)))
278        self._genc.add(mcgen('''
279#include "qemu/osdep.h"
280#include "%(prefix)sqapi-commands.h"
281#include "%(prefix)sqapi-init-commands.h"
282
283void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds)
284{
285    QTAILQ_INIT(cmds);
286
287''',
288                             prefix=self._prefix,
289                             c_prefix=c_name(self._prefix, protect=False)))
290
291    def visit_end(self) -> None:
292        with self._temp_module('./init'):
293            self._genc.add(mcgen('''
294}
295'''))
296
297    def visit_command(self,
298                      name: str,
299                      info: Optional[QAPISourceInfo],
300                      ifcond: QAPISchemaIfCond,
301                      features: List[QAPISchemaFeature],
302                      arg_type: Optional[QAPISchemaObjectType],
303                      ret_type: Optional[QAPISchemaType],
304                      gen: bool,
305                      success_response: bool,
306                      boxed: bool,
307                      allow_oob: bool,
308                      allow_preconfig: bool,
309                      coroutine: bool) -> None:
310        if not gen:
311            return
312        # FIXME: If T is a user-defined type, the user is responsible
313        # for making this work, i.e. to make T's condition the
314        # conjunction of the T-returning commands' conditions.  If T
315        # is a built-in type, this isn't possible: the
316        # qmp_marshal_output_T() will be generated unconditionally.
317        if ret_type and ret_type not in self._visited_ret_types[self._genc]:
318            self._visited_ret_types[self._genc].add(ret_type)
319            with ifcontext(ret_type.ifcond,
320                           self._genh, self._genc):
321                self._genc.add(gen_marshal_output(ret_type))
322        with ifcontext(ifcond, self._genh, self._genc):
323            self._genh.add(gen_command_decl(name, arg_type, boxed, ret_type))
324            self._genh.add(gen_marshal_decl(name))
325            self._genc.add(gen_marshal(name, arg_type, boxed, ret_type))
326        with self._temp_module('./init'):
327            with ifcontext(ifcond, self._genh, self._genc):
328                self._genc.add(gen_register_command(
329                    name, features, success_response, allow_oob,
330                    allow_preconfig, coroutine))
331
332
333def gen_commands(schema: QAPISchema,
334                 output_dir: str,
335                 prefix: str) -> None:
336    vis = QAPISchemaGenCommandVisitor(prefix)
337    schema.visit(vis)
338    vis.write(output_dir)
339