diff mbox

[08/10] qmp: add rocker device support

Message ID 1419916451-49258-9-git-send-email-sfeldma@gmail.com
State New
Headers show

Commit Message

Scott Feldman Dec. 30, 2014, 5:14 a.m. UTC
From: Scott Feldman <sfeldma@gmail.com>

Add QMP/HMP support for rocker devices.  This is mostly for debugging purposes
to see inside the device's tables and port configurations.  Some examples:

(qemu) rocker sw1
name: sw1
id: 0x0000013512005452
ports: 4

(qemu) rocker-ports sw1
            ena/    speed/ auto
      port  link    duplex neg?
     sw1.1  up     10G  FD  No
     sw1.2  up     10G  FD  No
     sw1.3  !ena   10G  FD  No
     sw1.4  !ena   10G  FD  No

(qemu) rocker-of-dpa-flows sw1
prio tbl hits key(mask) --> actions
2    60       lport 1 vlan 1 LLDP src 00:02:00:00:02:00 dst 01:80:c2:00:00:0e
2    60       lport 1 vlan 1 ARP src 00:02:00:00:02:00 dst 00:02:00:00:03:00
2    60       lport 2 vlan 2 IPv6 src 00:02:00:00:03:00 dst 33:33:ff:00:00:02 proto 58
3    50       vlan 2 dst 33:33:ff:00:00:02 --> write group 0x32000001 goto tbl 60
2    60       lport 2 vlan 2 IPv6 src 00:02:00:00:03:00 dst 33:33:ff:00:03:00 proto 58
3    50  1    vlan 2 dst 33:33:ff:00:03:00 --> write group 0x32000001 goto tbl 60
2    60       lport 2 vlan 2 ARP src 00:02:00:00:03:00 dst 00:02:00:00:02:00
3    50  2    vlan 2 dst 00:02:00:00:02:00 --> write group 0x02000001 goto tbl 60
2    60  1    lport 2 vlan 2 IP src 00:02:00:00:03:00 dst 00:02:00:00:02:00 proto 1
3    50  2    vlan 1 dst 00:02:00:00:03:00 --> write group 0x01000002 goto tbl 60
2    60  1    lport 1 vlan 1 IP src 00:02:00:00:02:00 dst 00:02:00:00:03:00 proto 1
2    60       lport 1 vlan 1 IPv6 src 00:02:00:00:02:00 dst 33:33:ff:00:00:01 proto 58
3    50       vlan 1 dst 33:33:ff:00:00:01 --> write group 0x31000000 goto tbl 60
2    60       lport 1 vlan 1 IPv6 src 00:02:00:00:02:00 dst 33:33:ff:00:02:00 proto 58
3    50  1    vlan 1 dst 33:33:ff:00:02:00 --> write group 0x31000000 goto tbl 60
1    60  173  lport 2 vlan 2 LLDP src <any> dst 01:80:c2:00:00:0e --> write group 0x02000000
1    60  6    lport 2 vlan 2 IPv6 src <any> dst <any> --> write group 0x02000000
1    60  174  lport 1 vlan 1 LLDP src <any> dst 01:80:c2:00:00:0e --> write group 0x01000000
1    60  174  lport 2 vlan 2 IP src <any> dst <any> --> write group 0x02000000
1    60  6    lport 1 vlan 1 IPv6 src <any> dst <any> --> write group 0x01000000
1    60  181  lport 2 vlan 2 ARP src <any> dst <any> --> write group 0x02000000
1    10  715  lport 2 --> apply new vlan 2 goto tbl 20
1    60  177  lport 1 vlan 1 ARP src <any> dst <any> --> write group 0x01000000
1    60  174  lport 1 vlan 1 IP src <any> dst <any> --> write group 0x01000000
1    10  717  lport 1 --> apply new vlan 1 goto tbl 20
1    0   1432 lport 0(0xffff) --> goto tbl 10

(qemu) rocker-of-dpa-groups sw1
id (decode) --> buckets
0x32000001 (type L2 multicast vlan 2 index 1) --> groups [0x02000001,0x02000000]
0x02000001 (type L2 interface vlan 2 lport 1) --> pop vlan out lport 1
0x01000002 (type L2 interface vlan 1 lport 2) --> pop vlan out lport 2
0x02000000 (type L2 interface vlan 2 lport 0) --> pop vlan out lport 0
0x01000000 (type L2 interface vlan 1 lport 0) --> pop vlan out lport 0
0x31000000 (type L2 multicast vlan 1 index 0) --> groups [0x01000002,0x01000000]

Signed-off-by: Scott Feldman <sfeldma@gmail.com>
Signed-off-by: Jiri Pirko <jiri@resnulli.us>
---
 hmp-commands.hx               |   56 ++++++++
 hmp.c                         |  303 ++++++++++++++++++++++++++++++++++++++++
 hmp.h                         |    4 +
 hw/net/rocker/rocker.c        |   46 ++++++
 hw/net/rocker/rocker_fp.c     |    1 +
 hw/net/rocker/rocker_of_dpa.c |  309 +++++++++++++++++++++++++++++++++++++++++
 qapi-schema.json              |   51 +++++++
 qmp-commands.hx               |   24 ++++
 8 files changed, 794 insertions(+)

Comments

Eric Blake Jan. 2, 2015, 11:56 p.m. UTC | #1
On 12/29/2014 10:14 PM, sfeldma@gmail.com wrote:
> From: Scott Feldman <sfeldma@gmail.com>

[your message came through as a top-level thread instead of in-reply-to
the 0/10 cover letter; please see if you can fix that before the next
submission]

> 
> Add QMP/HMP support for rocker devices.  This is mostly for debugging purposes
> to see inside the device's tables and port configurations.  Some examples:
> 

In this mail, I'll review just the QMP interface portion:

> +++ b/qapi-schema.json
> @@ -3515,3 +3515,54 @@
>  # Since: 2.1
>  ##
>  { 'command': 'rtc-reset-reinjection' }
> +
> +{ 'type': 'Rocker',
> +  'data': { 'name': 'str', 'id': 'uint64', 'ports': 'uint32' } }

Please add documentation for each new type and each member of that type,
including mention of which qemu release will first contain it.

> +
> +{ 'command': 'rocker',
> +  'data': { 'name': 'str' },
> +  'returns': 'Rocker' }
> +
> +{ 'type': 'RockerPort',
> +  'data': { 'name': 'str', 'enabled': 'bool', 'link_up': 'bool',
> +            'speed': 'uint32', 'duplex': 'uint8', 'autoneg': 'uint8' } }

Why is 'duplex' a 'uint8'?  Shouldn't it instead be an enum type?
Similar for 'autoneg'.

> +
> +{ 'command': 'rocker-ports',
> +  'data': { 'name': 'str' },
> +  'returns': ['RockerPort'] }
> +
> +{ 'type': 'RockerOfDpaFlowKey',
> +  'data' : { 'priority': 'uint32', 'tbl_id': 'uint32', '*in_pport': 'uint32',

Please use '-' rather than '_' in the spelling of new commands.

> +             '*tunnel_id': 'uint32', '*vlan_id': 'uint16',
> +             '*eth_type': 'uint16', '*eth_src': 'str', '*eth_dst': 'str',
> +             '*ip_proto': 'uint8', '*ip_tos': 'uint8', '*ip_dst': 'str' } }

Is 'str' the right type for IP addresses, or should it be a more
specific type?

> +
> +{ 'type': 'RockerOfDpaFlowMask',
> +  'data' : { '*in_pport': 'uint32', '*tunnel_id': 'uint32',
> +             '*vlan_id': 'uint16', '*eth_src': 'str', '*eth_dst': 'str',
> +             '*ip_proto': 'uint8', '*ip_tos': 'uint8' } }
> +
> +{ 'type': 'RockerOfDpaFlowAction',
> +  'data' : { '*goto_tbl': 'uint32', '*group_id': 'uint32',
> +             '*tun_log_pport': 'uint32', '*vlan_id': 'uint16',
> +             '*new_vlan_id': 'uint16', '*out_pport': 'uint32' } }
> +
> +{ 'type': 'RockerOfDpaFlow',
> +  'data': { 'cookie': 'uint64', 'hits': 'uint64', 'key': 'RockerOfDpaFlowKey',
> +            'mask': 'RockerOfDpaFlowMask', 'action': 'RockerOfDpaFlowAction' } }
> +
> +{ 'command': 'rocker-of-dpa-flows',
> +  'data': { 'name': 'str', '*tbl_id': 'uint32' },
> +  'returns': ['RockerOfDpaFlow'] }

Is the idea here that omitting 'tbl_id' (should be spelled 'tbl-id')
will give you an array of all table entries, while specifying an id will
give you an array of just the one requested entry?

> +
> +{ 'type': 'RockerOfDpaGroup',
> +  'data': { 'id': 'uint32',  'type': 'uint8', '*vlan_id': 'uint16',
> +            '*pport': 'uint32', '*index': 'uint32', '*out_pport': 'uint32',
> +            '*group_id': 'uint32', '*set_vlan_id': 'uint16',
> +            '*pop_vlan': 'uint8', '*group_ids': ['uint32'],
> +            '*set_eth_src': 'str', '*set_eth_dst': 'str',
> +            '*ttl_check': 'uint8' } }
> +
> +{ 'command': 'rocker-of-dpa-groups',
> +  'data': { 'name': 'str', '*type': 'uint8' },
> +  'returns': ['RockerOfDpaGroup'] }

Needs documentation before I can tell for sure, but based on just the
qapi specification, I think it will be reasonable once you fix naming
conventions.

> +++ b/qmp-commands.hx
> @@ -3860,3 +3860,27 @@ Move mouse pointer to absolute coordinates (20000, 400).
>  <- { "return": {} }
>  
>  EQMP
> +
> +    {
> +        .name       = "rocker",
> +        .args_type  = "name:s",
> +        .mhandler.cmd_new = qmp_marshal_input_rocker,
> +    },
> +

Could you also provide example exchanges for each command added (what
the user passes in, and what qemu responds)?
Scott Feldman Jan. 4, 2015, 9:16 p.m. UTC | #2
On Fri, Jan 2, 2015 at 3:56 PM, Eric Blake <eblake@redhat.com> wrote:
> On 12/29/2014 10:14 PM, sfeldma@gmail.com wrote:
>> From: Scott Feldman <sfeldma@gmail.com>
>
> [your message came through as a top-level thread instead of in-reply-to
> the 0/10 cover letter; please see if you can fix that before the next
> submission]

Looks like I want git send-email options --no-chain-reply-to and
--thread, correct?

>>
>> Add QMP/HMP support for rocker devices.  This is mostly for debugging purposes
>> to see inside the device's tables and port configurations.  Some examples:
>>
>
> In this mail, I'll review just the QMP interface portion:
>
>> +++ b/qapi-schema.json
>> @@ -3515,3 +3515,54 @@
>>  # Since: 2.1
>>  ##
>>  { 'command': 'rtc-reset-reinjection' }
>> +
>> +{ 'type': 'Rocker',
>> +  'data': { 'name': 'str', 'id': 'uint64', 'ports': 'uint32' } }
>
> Please add documentation for each new type and each member of that type,
> including mention of which qemu release will first contain it.

Ok, v2.

>> +
>> +{ 'command': 'rocker',
>> +  'data': { 'name': 'str' },
>> +  'returns': 'Rocker' }
>> +
>> +{ 'type': 'RockerPort',
>> +  'data': { 'name': 'str', 'enabled': 'bool', 'link_up': 'bool',
>> +            'speed': 'uint32', 'duplex': 'uint8', 'autoneg': 'uint8' } }
>
> Why is 'duplex' a 'uint8'?  Shouldn't it instead be an enum type?
> Similar for 'autoneg'.

Ok, v2.

>
>> +
>> +{ 'command': 'rocker-ports',
>> +  'data': { 'name': 'str' },
>> +  'returns': ['RockerPort'] }
>> +
>> +{ 'type': 'RockerOfDpaFlowKey',
>> +  'data' : { 'priority': 'uint32', 'tbl_id': 'uint32', '*in_pport': 'uint32',
>
> Please use '-' rather than '_' in the spelling of new commands.

Ok, v2.

>> +             '*tunnel_id': 'uint32', '*vlan_id': 'uint16',
>> +             '*eth_type': 'uint16', '*eth_src': 'str', '*eth_dst': 'str',
>> +             '*ip_proto': 'uint8', '*ip_tos': 'uint8', '*ip_dst': 'str' } }
>
> Is 'str' the right type for IP addresses, or should it be a more
> specific type?

String, using dotted-decimal notation seems best for user and script
parsing.  If it's encoded as native u32 then we need to say if it's
host-byte-order or network-byte-order.

>
>> +
>> +{ 'type': 'RockerOfDpaFlowMask',
>> +  'data' : { '*in_pport': 'uint32', '*tunnel_id': 'uint32',
>> +             '*vlan_id': 'uint16', '*eth_src': 'str', '*eth_dst': 'str',
>> +             '*ip_proto': 'uint8', '*ip_tos': 'uint8' } }
>> +
>> +{ 'type': 'RockerOfDpaFlowAction',
>> +  'data' : { '*goto_tbl': 'uint32', '*group_id': 'uint32',
>> +             '*tun_log_pport': 'uint32', '*vlan_id': 'uint16',
>> +             '*new_vlan_id': 'uint16', '*out_pport': 'uint32' } }
>> +
>> +{ 'type': 'RockerOfDpaFlow',
>> +  'data': { 'cookie': 'uint64', 'hits': 'uint64', 'key': 'RockerOfDpaFlowKey',
>> +            'mask': 'RockerOfDpaFlowMask', 'action': 'RockerOfDpaFlowAction' } }
>> +
>> +{ 'command': 'rocker-of-dpa-flows',
>> +  'data': { 'name': 'str', '*tbl_id': 'uint32' },
>> +  'returns': ['RockerOfDpaFlow'] }
>
> Is the idea here that omitting 'tbl_id' (should be spelled 'tbl-id')
> will give you an array of all table entries, while specifying an id will
> give you an array of just the one requested entry?

Correct

>
>> +
>> +{ 'type': 'RockerOfDpaGroup',
>> +  'data': { 'id': 'uint32',  'type': 'uint8', '*vlan_id': 'uint16',
>> +            '*pport': 'uint32', '*index': 'uint32', '*out_pport': 'uint32',
>> +            '*group_id': 'uint32', '*set_vlan_id': 'uint16',
>> +            '*pop_vlan': 'uint8', '*group_ids': ['uint32'],
>> +            '*set_eth_src': 'str', '*set_eth_dst': 'str',
>> +            '*ttl_check': 'uint8' } }
>> +
>> +{ 'command': 'rocker-of-dpa-groups',
>> +  'data': { 'name': 'str', '*type': 'uint8' },
>> +  'returns': ['RockerOfDpaGroup'] }
>
> Needs documentation before I can tell for sure, but based on just the
> qapi specification, I think it will be reasonable once you fix naming
> conventions.

Ok.

>
>> +++ b/qmp-commands.hx
>> @@ -3860,3 +3860,27 @@ Move mouse pointer to absolute coordinates (20000, 400).
>>  <- { "return": {} }
>>
>>  EQMP
>> +
>> +    {
>> +        .name       = "rocker",
>> +        .args_type  = "name:s",
>> +        .mhandler.cmd_new = qmp_marshal_input_rocker,
>> +    },
>> +
>
> Could you also provide example exchanges for each command added (what
> the user passes in, and what qemu responds)?

Provide here or in the commit msg?  Or in the code?  Here's an example session:

(qemu) rocker sw1
name: sw1
id: 0x0000013512005452
ports: 4

(qemu) rocker-ports sw1
            ena/    speed/ auto
      port  link    duplex neg?
     sw1.1  up     10G  FD  No
     sw1.2  up     10G  FD  No
     sw1.3  !ena   10G  FD  No
     sw1.4  !ena   10G  FD  No

(qemu) rocker-of-dpa-flows sw1
prio tbl hits key(mask) --> actions
3    50  11   vlan 3840 dst 00:02:00:00:03:00 --> write group
0x0f000002 goto tbl 60
3    50  13   vlan 3840 dst 00:02:00:00:02:00 --> write group
0x0f000001 goto tbl 60
1    30       IP dst 11.0.0.0/32 --> write group 0x0f000000 goto tbl 60
1    30       IP dst 11.0.0.0/24 --> write group 0x0f000000 goto tbl 60
1    30       IP dst 11.0.0.255/32 --> write group 0x0f000000 goto tbl 60
1    30  10   IP dst 11.0.0.3/32 --> write group 0x0f000000 goto tbl 60
0    20       pport 2 vlan 3840 IPv6 dst 52:54:00:12:35:02 --> goto tbl 30
0    20       pport 2 vlan 3840 IP dst 52:54:00:12:35:02 --> goto tbl 30
1    20  8    pport 2 vlan 3840 IPv6 dst
33:33:00:00:00:00(ff:ff:00:00:00:00) --> goto tbl 40
1    20       pport 2 vlan 3840 IP dst
01:00:5e:00:00:00(ff:ff:ff:80:00:00) --> goto tbl 40
3    60  7366 pport 2 vlan 3840 dst
01:80:c2:00:00:00(ff:ff:ff:ff:ff:f0) --> write group 0x0f000000
0    20       pport 1 vlan 3840 IPv6 dst 52:54:00:12:35:01 --> goto tbl 30
0    20  10   pport 1 vlan 3840 IP dst 52:54:00:12:35:01 --> goto tbl 30
1    50  14735 vlan 3840 --> write group 0x4f000000 goto tbl 60
1    20  8    pport 1 vlan 3840 IPv6 dst
33:33:00:00:00:00(ff:ff:00:00:00:00) --> goto tbl 40
1    20       pport 1 vlan 3840 IP dst
01:00:5e:00:00:00(ff:ff:ff:80:00:00) --> goto tbl 40
3    60  7366 pport 1 vlan 3840 dst
01:80:c2:00:00:00(ff:ff:ff:ff:ff:f0) --> write group 0x0f000000
1    10  7387 pport 2 vlan 0 --> apply new vlan 3840 goto tbl 20
1    10  7398 pport 1 vlan 0 --> apply new vlan 3840 goto tbl 20
1    0   14785 pport 0(0xffff0000) --> goto tbl 10

(qemu) rocker-of-dpa-flows sw1 10
prio tbl hits key(mask) --> actions
1    10  7387 pport 2 vlan 0 --> apply new vlan 3840 goto tbl 20
1    10  7398 pport 1 vlan 0 --> apply new vlan 3840 goto tbl 20

(qemu) rocker-of-dpa-flows sw1 20
prio tbl hits key(mask) --> actions
0    20       pport 2 vlan 3840 IPv6 dst 52:54:00:12:35:02 --> goto tbl 30
0    20       pport 2 vlan 3840 IP dst 52:54:00:12:35:02 --> goto tbl 30
1    20  8    pport 2 vlan 3840 IPv6 dst
33:33:00:00:00:00(ff:ff:00:00:00:00) --> goto tbl 40
1    20       pport 2 vlan 3840 IP dst
01:00:5e:00:00:00(ff:ff:ff:80:00:00) --> goto tbl 40
0    20       pport 1 vlan 3840 IPv6 dst 52:54:00:12:35:01 --> goto tbl 30
0    20  10   pport 1 vlan 3840 IP dst 52:54:00:12:35:01 --> goto tbl 30
1    20  8    pport 1 vlan 3840 IPv6 dst
33:33:00:00:00:00(ff:ff:00:00:00:00) --> goto tbl 40
1    20       pport 1 vlan 3840 IP dst
01:00:5e:00:00:00(ff:ff:ff:80:00:00) --> goto tbl 40

(qemu) rocker-of-dpa-groups sw1
id (decode) --> buckets
0x0f000002 (type L2 interface vlan 3840 pport 2) --> pop vlan out pport 2
0x0f000001 (type L2 interface vlan 3840 pport 1) --> pop vlan out pport 1
0x4f000000 (type L2 flood vlan 3840 index 0) --> groups [0x0f000002,0x0f000001]
0x0f000000 (type L2 interface vlan 3840 pport 0) --> pop vlan out pport 0

(qemu) rocker-of-dpa-groups sw1 0
id (decode) --> buckets
0x0f000002 (type L2 interface vlan 3840 pport 2) --> pop vlan out pport 2
0x0f000001 (type L2 interface vlan 3840 pport 1) --> pop vlan out pport 1
0x0f000000 (type L2 interface vlan 3840 pport 0) --> pop vlan out pport 0

(qemu) rocker-of-dpa-groups sw1 4
id (decode) --> buckets
0x4f000000 (type L2 flood vlan 3840 index 0) --> groups [0x0f000002,0x0f000001]
Scott Feldman Jan. 4, 2015, 9:24 p.m. UTC | #3
On Sun, Jan 4, 2015 at 1:16 PM, Scott Feldman <sfeldma@gmail.com> wrote:
> On Fri, Jan 2, 2015 at 3:56 PM, Eric Blake <eblake@redhat.com> wrote:
>> On 12/29/2014 10:14 PM, sfeldma@gmail.com wrote:
>>> From: Scott Feldman <sfeldma@gmail.com>
>>
>> In this mail, I'll review just the QMP interface portion:

Thanks for review, BTW.

Question: should these rocker defs be moved to qapi/rocker.json, and
included in top-level qapi-schema.json?

-scott
Eric Blake Jan. 5, 2015, 5:10 p.m. UTC | #4
On 01/04/2015 02:16 PM, Scott Feldman wrote:
> On Fri, Jan 2, 2015 at 3:56 PM, Eric Blake <eblake@redhat.com> wrote:
>> On 12/29/2014 10:14 PM, sfeldma@gmail.com wrote:
>>> From: Scott Feldman <sfeldma@gmail.com>
>>
>> [your message came through as a top-level thread instead of in-reply-to
>> the 0/10 cover letter; please see if you can fix that before the next
>> submission]
> 
> Looks like I want git send-email options --no-chain-reply-to and
> --thread, correct?

When I use git send-email, the defaults just work.  But I don't know if
there is something in your ~/.gitconfig that changes the defaults, so I
also don't know if those options would restore it back to the default.
All I can suggest is experimenting with sending a test series to
yourself to see if you got settings right, before sending to the list.

You can also update the wiki if you find out what was different for your
case, and where adding options got it fixed:

http://wiki.qemu.org/Contribute/SubmitAPatch


>>> +             '*tunnel_id': 'uint32', '*vlan_id': 'uint16',
>>> +             '*eth_type': 'uint16', '*eth_src': 'str', '*eth_dst': 'str',
>>> +             '*ip_proto': 'uint8', '*ip_tos': 'uint8', '*ip_dst': 'str' } }
>>
>> Is 'str' the right type for IP addresses, or should it be a more
>> specific type?
> 
> String, using dotted-decimal notation seems best for user and script
> parsing.  If it's encoded as native u32 then we need to say if it's
> host-byte-order or network-byte-order.

I can live with string if nothing is better; there's always the
possibility of an array of uint8, rather than trying to encode as a
native u32, but the further we get from normal notation, the harder it
gets to visualize what is going on.  Just throwing it out to make sure
we think about it.


>>> +++ b/qmp-commands.hx
>>> @@ -3860,3 +3860,27 @@ Move mouse pointer to absolute coordinates (20000, 400).
>>>  <- { "return": {} }
>>>
>>>  EQMP
>>> +
>>> +    {
>>> +        .name       = "rocker",
>>> +        .args_type  = "name:s",
>>> +        .mhandler.cmd_new = qmp_marshal_input_rocker,
>>> +    },
>>> +
>>
>> Could you also provide example exchanges for each command added (what
>> the user passes in, and what qemu responds)?
> 
> Provide here or in the commit msg?  Or in the code?  Here's an example session:

In the .hx file.  Plenty of other examples in that file to see what
format they use.

> 
> (qemu) rocker sw1
> name: sw1
> id: 0x0000013512005452
> ports: 4
> 
> (qemu) rocker-ports sw1
>             ena/    speed/ auto
>       port  link    duplex neg?
>      sw1.1  up     10G  FD  No
>      sw1.2  up     10G  FD  No
>      sw1.3  !ena   10G  FD  No
>      sw1.4  !ena   10G  FD  No

That's the HMP representation; I'm asking about the QMP representation
(the JSON exchange).
Eric Blake Jan. 5, 2015, 5:11 p.m. UTC | #5
On 01/04/2015 02:24 PM, Scott Feldman wrote:
> On Sun, Jan 4, 2015 at 1:16 PM, Scott Feldman <sfeldma@gmail.com> wrote:
>> On Fri, Jan 2, 2015 at 3:56 PM, Eric Blake <eblake@redhat.com> wrote:
>>> On 12/29/2014 10:14 PM, sfeldma@gmail.com wrote:
>>>> From: Scott Feldman <sfeldma@gmail.com>
>>>
>>> In this mail, I'll review just the QMP interface portion:
> 
> Thanks for review, BTW.
> 
> Question: should these rocker defs be moved to qapi/rocker.json, and
> included in top-level qapi-schema.json?

Up to you, if you think they are big enough and useful enough to be
broken into a separate file.
diff mbox

Patch

diff --git a/hmp-commands.hx b/hmp-commands.hx
index e37bc8b..67ad0d9 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -1786,6 +1786,62 @@  STEXI
 show available trace events and their state
 ETEXI
 
+    {
+        .name       = "rocker",
+        .args_type  = "name:s",
+        .params     = "rocker name",
+        .help       = "Show Rocker(s)",
+        .mhandler.cmd = hmp_rocker,
+    },
+
+STEXI
+@item rocker @var{name}
+@findex rocker
+Show Rocker(s)
+ETEXI
+
+    {
+        .name       = "rocker-ports",
+        .args_type  = "name:s",
+        .params     = "rocker_ports name",
+        .help       = "Show Rocker ports",
+        .mhandler.cmd = hmp_rocker_ports,
+    },
+
+STEXI
+@item rocker_ports @var{name}
+@findex rocker_ports
+Show Rocker ports
+ETEXI
+
+    {
+        .name       = "rocker-of-dpa-flows",
+        .args_type  = "name:s,tbl_id:i?",
+        .params     = "rocker_of_dpa_flows name [tbl_id]",
+        .help       = "Show Rocker OF-DPA flow tables",
+        .mhandler.cmd = hmp_rocker_of_dpa_flows,
+    },
+
+STEXI
+@item rocker_of_dpa_flows @var{name} [@var{tbl_id}]
+@findex rocker_of_dpa_flows
+Show Rocker OF-DPA flow tables
+ETEXI
+
+    {
+        .name       = "rocker-of-dpa-groups",
+        .args_type  = "name:s,type:i?",
+        .params     = "rocker_of_dpa_groups name [type]",
+        .help       = "Show Rocker OF-DPA groups",
+        .mhandler.cmd = hmp_rocker_of_dpa_groups,
+    },
+
+STEXI
+@item rocker_of_dpa_groups @var{name} [@var{type}]
+@findex rocker_of_dpa_groups
+Show Rocker OF-DPA groups
+ETEXI
+
 STEXI
 @end table
 ETEXI
diff --git a/hmp.c b/hmp.c
index 481be80..2290d10 100644
--- a/hmp.c
+++ b/hmp.c
@@ -15,6 +15,7 @@ 
 
 #include "hmp.h"
 #include "net/net.h"
+#include "net/eth.h"
 #include "sysemu/char.h"
 #include "qemu/option.h"
 #include "qemu/timer.h"
@@ -1813,3 +1814,305 @@  void hmp_info_memory_devices(Monitor *mon, const QDict *qdict)
 
     qapi_free_MemoryDeviceInfoList(info_list);
 }
+
+void hmp_rocker(Monitor *mon, const QDict *qdict)
+{
+    const char *name = qdict_get_str(qdict, "name");
+    Rocker *rocker;
+    Error *errp = NULL;
+
+    rocker = qmp_rocker(name, &errp);
+    if (errp != NULL) {
+        hmp_handle_error(mon, &errp);
+        return;
+    }
+
+    monitor_printf(mon, "name: %s\n", rocker->name);
+    monitor_printf(mon, "id: 0x%016lx\n", rocker->id);
+    monitor_printf(mon, "ports: %d\n", rocker->ports);
+
+    qapi_free_Rocker(rocker);
+}
+
+void hmp_rocker_ports(Monitor *mon, const QDict *qdict)
+{
+    RockerPortList *list, *port;
+    const char *name = qdict_get_str(qdict, "name");
+    Error *errp = NULL;
+
+    list = qmp_rocker_ports(name, &errp);
+    if (errp != NULL) {
+        hmp_handle_error(mon, &errp);
+        return;
+    }
+
+    monitor_printf(mon, "            ena/    speed/ auto\n");
+    monitor_printf(mon, "      port  link    duplex neg?\n");
+
+    for (port = list; port; port = port->next)
+        monitor_printf(mon, "%10s  %-4s   %-3s  %2s  %-3s\n",
+                       port->value->name,
+                       port->value->enabled ? port->value->link_up ?
+                       "up" : "down" : "!ena",
+                       port->value->speed == 10000 ? "10G" : "??",
+                       port->value->duplex ? "FD" : "HD",
+                       port->value->autoneg ? "Yes" : "No");
+
+    qapi_free_RockerPortList(list);
+}
+
+void hmp_rocker_of_dpa_flows(Monitor *mon, const QDict *qdict)
+{
+    RockerOfDpaFlowList *list, *info;
+    const char *name = qdict_get_str(qdict, "name");
+    uint32_t tbl_id = qdict_get_try_int(qdict, "tbl_id", -1);
+    Error *errp = NULL;
+
+    list = qmp_rocker_of_dpa_flows(name, tbl_id != -1, tbl_id, &errp);
+    if (errp != NULL) {
+        hmp_handle_error(mon, &errp);
+        return;
+    }
+
+    monitor_printf(mon, "prio tbl hits key(mask) --> actions\n");
+
+    for (info = list; info; info = info->next) {
+        RockerOfDpaFlow *flow = info->value;
+        RockerOfDpaFlowKey *key = flow->key;
+        RockerOfDpaFlowMask *mask = flow->mask;
+        RockerOfDpaFlowAction *action = flow->action;
+
+        if (flow->hits) {
+            monitor_printf(mon, "%-4d %-3d %-4ld",
+                           key->priority, key->tbl_id, flow->hits);
+        } else {
+            monitor_printf(mon, "%-4d %-3d     ",
+                           key->priority, key->tbl_id);
+        }
+
+        if (key->has_in_pport) {
+            monitor_printf(mon, " pport %d", key->in_pport);
+            if (mask->has_in_pport) {
+                monitor_printf(mon, "(0x%x)", mask->in_pport);
+            }
+        }
+
+        if (key->has_vlan_id) {
+            monitor_printf(mon, " vlan %d",
+                           key->vlan_id & VLAN_VID_MASK);
+            if (mask->has_vlan_id) {
+                monitor_printf(mon, "(0x%x)", mask->vlan_id);
+            }
+        }
+
+        if (key->has_tunnel_id) {
+            monitor_printf(mon, " tunnel %d", key->tunnel_id);
+            if (mask->has_tunnel_id) {
+                monitor_printf(mon, "(0x%x)", mask->tunnel_id);
+            }
+        }
+
+        if (key->has_eth_type) {
+            switch (key->eth_type) {
+            case 0x0806:
+                monitor_printf(mon, " ARP");
+                break;
+            case 0x0800:
+                monitor_printf(mon, " IP");
+                break;
+            case 0x86dd:
+                monitor_printf(mon, " IPv6");
+                break;
+            case 0x8809:
+                monitor_printf(mon, " LACP");
+                break;
+            case 0x88cc:
+                monitor_printf(mon, " LLDP");
+                break;
+            default:
+                monitor_printf(mon, " eth type 0x%04x", key->eth_type);
+                break;
+            }
+        }
+
+        if (key->has_eth_src) {
+            if ((strcmp(key->eth_src, "01:00:00:00:00:00") == 0) &&
+                (mask->has_eth_src) &&
+                (strcmp(mask->eth_src, "01:00:00:00:00:00") == 0)) {
+                monitor_printf(mon, " src <any mcast/bcast>");
+            } else if ((strcmp(key->eth_src, "00:00:00:00:00:00") == 0) &&
+                (mask->has_eth_src) &&
+                (strcmp(mask->eth_src, "01:00:00:00:00:00") == 0)) {
+                monitor_printf(mon, " src <any ucast>");
+            } else {
+                monitor_printf(mon, " src %s", key->eth_src);
+                if (mask->has_eth_src) {
+                    monitor_printf(mon, "(%s)", mask->eth_src);
+                }
+            }
+        }
+
+        if (key->has_eth_dst) {
+            if ((strcmp(key->eth_dst, "01:00:00:00:00:00") == 0) &&
+                (mask->has_eth_dst) &&
+                (strcmp(mask->eth_dst, "01:00:00:00:00:00") == 0)) {
+                monitor_printf(mon, " dst <any mcast/bcast>");
+            } else if ((strcmp(key->eth_dst, "00:00:00:00:00:00") == 0) &&
+                (mask->has_eth_dst) &&
+                (strcmp(mask->eth_dst, "01:00:00:00:00:00") == 0)) {
+                monitor_printf(mon, " dst <any ucast>");
+            } else {
+                monitor_printf(mon, " dst %s", key->eth_dst);
+                if (mask->has_eth_dst) {
+                    monitor_printf(mon, "(%s)", mask->eth_dst);
+                }
+            }
+        }
+
+        if (key->has_ip_proto) {
+            monitor_printf(mon, " proto %d", key->ip_proto);
+            if (mask->has_ip_proto) {
+                monitor_printf(mon, "(0x%x)", mask->ip_proto);
+            }
+        }
+
+        if (key->has_ip_tos) {
+            monitor_printf(mon, " TOS %d", key->ip_tos);
+            if (mask->has_ip_tos) {
+                monitor_printf(mon, "(0x%x)", mask->ip_tos);
+            }
+        }
+
+        if (key->has_ip_dst) {
+            monitor_printf(mon, " dst %s", key->ip_dst);
+        }
+
+        if (action->has_goto_tbl || action->has_group_id ||
+            action->has_new_vlan_id) {
+            monitor_printf(mon, " -->");
+        }
+
+        if (action->has_new_vlan_id) {
+            monitor_printf(mon, " apply new vlan %d",
+                           ntohs(action->new_vlan_id));
+        }
+
+        if (action->has_group_id) {
+            monitor_printf(mon, " write group 0x%08x", action->group_id);
+        }
+
+        if (action->has_goto_tbl) {
+            monitor_printf(mon, " goto tbl %d", action->goto_tbl);
+        }
+
+        monitor_printf(mon, "\n");
+    }
+
+    qapi_free_RockerOfDpaFlowList(list);
+}
+
+void hmp_rocker_of_dpa_groups(Monitor *mon, const QDict *qdict)
+{
+    RockerOfDpaGroupList *list, *g;
+    const char *name = qdict_get_str(qdict, "name");
+    uint8_t type = qdict_get_try_int(qdict, "type", 9);
+    Error *errp = NULL;
+    bool set = false;
+
+    list = qmp_rocker_of_dpa_groups(name, type != 9, type, &errp);
+    if (errp != NULL) {
+        hmp_handle_error(mon, &errp);
+        return;
+    }
+
+    monitor_printf(mon, "id (decode) --> buckets\n");
+
+    for (g = list; g; g = g->next) {
+        RockerOfDpaGroup *group = g->value;
+
+        monitor_printf(mon, "0x%08x", group->id);
+
+        monitor_printf(mon, " (type %s", group->type == 0 ? "L2 interface" :
+                                         group->type == 1 ? "L2 rewrite" :
+                                         group->type == 2 ? "L3 unicast" :
+                                         group->type == 3 ? "L2 multicast" :
+                                         group->type == 4 ? "L2 flood" :
+                                         group->type == 5 ? "L3 interface" :
+                                         group->type == 6 ? "L3 multicast" :
+                                         group->type == 7 ? "L3 ECMP" :
+                                         group->type == 8 ? "L2 overlay" :
+                                         "unknown");
+
+        if (group->has_vlan_id) {
+            monitor_printf(mon, " vlan %d", group->vlan_id);
+        }
+
+        if (group->has_pport) {
+            monitor_printf(mon, " pport %d", group->pport);
+        }
+
+        if (group->has_index) {
+            monitor_printf(mon, " index %d", group->index);
+        }
+
+        monitor_printf(mon, ") -->");
+
+        if (group->has_set_vlan_id && group->set_vlan_id) {
+            set = true;
+            monitor_printf(mon, " set vlan %d",
+                           group->set_vlan_id & VLAN_VID_MASK);
+        }
+
+        if (group->has_set_eth_src) {
+            if (!set) {
+                set = true;
+                monitor_printf(mon, " set");
+            }
+            monitor_printf(mon, " src %s", group->set_eth_src);
+        }
+
+        if (group->has_set_eth_dst) {
+            if (!set) {
+                set = true;
+                monitor_printf(mon, " set");
+            }
+            monitor_printf(mon, " dst %s", group->set_eth_dst);
+        }
+
+        set = false;
+
+        if (group->has_ttl_check && group->ttl_check) {
+            monitor_printf(mon, " check TTL");
+        }
+
+        if (group->has_group_id && group->group_id) {
+            monitor_printf(mon, " group id 0x%08x", group->group_id);
+        }
+
+        if (group->has_pop_vlan && group->pop_vlan) {
+            monitor_printf(mon, " pop vlan");
+        }
+
+        if (group->has_out_pport) {
+            monitor_printf(mon, " out pport %d", group->out_pport);
+        }
+
+        if (group->has_group_ids) {
+            struct uint32List *id;
+
+            monitor_printf(mon, " groups [");
+            for (id = group->group_ids; id; id = id->next) {
+                monitor_printf(mon, "0x%08x", id->value);
+                if (id->next) {
+                    monitor_printf(mon, ",");
+                }
+            }
+            monitor_printf(mon, "]");
+        }
+
+        monitor_printf(mon, "\n");
+    }
+
+    qapi_free_RockerOfDpaGroupList(list);
+}
+
diff --git a/hmp.h b/hmp.h
index 4bb5dca..dc8a15c 100644
--- a/hmp.h
+++ b/hmp.h
@@ -116,5 +116,9 @@  void host_net_remove_completion(ReadLineState *rs, int nb_args,
                                 const char *str);
 void delvm_completion(ReadLineState *rs, int nb_args, const char *str);
 void loadvm_completion(ReadLineState *rs, int nb_args, const char *str);
+void hmp_rocker(Monitor *mon, const QDict *qdict);
+void hmp_rocker_ports(Monitor *mon, const QDict *qdict);
+void hmp_rocker_of_dpa_flows(Monitor *mon, const QDict *qdict);
+void hmp_rocker_of_dpa_groups(Monitor *mon, const QDict *qdict);
 
 #endif
diff --git a/hw/net/rocker/rocker.c b/hw/net/rocker/rocker.c
index b410552..278894e 100644
--- a/hw/net/rocker/rocker.c
+++ b/hw/net/rocker/rocker.c
@@ -22,6 +22,7 @@ 
 #include "net/eth.h"
 #include "qemu/iov.h"
 #include "qemu/bitops.h"
+#include "qmp-commands.h"
 
 #include "rocker.h"
 #include "rocker_hw.h"
@@ -92,6 +93,51 @@  struct world *rocker_get_world(struct rocker *r, enum rocker_world_type type)
     return NULL;
 }
 
+Rocker *qmp_rocker(const char *name, Error **errp)
+{
+    Rocker *rocker = g_malloc0(sizeof(*rocker));
+    struct rocker *r;
+
+    r = rocker_find(name);
+    if (!r) {
+        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+                  "rocker %s not found", name);
+        return NULL;
+    }
+
+    rocker->name = g_strdup(r->name);
+    rocker->id = r->switch_id;
+    rocker->ports = r->fp_ports;
+
+    return rocker;
+}
+
+RockerPortList *qmp_rocker_ports(const char *name, Error **errp)
+{
+    RockerPortList *list = NULL;
+    struct rocker *r;
+    int i;
+
+    r = rocker_find(name);
+    if (!r) {
+        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+                  "rocker %s not found", name);
+        return NULL;
+    }
+
+    for (i = r->fp_ports - 1; i >= 0; i--) {
+        RockerPortList *info = g_malloc0(sizeof(*info));
+        info->value = g_malloc0(sizeof(*info->value));
+        struct fp_port *port = r->fp_port[i];
+
+        fp_port_get_info(port, info);
+        info->next = list;
+        list = info;
+    }
+
+    return list;
+}
+
 uint32_t rocker_fp_ports(struct rocker *r)
 {
     return r->fp_ports;
diff --git a/hw/net/rocker/rocker_fp.c b/hw/net/rocker/rocker_fp.c
index 01734e5..9cb1cc5 100644
--- a/hw/net/rocker/rocker_fp.c
+++ b/hw/net/rocker/rocker_fp.c
@@ -15,6 +15,7 @@ 
  */
 
 #include "net/clients.h"
+#include "qmp-commands.h"
 
 #include "rocker.h"
 #include "rocker_hw.h"
diff --git a/hw/net/rocker/rocker_of_dpa.c b/hw/net/rocker/rocker_of_dpa.c
index 328f351..432665b 100644
--- a/hw/net/rocker/rocker_of_dpa.c
+++ b/hw/net/rocker/rocker_of_dpa.c
@@ -17,6 +17,7 @@ 
 #include "net/eth.h"
 #include "qemu/iov.h"
 #include "qemu/timer.h"
+#include "qmp-commands.h"
 
 #include "rocker.h"
 #include "rocker_hw.h"
@@ -2321,6 +2322,314 @@  static void of_dpa_uninit(struct world *world)
     g_hash_table_destroy(of_dpa->flow_tbl);
 }
 
+struct of_dpa_flow_fill_context {
+    RockerOfDpaFlowList *list;
+    uint32_t tbl_id;
+};
+
+static void of_dpa_flow_fill(void *cookie, void *value, void *user_data)
+{
+    struct of_dpa_flow *flow = value;
+    struct of_dpa_flow_key *key = &flow->key;
+    struct of_dpa_flow_key *mask = &flow->mask;
+    struct of_dpa_flow_fill_context *flow_context = user_data;
+    RockerOfDpaFlowList *new;
+    RockerOfDpaFlow *nflow;
+    RockerOfDpaFlowKey *nkey;
+    RockerOfDpaFlowMask *nmask;
+    RockerOfDpaFlowAction *naction;
+
+    if (flow_context->tbl_id != -1 &&
+        flow_context->tbl_id != key->tbl_id) {
+        return;
+    }
+
+    new = g_malloc0(sizeof(*new));
+    nflow = new->value = g_malloc0(sizeof(*nflow));
+    nkey = nflow->key = g_malloc0(sizeof(*nkey));
+    nmask = nflow->mask = g_malloc0(sizeof(*nmask));
+    naction = nflow->action = g_malloc0(sizeof(*naction));
+
+    nflow->cookie = flow->cookie;
+    nflow->hits = flow->stats.hits;
+    nkey->priority = flow->priority;
+    nkey->tbl_id = key->tbl_id;
+
+    if (key->in_pport || mask->in_pport) {
+        nkey->has_in_pport = true;
+        nkey->in_pport = key->in_pport;
+    }
+
+    if (nkey->has_in_pport && mask->in_pport != 0xffffffff) {
+        nmask->has_in_pport = true;
+        nmask->in_pport = mask->in_pport;
+    }
+
+    if (key->eth.vlan_id || mask->eth.vlan_id) {
+        nkey->has_vlan_id = true;
+        nkey->vlan_id = ntohs(key->eth.vlan_id);
+    }
+
+    if (nkey->has_vlan_id && mask->eth.vlan_id != 0xffff) {
+        nmask->has_vlan_id = true;
+        nmask->vlan_id = ntohs(mask->eth.vlan_id);
+    }
+
+    if (key->tunnel_id || mask->tunnel_id) {
+        nkey->has_tunnel_id = true;
+        nkey->tunnel_id = key->tunnel_id;
+    }
+
+    if (nkey->has_tunnel_id && mask->tunnel_id != 0xffffffff) {
+        nmask->has_tunnel_id = true;
+        nmask->tunnel_id = mask->tunnel_id;
+    }
+
+    if (memcmp(key->eth.src.a, zero_mac.a, ETH_ALEN) ||
+        memcmp(mask->eth.src.a, zero_mac.a, ETH_ALEN)) {
+        nkey->has_eth_src = true;
+        nkey->eth_src = qemu_mac_strdup_printf(key->eth.src.a);
+    }
+
+    if (nkey->has_eth_src && memcmp(mask->eth.src.a, ff_mac.a, ETH_ALEN)) {
+        nmask->has_eth_src = true;
+        nmask->eth_src = qemu_mac_strdup_printf(mask->eth.src.a);
+    }
+
+    if (memcmp(key->eth.dst.a, zero_mac.a, ETH_ALEN) ||
+        memcmp(mask->eth.dst.a, zero_mac.a, ETH_ALEN)) {
+        nkey->has_eth_dst = true;
+        nkey->eth_dst = qemu_mac_strdup_printf(key->eth.dst.a);
+    }
+
+    if (nkey->has_eth_dst && memcmp(mask->eth.dst.a, ff_mac.a, ETH_ALEN)) {
+        nmask->has_eth_dst = true;
+        nmask->eth_dst = qemu_mac_strdup_printf(mask->eth.dst.a);
+    }
+
+    if (key->eth.type) {
+
+        nkey->has_eth_type = true;
+        nkey->eth_type = ntohs(key->eth.type);
+
+        switch (ntohs(key->eth.type)) {
+        case 0x0800:
+        case 0x86dd:
+            if (key->ip.proto || mask->ip.proto) {
+                nkey->has_ip_proto = true;
+                nkey->ip_proto = key->ip.proto;
+            }
+            if (nkey->has_ip_proto && mask->ip.proto != 0xff) {
+                nmask->has_ip_proto = true;
+                nmask->ip_proto = mask->ip.proto;
+            }
+            if (key->ip.tos || mask->ip.tos) {
+                nkey->has_ip_tos = true;
+                nkey->ip_tos = key->ip.tos;
+            }
+            if (nkey->has_ip_tos && mask->ip.tos != 0xff) {
+                nmask->has_ip_tos = true;
+                nmask->ip_tos = mask->ip.tos;
+            }
+            break;
+        }
+
+        switch (ntohs(key->eth.type)) {
+        case 0x0800:
+            if (key->ipv4.addr.dst || mask->ipv4.addr.dst) {
+                char *dst = inet_ntoa(*(struct in_addr *)&key->ipv4.addr.dst);
+                int dst_len = of_dpa_mask2prefix(mask->ipv4.addr.dst);
+                nkey->has_ip_dst = true;
+                nkey->ip_dst = g_strdup_printf("%s/%d", dst, dst_len);
+            }
+            break;
+        }
+    }
+
+    if (flow->action.goto_tbl) {
+        naction->has_goto_tbl = true;
+        naction->goto_tbl = flow->action.goto_tbl;
+    }
+
+    if (flow->action.write.group_id) {
+        naction->has_group_id = true;
+        naction->group_id = flow->action.write.group_id;
+    }
+
+    if (flow->action.apply.new_vlan_id) {
+        naction->has_new_vlan_id = true;
+        naction->new_vlan_id = flow->action.apply.new_vlan_id;
+    }
+
+    new->next = flow_context->list;
+    flow_context->list = new;
+}
+
+RockerOfDpaFlowList *qmp_rocker_of_dpa_flows(const char *name, bool has_tbl_id,
+                                             uint32_t tbl_id, Error **errp)
+{
+    struct rocker *r;
+    struct world *w;
+    struct of_dpa *of_dpa;
+    struct of_dpa_flow_fill_context fill_context = {
+        .list = NULL,
+        .tbl_id = tbl_id,
+    };
+
+    r = rocker_find(name);
+    if (!r) {
+        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+                  "rocker %s not found", name);
+        return NULL;
+    }
+
+    w = rocker_get_world(r, ROCKER_WORLD_TYPE_OF_DPA);
+    if (!w) {
+        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+                  "rocker %s doesn't have OF-DPA world", name);
+        return NULL;
+    }
+
+    of_dpa = world_private(w);
+
+    g_hash_table_foreach(of_dpa->flow_tbl, of_dpa_flow_fill, &fill_context);
+
+    return fill_context.list;
+}
+
+struct of_dpa_group_fill_context {
+    RockerOfDpaGroupList *list;
+    uint8_t type;
+};
+
+static void of_dpa_group_fill(void *key, void *value, void *user_data)
+{
+    struct of_dpa_group *group = value;
+    struct of_dpa_group_fill_context *flow_context = user_data;
+    RockerOfDpaGroupList *new;
+    RockerOfDpaGroup *ngroup;
+    struct uint32List *id;
+    int i;
+
+    if (flow_context->type != 9 &&
+        flow_context->type != ROCKER_GROUP_TYPE_GET(group->id)) {
+        return;
+    }
+
+    new = g_malloc0(sizeof(*new));
+    ngroup = new->value = g_malloc0(sizeof(*ngroup));
+
+    ngroup->id = group->id;
+
+    ngroup->type = ROCKER_GROUP_TYPE_GET(group->id);
+
+    switch (ngroup->type) {
+    case ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE:
+        ngroup->has_vlan_id = true;
+        ngroup->vlan_id = ROCKER_GROUP_VLAN_GET(group->id);
+        ngroup->has_pport = true;
+        ngroup->pport = ROCKER_GROUP_PORT_GET(group->id);
+        ngroup->has_out_pport = true;
+        ngroup->out_pport = group->l2_interface.out_pport;
+        ngroup->has_pop_vlan = true;
+        ngroup->pop_vlan = group->l2_interface.pop_vlan;
+        break;
+    case ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE:
+        ngroup->has_index = true;
+        ngroup->index = ROCKER_GROUP_INDEX_LONG_GET(group->id);
+        ngroup->has_group_id = true;
+        ngroup->group_id = group->l2_rewrite.group_id;
+        if (group->l2_rewrite.vlan_id) {
+            ngroup->has_set_vlan_id = true;
+            ngroup->set_vlan_id = ntohs(group->l2_rewrite.vlan_id);
+        }
+        break;
+        if (memcmp(group->l2_rewrite.src_mac.a, zero_mac.a, ETH_ALEN)) {
+            ngroup->has_set_eth_src = true;
+            ngroup->set_eth_src =
+                qemu_mac_strdup_printf(group->l2_rewrite.src_mac.a);
+        }
+        if (memcmp(group->l2_rewrite.dst_mac.a, zero_mac.a, ETH_ALEN)) {
+            ngroup->has_set_eth_dst = true;
+            ngroup->set_eth_dst =
+                qemu_mac_strdup_printf(group->l2_rewrite.dst_mac.a);
+        }
+    case ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD:
+    case ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST:
+        ngroup->has_vlan_id = true;
+        ngroup->vlan_id = ROCKER_GROUP_VLAN_GET(group->id);
+        ngroup->has_index = true;
+        ngroup->index = ROCKER_GROUP_INDEX_GET(group->id);
+        for (i = 0; i < group->l2_flood.group_count; i++) {
+            ngroup->has_group_ids = true;
+            id = g_malloc0(sizeof(*id));
+            id->value = group->l2_flood.group_ids[i];
+            id->next = ngroup->group_ids;
+            ngroup->group_ids = id;
+        }
+        break;
+    case ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST:
+        ngroup->has_index = true;
+        ngroup->index = ROCKER_GROUP_INDEX_LONG_GET(group->id);
+        ngroup->has_group_id = true;
+        ngroup->group_id = group->l3_unicast.group_id;
+        if (group->l3_unicast.vlan_id) {
+            ngroup->has_set_vlan_id = true;
+            ngroup->set_vlan_id = ntohs(group->l3_unicast.vlan_id);
+        }
+        if (memcmp(group->l3_unicast.src_mac.a, zero_mac.a, ETH_ALEN)) {
+            ngroup->has_set_eth_src = true;
+            ngroup->set_eth_src =
+                qemu_mac_strdup_printf(group->l3_unicast.src_mac.a);
+        }
+        if (memcmp(group->l3_unicast.dst_mac.a, zero_mac.a, ETH_ALEN)) {
+            ngroup->has_set_eth_dst = true;
+            ngroup->set_eth_dst =
+                qemu_mac_strdup_printf(group->l3_unicast.dst_mac.a);
+        }
+        if (group->l3_unicast.ttl_check) {
+            ngroup->has_ttl_check = true;
+            ngroup->ttl_check = group->l3_unicast.ttl_check;
+        }
+        break;
+    }
+
+    new->next = flow_context->list;
+    flow_context->list = new;
+}
+
+RockerOfDpaGroupList *qmp_rocker_of_dpa_groups(const char *name, bool has_type,
+                                               uint8_t type, Error **errp)
+{
+    struct rocker *r;
+    struct world *w;
+    struct of_dpa *of_dpa;
+    struct of_dpa_group_fill_context fill_context = {
+        .list = NULL,
+        .type = type,
+    };
+
+    r = rocker_find(name);
+    if (!r) {
+        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+                  "rocker %s not found", name);
+        return NULL;
+    }
+
+    w = rocker_get_world(r, ROCKER_WORLD_TYPE_OF_DPA);
+    if (!w) {
+        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+                  "rocker %s doesn't have OF-DPA world", name);
+        return NULL;
+    }
+
+    of_dpa = world_private(w);
+
+    g_hash_table_foreach(of_dpa->group_tbl, of_dpa_group_fill, &fill_context);
+
+    return fill_context.list;
+}
+
 static struct world_ops of_dpa_ops = {
     .init = of_dpa_init,
     .uninit = of_dpa_uninit,
diff --git a/qapi-schema.json b/qapi-schema.json
index 563b4ad..6fdf58b 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3515,3 +3515,54 @@ 
 # Since: 2.1
 ##
 { 'command': 'rtc-reset-reinjection' }
+
+{ 'type': 'Rocker',
+  'data': { 'name': 'str', 'id': 'uint64', 'ports': 'uint32' } }
+
+{ 'command': 'rocker',
+  'data': { 'name': 'str' },
+  'returns': 'Rocker' }
+
+{ 'type': 'RockerPort',
+  'data': { 'name': 'str', 'enabled': 'bool', 'link_up': 'bool',
+            'speed': 'uint32', 'duplex': 'uint8', 'autoneg': 'uint8' } }
+
+{ 'command': 'rocker-ports',
+  'data': { 'name': 'str' },
+  'returns': ['RockerPort'] }
+
+{ 'type': 'RockerOfDpaFlowKey',
+  'data' : { 'priority': 'uint32', 'tbl_id': 'uint32', '*in_pport': 'uint32',
+             '*tunnel_id': 'uint32', '*vlan_id': 'uint16',
+             '*eth_type': 'uint16', '*eth_src': 'str', '*eth_dst': 'str',
+             '*ip_proto': 'uint8', '*ip_tos': 'uint8', '*ip_dst': 'str' } }
+
+{ 'type': 'RockerOfDpaFlowMask',
+  'data' : { '*in_pport': 'uint32', '*tunnel_id': 'uint32',
+             '*vlan_id': 'uint16', '*eth_src': 'str', '*eth_dst': 'str',
+             '*ip_proto': 'uint8', '*ip_tos': 'uint8' } }
+
+{ 'type': 'RockerOfDpaFlowAction',
+  'data' : { '*goto_tbl': 'uint32', '*group_id': 'uint32',
+             '*tun_log_pport': 'uint32', '*vlan_id': 'uint16',
+             '*new_vlan_id': 'uint16', '*out_pport': 'uint32' } }
+
+{ 'type': 'RockerOfDpaFlow',
+  'data': { 'cookie': 'uint64', 'hits': 'uint64', 'key': 'RockerOfDpaFlowKey',
+            'mask': 'RockerOfDpaFlowMask', 'action': 'RockerOfDpaFlowAction' } }
+
+{ 'command': 'rocker-of-dpa-flows',
+  'data': { 'name': 'str', '*tbl_id': 'uint32' },
+  'returns': ['RockerOfDpaFlow'] }
+
+{ 'type': 'RockerOfDpaGroup',
+  'data': { 'id': 'uint32',  'type': 'uint8', '*vlan_id': 'uint16',
+            '*pport': 'uint32', '*index': 'uint32', '*out_pport': 'uint32',
+            '*group_id': 'uint32', '*set_vlan_id': 'uint16',
+            '*pop_vlan': 'uint8', '*group_ids': ['uint32'],
+            '*set_eth_src': 'str', '*set_eth_dst': 'str',
+            '*ttl_check': 'uint8' } }
+
+{ 'command': 'rocker-of-dpa-groups',
+  'data': { 'name': 'str', '*type': 'uint8' },
+  'returns': ['RockerOfDpaGroup'] }
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 6945d30..fdad1c9 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -3860,3 +3860,27 @@  Move mouse pointer to absolute coordinates (20000, 400).
 <- { "return": {} }
 
 EQMP
+
+    {
+        .name       = "rocker",
+        .args_type  = "name:s",
+        .mhandler.cmd_new = qmp_marshal_input_rocker,
+    },
+
+    {
+        .name       = "rocker-ports",
+        .args_type  = "name:s",
+        .mhandler.cmd_new = qmp_marshal_input_rocker_ports,
+    },
+
+    {
+        .name       = "rocker-of-dpa-flows",
+        .args_type  = "name:s,tbl_id:i?",
+        .mhandler.cmd_new = qmp_marshal_input_rocker_of_dpa_flows,
+    },
+
+    {
+        .name       = "rocker-of-dpa-groups",
+        .args_type  = "name:s,type:i?",
+        .mhandler.cmd_new = qmp_marshal_input_rocker_of_dpa_groups,
+    },