diff mbox

[v5,07/10] qmp: add rocker device support

Message ID 1421913839-22448-8-git-send-email-sfeldma@gmail.com
State New
Headers show

Commit Message

Scott Feldman Jan. 22, 2015, 8:03 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) info rocker sw1
name: sw1
id: 0x0000013512005452
ports: 4

(qemu) info 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) info 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) info 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               |   24 ++++
 hmp.c                         |  303 ++++++++++++++++++++++++++++++++++++++++
 hmp.h                         |    4 +
 hw/net/rocker/rocker.c        |   45 ++++++
 hw/net/rocker/rocker_fp.c     |   10 ++
 hw/net/rocker/rocker_fp.h     |    1 +
 hw/net/rocker/rocker_of_dpa.c |  308 +++++++++++++++++++++++++++++++++++++++++
 monitor.c                     |   28 ++++
 qapi-schema.json              |    3 +
 qapi/rocker.json              |  259 ++++++++++++++++++++++++++++++++++
 qmp-commands.hx               |   97 +++++++++++++
 11 files changed, 1082 insertions(+)
 create mode 100644 qapi/rocker.json

Comments

Eric Blake Feb. 3, 2015, 3:10 p.m. UTC | #1
On 01/22/2015 01:03 AM, sfeldma@gmail.com wrote:
> 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:
> 

QMP interface review:

> +++ b/qapi-schema.json
> @@ -3523,3 +3523,6 @@
>  # Since: 2.1
>  ##
>  { 'command': 'rtc-reset-reinjection' }
> +
> +# Rocker ethernet network switch
> +{ 'include': 'qapi/rocker.json' }
> diff --git a/qapi/rocker.json b/qapi/rocker.json
> new file mode 100644
> index 0000000..326c6c7
> --- /dev/null
> +++ b/qapi/rocker.json
> @@ -0,0 +1,259 @@
> +##
> +# @Rocker:
> +#
> +# Rocker switch information.
> +#
> +# @name: switch name
> +#
> +# @id: switch ID
> +#
> +# @ports: number of front-panel ports
> +##

Missing a 'Since: 2.3' designation.

> +{ 'type': 'RockerSwitch',
> +  'data': { 'name': 'str', 'id': 'uint64', 'ports': 'uint32' } }
> +
> +##
> +# @rocker:
> +#
> +# Return rocker switch information.
> +#
> +# Returns: @Rocker information
> +#
> +# Since: 2.3
> +##
> +{ 'command': 'rocker',
> +  'data': { 'name': 'str' },
> +  'returns': 'RockerSwitch' }

Should this command be named 'query-rocker', as it is used for queries?
 Should the 'name' argument be optional, and the output be an array (all
rocker devices, rather than just a given rocker name lookup)?

> +
> +##
> +# @RockerPortDuplex:
> +#
> +# An eumeration of port duplex states.
> +#
> +# @half: half duplex
> +#
> +# @full: full duplex
> +##

Missing a 'Since: 2.3' designation.

> +{ 'enum': 'RockerPortDuplex', 'data': [ 'half', 'full' ] }
> +
> +##
> +# @RockerPortAutoneg:
> +#
> +# An eumeration of port autoneg states.
> +#
> +# @off: autoneg is off
> +#
> +# @on: autoneg is on
> +##

Missing a 'Since: 2.3' designation.

> +{ 'enum': 'RockerPortAutoneg', 'data': [ 'off', 'on' ] }
> +
> +##
> +# @RockerPort:
> +#
> +# Rocker switch port information.
> +#
> +# @name: port name
> +#
> +# @enabled: port is enabled for I/O
> +#
> +# @link-up: physical link is UP on port
> +#
> +# @speed: port link speed in Mbps
> +#
> +# @duplex: port link duplex
> +#
> +# @autoneg: port link autoneg
> +##

Missing a 'Since: 2.3' designation.

> +{ 'type': 'RockerPort',
> +  'data': { 'name': 'str', 'enabled': 'bool', 'link-up': 'bool',
> +            'speed': 'uint32', 'duplex': 'RockerPortDuplex',
> +            'autoneg': 'RockerPortAutoneg' } }
> +
> +##
> +# @rocker-ports:
> +#
> +# Return rocker switch information.
> +#
> +# Returns: @Rocker information
> +#
> +# Since: 2.3
> +##
> +{ 'command': 'rocker-ports',

Should this be named 'query-rocker-ports'?  Should the port information
be returned as part of the more generic 'rocker' command rather than
having to do a two-stage query (what are my rocker devices, then for
each device what are the ports)?

> +  'data': { 'name': 'str' },
> +  'returns': ['RockerPort'] }
> +
> +##
> +# @RockerOfDpaFlowKey:
> +#
> +# Rocker switch OF-DPA flow key
> +#
> +# @priority: key priority, 0 being lowest priority
> +#
> +# @tbl-id: flow table ID
> +#
> +# @in-pport: physical input port
> +#
> +# @tunnel-id: tunnel ID
> +#
> +# @vlan-id: VLAN ID
> +#
> +# @eth-type: Ethernet header type
> +#
> +# @eth-src: Ethernet header source MAC address
> +#
> +# @eth-dst: Ethernet header destination MAC address
> +#
> +# @ip-proto: IP Header protocol field
> +#
> +# @ip-tos: IP header TOS field
> +#
> +# @ip-dst: IP header destination address
> +##

Missing a 'Since: 2.3' designation.

> +{ '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' } }

Missing '#optional' tags on the various optional fields.  Why are
certain fields optional?  Does it mean they have a default value, or
that they don't make sense in some configurations?  The docs could be
more clear on that.

> +
> +##
> +# @RockerOfDpaFlowMask:
> +#
> +# Rocker switch OF-DPA flow mask
> +#
> +# @in-pport: physical input port
> +#
> +# @tunnel-id: tunnel ID
> +#
> +# @vlan-id: VLAN ID
> +#
> +# @eth-src: Ethernet header source MAC address
> +#
> +# @eth-dst: Ethernet header destination MAC address
> +#
> +# @ip-proto: IP Header protocol field
> +#
> +# @ip-tos: IP header TOS field
> +##

Missing a 'Since: 2.3' designation.

> +{ 'type': 'RockerOfDpaFlowMask',
> +  'data' : { '*in-pport': 'uint32', '*tunnel-id': 'uint32',
> +             '*vlan-id': 'uint16', '*eth-src': 'str', '*eth-dst': 'str',
> +             '*ip-proto': 'uint8', '*ip-tos': 'uint8' } }

Again, missing #optional tags in the docs, as well as what it means when
a field is omitted.

> +
> +##
> +# @RockerOfDpaFlowAction:
> +#
> +# Rocker switch OF-DPA flow action
> +#
> +# @goto-tbl: next table ID
> +#
> +# @group-id: group ID
> +#
> +# @tunnel-lport: tunnel logical port ID
> +#
> +# @vlan-id: VLAN ID
> +#
> +# @new-vlan-id: new VLAN ID
> +#
> +# @out-pport: physical output port
> +##

Missing a 'Since: 2.3' designation.

> +{ 'type': 'RockerOfDpaFlowAction',
> +  'data' : { '*goto-tbl': 'uint32', '*group-id': 'uint32',
> +             '*tunnel-lport': 'uint32', '*vlan-id': 'uint16',
> +             '*new-vlan-id': 'uint16', '*out-pport': 'uint32' } }

Repeat of #optional comments...

> +
> +##
> +# @RockerOfDpaFlow:
> +#
> +# Rocker switch OF-DPA flow
> +#
> +# @cookie: flow unique cookie ID
> +#
> +# @hits: count of matches (hits) on flow
> +#
> +# @key: flow key
> +#
> +# @mask: flow mask
> +#
> +# @action: flow action
> +#
> +# Since: 2.3
> +##
> +{ 'type': 'RockerOfDpaFlow',
> +  'data': { 'cookie': 'uint64', 'hits': 'uint64', 'key': 'RockerOfDpaFlowKey',
> +            'mask': 'RockerOfDpaFlowMask', 'action': 'RockerOfDpaFlowAction' } }
> +
> +##
> +# @rocker-of-dpa-flows:
> +#
> +# Return rocker OF-DPA flow information.
> +#
> +# @name: switch name
> +#
> +# @tbl-id: (optional) flow table ID.  If tbl-id is not specified, returns

s/(optional)/#optional/ for consistency with other .json files

> +# flow information for all tables.
> +#
> +# Returns: @Rocker OF-DPA flow information
> +#
> +# Since: 2.3
> +##
> +{ 'command': 'rocker-of-dpa-flows',

Should this command be in the query-* namespace?

> +  'data': { 'name': 'str', '*tbl-id': 'uint32' },
> +  'returns': ['RockerOfDpaFlow'] }
> +
> +##
> +# @RockerOfDpaGroup:
> +#
> +# Rocker switch OF-DPA group
> +#
> +# @id: group unique ID
> +#
> +# @type: group type
> +#
> +# @vlan-id: VLAN ID
> +#
> +# @pport: physical port number
> +#
> +# @index: group index, unique with group type
> +#
> +# @out-pport: output physical port number
> +#
> +# @group-id: next group ID
> +#
> +# @set-vlan-id: VLAN ID to set
> +#
> +# @pop-vlan: pop VLAN headr from packet
> +#
> +# @group-ids: list of next group IDs
> +#
> +# @set-eth-src: set source MAC address in Ethernet header
> +#
> +# @set-eth-dst: set destination MAC address in Ethernet header
> +#
> +# @ttl-check: perform TTL check
> +##

Missing a 'Since: 2.3' designation.

> +{ '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' } }

Again on #optional...

> +
> +##
> +# @rocker-of-dpa-groups:
> +#
> +# Return rocker OF-DPA group information.
> +#
> +# @name: switch name
> +#
> +# @type: (optional) group type.  If type is not specified, returns
> +# group information for all group types.
> +#
> +# Returns: @Rocker OF-DPA group information
> +#
> +# Since: 2.3
> +##
> +{ 'command': 'rocker-of-dpa-groups',
> +  'data': { 'name': 'str', '*type': 'uint8' },
> +  'returns': ['RockerOfDpaGroup'] }
> +
> diff --git a/qmp-commands.hx b/qmp-commands.hx
> index 8957201..9007c12 100644
> --- a/qmp-commands.hx
> +++ b/qmp-commands.hx
> @@ -3902,3 +3902,100 @@ Move mouse pointer to absolute coordinates (20000, 400).
>  <- { "return": {} }
>  
>  EQMP
> +
> +    {
> +        .name       = "query-rocker",

Doesn't match the naming in your .json file (but I do like query-rocker
better).

> +        .args_type  = "name:s",
> +        .mhandler.cmd_new = qmp_marshal_input_rocker,
> +    },
> +
> +SQMP
> +Show rocker switch
> +------------------
> +
> +Arguments:
> +
> +- "name": switch name
> +
> +Example:
> +
> +-> { "execute": "query-rocker", "arguments": { "name": "sw1" } }
> +<- { "return": {"name": "sw1", "ports": 2, "id": 1327446905938}}

Is there a command to learn which switch names exist?  As written, this
command assumes you already know the switch name, which makes the switch
name on output a bit redundant.  My suggestion above was to make 'name'
be optional on input, and return an array on output.

> +
> +EQMP
> +
> +    {
> +        .name       = "query-rocker-ports",

Doesn't match .json naming.

> +        .args_type  = "name:s",
> +        .mhandler.cmd_new = qmp_marshal_input_rocker_ports,
> +    },
> +
> +SQMP
> +Show rocker switch ports
> +------------------------
> +
> +Arguments:
> +
> +- "name": switch name
> +
> +Example:
> +
> +-> { "execute": "query-rocker-ports", "arguments": { "name": "sw1" } }
> +<- { "return": [ {"duplex": "full", "enabled": true, "name": "sw1.1", "autoneg": "off", "link-up": true, "speed": 10000},
> +                 {"duplex": "full", "enabled": true, "name": "sw1.2", "autoneg": "off", "link-up": true, "speed": 10000}
> +   ]}
> +
> +EQMP
> +
> +    {
> +        .name       = "query-rocker-of-dpa-flows",

Another naming mismatch.

> +        .args_type  = "name:s,tbl-id:i?",
> +        .mhandler.cmd_new = qmp_marshal_input_rocker_of_dpa_flows,
> +    },
> +
> +SQMP
> +Show rocker switch OF-DPA flow tables
> +-------------------------------------
> +
> +Arguments:
> +
> +- "name": switch name
> +- "tbl-id": (optional) flow table ID
> +
> +Example:
> +
> +-> { "execute": "query-rocker-of-dpa-flows", "arguments": { "name": "sw1" } }
> +<- { "return": [ {"key": {"in-pport": 0, "priority": 1, "tbl-id": 0},
> +                  "hits": 138,
> +                  "cookie": 0,
> +                  "action": {"goto-tbl": 10},
> +                  "mask": {"in-pport": 4294901760}
> +                 },
> +                 {...more...},
> +   ]}
> +
> +EQMP
> +
> +    {
> +        .name       = "query-rocker-of-dpa-groups",

and again

> +        .args_type  = "name:s,type:i?",
> +        .mhandler.cmd_new = qmp_marshal_input_rocker_of_dpa_groups,
> +    },
> +
> +SQMP
> +Show rocker OF-DPA group tables
> +-------------------------------
> +
> +Arguments:
> +
> +- "name": switch name
> +- "type": (optional) group type
> +
> +Example:
> +
> +-> { "execute": "query-rocker-of-dpa-groups", "arguments": { "name": "sw1" } }
> +<- { "return": [ {"type": 0, "out-pport": 2, "pport": 2, "vlan-id": 3841, "pop-vlan": 1, "id": 251723778},
> +                 {"type": 0, "out-pport": 0, "pport": 0, "vlan-id": 3841, "pop-vlan": 1, "id": 251723776},
> +                 {"type": 0, "out-pport": 1, "pport": 1, "vlan-id": 3840, "pop-vlan": 1, "id": 251658241},
> +                 {"type": 0, "out-pport": 0, "pport": 0, "vlan-id": 3840, "pop-vlan": 1, "id": 251658240}
> +   ]}
>
Scott Feldman Feb. 5, 2015, 6:10 a.m. UTC | #2
On Tue, Feb 3, 2015 at 7:10 AM, Eric Blake <eblake@redhat.com> wrote:
> On 01/22/2015 01:03 AM, sfeldma@gmail.com wrote:
>> 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:
>>
>
> QMP interface review:
>
>> +++ b/qapi-schema.json
>> @@ -3523,3 +3523,6 @@
>>  # Since: 2.1
>>  ##
>>  { 'command': 'rtc-reset-reinjection' }
>> +
>> +# Rocker ethernet network switch
>> +{ 'include': 'qapi/rocker.json' }
>> diff --git a/qapi/rocker.json b/qapi/rocker.json
>> new file mode 100644
>> index 0000000..326c6c7
>> --- /dev/null
>> +++ b/qapi/rocker.json
>> @@ -0,0 +1,259 @@
>> +##
>> +# @Rocker:
>> +#
>> +# Rocker switch information.
>> +#
>> +# @name: switch name
>> +#
>> +# @id: switch ID
>> +#
>> +# @ports: number of front-panel ports
>> +##
>
> Missing a 'Since: 2.3' designation.

fixed all of these

>
>> +{ 'type': 'RockerSwitch',
>> +  'data': { 'name': 'str', 'id': 'uint64', 'ports': 'uint32' } }
>> +
>> +##
>> +# @rocker:
>> +#
>> +# Return rocker switch information.
>> +#
>> +# Returns: @Rocker information
>> +#
>> +# Since: 2.3
>> +##
>> +{ 'command': 'rocker',
>> +  'data': { 'name': 'str' },
>> +  'returns': 'RockerSwitch' }
>
> Should this command be named 'query-rocker', as it is used for queries?

added "query-" to all cmds.

>  Should the 'name' argument be optional, and the output be an array (all
> rocker devices, rather than just a given rocker name lookup)?

we can add a query-rockers cmds later to get list of rocker names (or
list of RockerSwitch'es).

>
> Missing '#optional' tags on the various optional fields.  Why are
> certain fields optional?  Does it mean they have a default value, or
> that they don't make sense in some configurations?  The docs could be
> more clear on that.

Added #optional for the optional fields and added comment for each
section that these are fields that may or may not be present depending
on context of object being returned.
diff mbox

Patch

diff --git a/hmp-commands.hx b/hmp-commands.hx
index e37bc8b..6873a3a 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -1787,5 +1787,29 @@  show available trace events and their state
 ETEXI
 
 STEXI
+@item rocker @var{name}
+@findex rocker
+Show Rocker(s)
+ETEXI
+
+STEXI
+@item rocker_ports @var{name}
+@findex rocker_ports
+Show Rocker ports
+ETEXI
+
+STEXI
+@item rocker_of_dpa_flows @var{name} [@var{tbl_id}]
+@findex rocker_of_dpa_flows
+Show Rocker OF-DPA flow tables
+ETEXI
+
+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..949d43c 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");
+    RockerSwitch *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_RockerSwitch(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 d8a1a8e..2cfb975 100644
--- a/hw/net/rocker/rocker.c
+++ b/hw/net/rocker/rocker.c
@@ -94,6 +94,51 @@  World *rocker_get_world(Rocker *r, enum rocker_world_type type)
     return NULL;
 }
 
+RockerSwitch *qmp_rocker(const char *name, Error **errp)
+{
+    RockerSwitch *rocker = g_malloc0(sizeof(*rocker));
+    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;
+    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(Rocker *r)
 {
     return r->fp_ports;
diff --git a/hw/net/rocker/rocker_fp.c b/hw/net/rocker/rocker_fp.c
index 8d41190..0bcc93c 100644
--- a/hw/net/rocker/rocker_fp.c
+++ b/hw/net/rocker/rocker_fp.c
@@ -46,6 +46,16 @@  bool fp_port_get_link_up(FpPort *port)
     return !qemu_get_queue(port->nic)->link_down;
 }
 
+void fp_port_get_info(FpPort *port, RockerPortList *info)
+{
+    info->value->name = g_strdup(port->name);
+    info->value->enabled = port->enabled;
+    info->value->link_up = fp_port_get_link_up(port);
+    info->value->speed = port->speed;
+    info->value->duplex = port->duplex;
+    info->value->autoneg = port->autoneg;
+}
+
 void fp_port_get_macaddr(FpPort *port, MACAddr *macaddr)
 {
     memcpy(macaddr->a, port->conf.macaddr.a, sizeof(macaddr->a));
diff --git a/hw/net/rocker/rocker_fp.h b/hw/net/rocker/rocker_fp.h
index f827f8d..564c4ce 100644
--- a/hw/net/rocker/rocker_fp.h
+++ b/hw/net/rocker/rocker_fp.h
@@ -29,6 +29,7 @@  typedef struct world World;
 int fp_port_eg(FpPort *port, const struct iovec *iov, int iovcnt);
 
 bool fp_port_get_link_up(FpPort *port);
+void fp_port_get_info(FpPort *port, RockerPortList *info);
 void fp_port_get_macaddr(FpPort *port, MACAddr *macaddr);
 void fp_port_set_macaddr(FpPort *port, MACAddr *macaddr);
 uint8_t fp_port_get_learning(FpPort *port);
diff --git a/hw/net/rocker/rocker_of_dpa.c b/hw/net/rocker/rocker_of_dpa.c
index 1cf7e4e..3c14e3f 100644
--- a/hw/net/rocker/rocker_of_dpa.c
+++ b/hw/net/rocker/rocker_of_dpa.c
@@ -2301,6 +2301,314 @@  static void of_dpa_uninit(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 WorldOps of_dpa_ops = {
     .init = of_dpa_init,
     .uninit = of_dpa_uninit,
diff --git a/monitor.c b/monitor.c
index 7e4f605..79b8ebf 100644
--- a/monitor.c
+++ b/monitor.c
@@ -2935,6 +2935,34 @@  static mon_cmd_t info_cmds[] = {
         .mhandler.cmd = hmp_info_memory_devices,
     },
     {
+        .name       = "rocker",
+        .args_type  = "name:s",
+        .params     = "name",
+        .help       = "Show rocker switch",
+        .mhandler.cmd = hmp_rocker,
+    },
+    {
+        .name       = "rocker-ports",
+        .args_type  = "name:s",
+        .params     = "name",
+        .help       = "Show rocker ports",
+        .mhandler.cmd = hmp_rocker_ports,
+    },
+    {
+        .name       = "rocker-of-dpa-flows",
+        .args_type  = "name:s,tbl_id:i?",
+        .params     = "name [tbl_id]",
+        .help       = "Show rocker OF-DPA flow tables",
+        .mhandler.cmd = hmp_rocker_of_dpa_flows,
+    },
+    {
+        .name       = "rocker-of-dpa-groups",
+        .args_type  = "name:s,type:i?",
+        .params     = "name [type]",
+        .help       = "Show rocker OF-DPA groups",
+        .mhandler.cmd = hmp_rocker_of_dpa_groups,
+    },
+    {
         .name       = NULL,
     },
 };
diff --git a/qapi-schema.json b/qapi-schema.json
index fbfc52f..c6d04ca 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3523,3 +3523,6 @@ 
 # Since: 2.1
 ##
 { 'command': 'rtc-reset-reinjection' }
+
+# Rocker ethernet network switch
+{ 'include': 'qapi/rocker.json' }
diff --git a/qapi/rocker.json b/qapi/rocker.json
new file mode 100644
index 0000000..326c6c7
--- /dev/null
+++ b/qapi/rocker.json
@@ -0,0 +1,259 @@ 
+##
+# @Rocker:
+#
+# Rocker switch information.
+#
+# @name: switch name
+#
+# @id: switch ID
+#
+# @ports: number of front-panel ports
+##
+{ 'type': 'RockerSwitch',
+  'data': { 'name': 'str', 'id': 'uint64', 'ports': 'uint32' } }
+
+##
+# @rocker:
+#
+# Return rocker switch information.
+#
+# Returns: @Rocker information
+#
+# Since: 2.3
+##
+{ 'command': 'rocker',
+  'data': { 'name': 'str' },
+  'returns': 'RockerSwitch' }
+
+##
+# @RockerPortDuplex:
+#
+# An eumeration of port duplex states.
+#
+# @half: half duplex
+#
+# @full: full duplex
+##
+{ 'enum': 'RockerPortDuplex', 'data': [ 'half', 'full' ] }
+
+##
+# @RockerPortAutoneg:
+#
+# An eumeration of port autoneg states.
+#
+# @off: autoneg is off
+#
+# @on: autoneg is on
+##
+{ 'enum': 'RockerPortAutoneg', 'data': [ 'off', 'on' ] }
+
+##
+# @RockerPort:
+#
+# Rocker switch port information.
+#
+# @name: port name
+#
+# @enabled: port is enabled for I/O
+#
+# @link-up: physical link is UP on port
+#
+# @speed: port link speed in Mbps
+#
+# @duplex: port link duplex
+#
+# @autoneg: port link autoneg
+##
+{ 'type': 'RockerPort',
+  'data': { 'name': 'str', 'enabled': 'bool', 'link-up': 'bool',
+            'speed': 'uint32', 'duplex': 'RockerPortDuplex',
+            'autoneg': 'RockerPortAutoneg' } }
+
+##
+# @rocker-ports:
+#
+# Return rocker switch information.
+#
+# Returns: @Rocker information
+#
+# Since: 2.3
+##
+{ 'command': 'rocker-ports',
+  'data': { 'name': 'str' },
+  'returns': ['RockerPort'] }
+
+##
+# @RockerOfDpaFlowKey:
+#
+# Rocker switch OF-DPA flow key
+#
+# @priority: key priority, 0 being lowest priority
+#
+# @tbl-id: flow table ID
+#
+# @in-pport: physical input port
+#
+# @tunnel-id: tunnel ID
+#
+# @vlan-id: VLAN ID
+#
+# @eth-type: Ethernet header type
+#
+# @eth-src: Ethernet header source MAC address
+#
+# @eth-dst: Ethernet header destination MAC address
+#
+# @ip-proto: IP Header protocol field
+#
+# @ip-tos: IP header TOS field
+#
+# @ip-dst: IP header destination address
+##
+{ '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' } }
+
+##
+# @RockerOfDpaFlowMask:
+#
+# Rocker switch OF-DPA flow mask
+#
+# @in-pport: physical input port
+#
+# @tunnel-id: tunnel ID
+#
+# @vlan-id: VLAN ID
+#
+# @eth-src: Ethernet header source MAC address
+#
+# @eth-dst: Ethernet header destination MAC address
+#
+# @ip-proto: IP Header protocol field
+#
+# @ip-tos: IP header TOS field
+##
+{ 'type': 'RockerOfDpaFlowMask',
+  'data' : { '*in-pport': 'uint32', '*tunnel-id': 'uint32',
+             '*vlan-id': 'uint16', '*eth-src': 'str', '*eth-dst': 'str',
+             '*ip-proto': 'uint8', '*ip-tos': 'uint8' } }
+
+##
+# @RockerOfDpaFlowAction:
+#
+# Rocker switch OF-DPA flow action
+#
+# @goto-tbl: next table ID
+#
+# @group-id: group ID
+#
+# @tunnel-lport: tunnel logical port ID
+#
+# @vlan-id: VLAN ID
+#
+# @new-vlan-id: new VLAN ID
+#
+# @out-pport: physical output port
+##
+{ 'type': 'RockerOfDpaFlowAction',
+  'data' : { '*goto-tbl': 'uint32', '*group-id': 'uint32',
+             '*tunnel-lport': 'uint32', '*vlan-id': 'uint16',
+             '*new-vlan-id': 'uint16', '*out-pport': 'uint32' } }
+
+##
+# @RockerOfDpaFlow:
+#
+# Rocker switch OF-DPA flow
+#
+# @cookie: flow unique cookie ID
+#
+# @hits: count of matches (hits) on flow
+#
+# @key: flow key
+#
+# @mask: flow mask
+#
+# @action: flow action
+#
+# Since: 2.3
+##
+{ 'type': 'RockerOfDpaFlow',
+  'data': { 'cookie': 'uint64', 'hits': 'uint64', 'key': 'RockerOfDpaFlowKey',
+            'mask': 'RockerOfDpaFlowMask', 'action': 'RockerOfDpaFlowAction' } }
+
+##
+# @rocker-of-dpa-flows:
+#
+# Return rocker OF-DPA flow information.
+#
+# @name: switch name
+#
+# @tbl-id: (optional) flow table ID.  If tbl-id is not specified, returns
+# flow information for all tables.
+#
+# Returns: @Rocker OF-DPA flow information
+#
+# Since: 2.3
+##
+{ 'command': 'rocker-of-dpa-flows',
+  'data': { 'name': 'str', '*tbl-id': 'uint32' },
+  'returns': ['RockerOfDpaFlow'] }
+
+##
+# @RockerOfDpaGroup:
+#
+# Rocker switch OF-DPA group
+#
+# @id: group unique ID
+#
+# @type: group type
+#
+# @vlan-id: VLAN ID
+#
+# @pport: physical port number
+#
+# @index: group index, unique with group type
+#
+# @out-pport: output physical port number
+#
+# @group-id: next group ID
+#
+# @set-vlan-id: VLAN ID to set
+#
+# @pop-vlan: pop VLAN headr from packet
+#
+# @group-ids: list of next group IDs
+#
+# @set-eth-src: set source MAC address in Ethernet header
+#
+# @set-eth-dst: set destination MAC address in Ethernet header
+#
+# @ttl-check: perform TTL check
+##
+{ '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' } }
+
+##
+# @rocker-of-dpa-groups:
+#
+# Return rocker OF-DPA group information.
+#
+# @name: switch name
+#
+# @type: (optional) group type.  If type is not specified, returns
+# group information for all group types.
+#
+# Returns: @Rocker OF-DPA group information
+#
+# Since: 2.3
+##
+{ 'command': 'rocker-of-dpa-groups',
+  'data': { 'name': 'str', '*type': 'uint8' },
+  'returns': ['RockerOfDpaGroup'] }
+
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 8957201..9007c12 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -3902,3 +3902,100 @@  Move mouse pointer to absolute coordinates (20000, 400).
 <- { "return": {} }
 
 EQMP
+
+    {
+        .name       = "query-rocker",
+        .args_type  = "name:s",
+        .mhandler.cmd_new = qmp_marshal_input_rocker,
+    },
+
+SQMP
+Show rocker switch
+------------------
+
+Arguments:
+
+- "name": switch name
+
+Example:
+
+-> { "execute": "query-rocker", "arguments": { "name": "sw1" } }
+<- { "return": {"name": "sw1", "ports": 2, "id": 1327446905938}}
+
+EQMP
+
+    {
+        .name       = "query-rocker-ports",
+        .args_type  = "name:s",
+        .mhandler.cmd_new = qmp_marshal_input_rocker_ports,
+    },
+
+SQMP
+Show rocker switch ports
+------------------------
+
+Arguments:
+
+- "name": switch name
+
+Example:
+
+-> { "execute": "query-rocker-ports", "arguments": { "name": "sw1" } }
+<- { "return": [ {"duplex": "full", "enabled": true, "name": "sw1.1", "autoneg": "off", "link-up": true, "speed": 10000},
+                 {"duplex": "full", "enabled": true, "name": "sw1.2", "autoneg": "off", "link-up": true, "speed": 10000}
+   ]}
+
+EQMP
+
+    {
+        .name       = "query-rocker-of-dpa-flows",
+        .args_type  = "name:s,tbl-id:i?",
+        .mhandler.cmd_new = qmp_marshal_input_rocker_of_dpa_flows,
+    },
+
+SQMP
+Show rocker switch OF-DPA flow tables
+-------------------------------------
+
+Arguments:
+
+- "name": switch name
+- "tbl-id": (optional) flow table ID
+
+Example:
+
+-> { "execute": "query-rocker-of-dpa-flows", "arguments": { "name": "sw1" } }
+<- { "return": [ {"key": {"in-pport": 0, "priority": 1, "tbl-id": 0},
+                  "hits": 138,
+                  "cookie": 0,
+                  "action": {"goto-tbl": 10},
+                  "mask": {"in-pport": 4294901760}
+                 },
+                 {...more...},
+   ]}
+
+EQMP
+
+    {
+        .name       = "query-rocker-of-dpa-groups",
+        .args_type  = "name:s,type:i?",
+        .mhandler.cmd_new = qmp_marshal_input_rocker_of_dpa_groups,
+    },
+
+SQMP
+Show rocker OF-DPA group tables
+-------------------------------
+
+Arguments:
+
+- "name": switch name
+- "type": (optional) group type
+
+Example:
+
+-> { "execute": "query-rocker-of-dpa-groups", "arguments": { "name": "sw1" } }
+<- { "return": [ {"type": 0, "out-pport": 2, "pport": 2, "vlan-id": 3841, "pop-vlan": 1, "id": 251723778},
+                 {"type": 0, "out-pport": 0, "pport": 0, "vlan-id": 3841, "pop-vlan": 1, "id": 251723776},
+                 {"type": 0, "out-pport": 1, "pport": 1, "vlan-id": 3840, "pop-vlan": 1, "id": 251658241},
+                 {"type": 0, "out-pport": 0, "pport": 0, "vlan-id": 3840, "pop-vlan": 1, "id": 251658240}
+   ]}