import argparse
import gdb
from . import autocompeletion, utils
from .lists import NxList
RPMSG_HOST = 0
RPMSG_DEVICE = 1
CONFIG_RPMSG = utils.lookup_type("struct rpmsg_s") is not None
@autocompeletion.complete
class RPMsgDump(gdb.Command):
"""Dump rpmsg service"""
CALLBACK_HEADER = ["rpmsg_cb_s at", "ns_match", "ns_bind"]
ENDPOINT_HEADER = [
"name",
"addr",
"dest",
"cb",
"ns_bound_cb",
"ns_unbind_cb",
"endpoint",
]
VQ_HEADER = [
"vq",
"name",
"size",
"free",
"queued",
"desc_head_idx",
"available_idx",
"avail.idx",
"avail.flags",
"used_cons_idx",
"used.idx",
"used.flags",
]
CALLBACK_FORAMTTER = "{:<20} {:<40} {:<40}"
ENDPOINT_FORMATTER = "{:<24} {:<5} {:<11} {:<40} {:<40} {:<40} {:<20}"
VQ_FORMATTER = (
"{:<12} {:<8} {:<5} {:<5} {:<7} {:<14} {:<14} {:<10} {:<12} {:<14} {:<9} {:<11}"
)
def get_argparser(self):
parser = argparse.ArgumentParser(description=self.__doc__)
parser.add_argument(
"-f",
"--full",
action="store_true",
help="Dump the rpmsg information fullly",
)
return parser
def __init__(self):
if CONFIG_RPMSG:
super(RPMsgDump, self).__init__("rpmsgdump", gdb.COMMAND_USER)
self.parser = self.get_argparser()
def parse_args(self, arg):
try:
return self.parser.parse_args(gdb.string_to_argv(arg))
except SystemExit:
return
def is_rpmsg_transport(self, rdev, transport):
if transport == "virtio":
tx_payload = "rpmsg_virtio_get_tx_payload_buffer"
elif transport == "port":
tx_payload = "rpmsg_port_get_tx_payload_buffer"
else:
return False
symbol = gdb.lookup_symbol(tx_payload)
if symbol[0] is None:
return False
real_addr = symbol[0].value().address
ops_addr = int(utils.Value(rdev["ops"]["get_tx_payload_buffer"])) & ~1
return real_addr == ops_addr
def dump_virtqueue(self, vq):
vr = vq["vq_ring"]
gdb.write(
self.VQ_FORMATTER.format(
f"{vq}",
f"{vq['vq_name'].string()}",
f"{vq['vq_nentries']}",
f"{vq['vq_free_cnt']}",
f"{vq['vq_queued_cnt']}",
f"{vq['vq_desc_head_idx']}",
f"{vq['vq_available_idx']}",
f"{vr['avail']['idx']}",
f"{vr['avail']['flags']}",
f"{vq['vq_used_cons_idx']}",
f"{vr['used']['idx']}",
f"{vr['used']['flags']}",
)
+ "\n"
)
def rpmsg_virtio_buffer_nused(self, rvdev, rx):
vq = rvdev["rvq"] if rx else rvdev["svq"]
is_host = rvdev["vdev"]["role"] == RPMSG_HOST
nused = vq["vq_ring"]["avail"]["idx"] - vq["vq_ring"]["used"]["idx"]
if is_host ^ rx:
return nused
else:
return vq["vq_nentries"] - nused
def dump_rpmsg_virtio_buffer(self, rvdev, rx):
vq = rvdev["rvq"] if rx else rvdev["svq"]
nused = self.rpmsg_virtio_buffer_nused(rvdev, rx)
gdb.write(
f" {'RX' if rx else 'Tx'} buffer, total {vq['vq_nentries']}, pending {nused}\n"
)
for i in range(nused):
if (rvdev["vdev"]["role"] == RPMSG_HOST) ^ rx:
desc_idx = (vq["vq_ring"]["used"]["idx"] + i) & (vq["vq_nentries"] - 1)
desc_idx = vq["vq_ring"]["avail"]["ring"][desc_idx]
else:
desc_idx = (vq["vq_ring"]["avail"]["idx"] + i) & (vq["vq_nentries"] - 1)
desc_idx = vq["vq_ring"]["used"]["ring"][desc_idx]["id"]
addr = vq["vq_ring"]["desc"][desc_idx]["addr"]
if addr:
hdr = addr.cast(utils.lookup_type("struct rpmsg_hdr").pointer())
ept = self.rpmsg_get_ept_from_addr(
rvdev["rdev"], hdr["dst"] if rx else hdr["src"]
)
if ept:
gdb.write(
f" {'RX' if rx else 'TX'} buffer:{addr} held by {ept['name']}\n"
)
def dump_rpmsg_virtio(self, rdev):
if not self.is_rpmsg_transport(rdev, "virtio"):
return
rvdev = rdev.cast(utils.lookup_type("struct rpmsg_virtio_device").pointer())
gdb.write("Rpmsg VirtIO Transport:\n")
gdb.write(
f"rvdev:{rvdev} h2r_buf_size:{rvdev['config']['h2r_buf_size']} "
f"r2h_buf_size:{rvdev['config']['r2h_buf_size']}\n"
)
for vbuff in NxList(rvdev["reclaimer"], "struct vbuff_reclaimer_t", "node"):
gdb.write(f"rvdev reclaimer vbuff:{vbuff} idx:{vbuff['idx']}")
gdb.write("Rpmsg Virtqueues:\n")
self.print_headers(self.VQ_HEADER, self.VQ_FORMATTER)
try:
self.dump_virtqueue(rvdev["svq"])
self.dump_virtqueue(rvdev["rvq"])
gdb.write(" rpmsg buffer list:\n")
self.dump_rpmsg_virtio_buffer(rvdev, True)
self.dump_rpmsg_virtio_buffer(rvdev, False)
except gdb.error as e:
gdb.write(f"Error when dump virtqueues: {e}\n")
gdb.write("\n")
def rpmsg_get_ept_from_addr(self, rdev, addr):
endpoints = rdev["endpoints"]
for endpoint in NxList(endpoints, "struct rpmsg_endpoint", "node"):
ept_addr = endpoint["addr"]
if ept_addr == addr:
return endpoint
return None
def rpmsg_port_node_to_buf(self, queue, node):
node_offset = (int(node) - int(queue["node"])) / int(
utils.sizeof("struct list_node")
)
buf_addr = queue["buf"] + (int(node_offset) * queue["len"])
return buf_addr
def dump_rpmsg_port_buffer(self, rdev, queue, label):
buffer_list = []
head = queue["ready"]["head"]
for node in NxList(head):
hdr = self.rpmsg_port_node_to_buf(queue, node)
hdr = hdr.cast(utils.lookup_type("struct rpmsg_port_header_s").pointer())
if not hdr or not hdr["buf"]:
continue
rphdr = hdr["buf"].cast(utils.lookup_type("struct rpmsg_hdr").pointer())
ept_addr = rphdr["dst"] if label == "RX" else rphdr["src"]
ept = self.rpmsg_get_ept_from_addr(rdev, ept_addr)
if ept:
ept_name = ept["name"].string().split("\0", 1)[0]
buffer_list.append(f"{label} buffer:{rphdr} held by {ept_name}\n")
return buffer_list
def dump_rpmsg_port(self, rdev):
if not self.is_rpmsg_transport(rdev, "port"):
return
port = utils.container_of(rdev, "struct rpmsg_port_s", "rdev")
gdb.write(f"rxq nused:{port['rxq']['ready']['num']}\n")
gdb.write(f"rxq navail:{port['rxq']['free']['num']}\n")
gdb.write(f"txq nused:{port['txq']['ready']['num']}\n")
gdb.write(f"txq navail:{port['txq']['free']['num']}\n")
rx_buffers = self.dump_rpmsg_port_buffer(rdev, port["rxq"], "RX")
tx_buffers = self.dump_rpmsg_port_buffer(rdev, port["txq"], "TX")
for buffer in rx_buffers + tx_buffers:
gdb.write(buffer + "\n")
def print_headers(self, headers, formatter):
gdb.write(formatter.format(*headers) + "\n")
gdb.write(formatter.format(*["-" * len(header) for header in headers]) + "\n")
def dump_rdev_epts(self, endpoints_head):
gdb.write("Rpmsg Device Endpoints:\n")
gdb.write(f"endpoints list:{endpoints_head}\n")
self.print_headers(self.ENDPOINT_HEADER, self.ENDPOINT_FORMATTER)
output = []
for endpoint in NxList(endpoints_head, "struct rpmsg_endpoint", "node"):
output.append(
self.ENDPOINT_FORMATTER.format(
f"{endpoint['name'].string()}",
f"{endpoint['addr']}",
f"{endpoint['dest_addr']}",
f"{endpoint['cb']}",
f"{endpoint['ns_bound_cb']}",
f"{endpoint['ns_unbind_cb']}",
f"{endpoint}",
)
)
gdb.write("\n".join(output) + "\n")
def dump_rdev_bitmap(self, rdev):
bitmap_values = [hex(bit) for bit in utils.ArrayIterator(rdev["bitmap"])]
gdb.write(
f"bitmap: {' '.join(bitmap_values):<20} bitnext: {hex(rdev['bitnext'])}\n"
)
def dump_rdev(self, rdev):
gdb.write("Rpmsg Device:\n")
gdb.write(f"rdev:{rdev}\n")
self.dump_rdev_bitmap(rdev)
self.dump_rdev_epts(rdev["endpoints"])
def dump_rpmsg_cb(self):
gdb.write("Rpmsg Callback:\n")
self.print_headers(self.CALLBACK_HEADER, self.CALLBACK_FORAMTTER)
output = []
for cb in NxList(
utils.parse_and_eval("g_rpmsg_cb"), "struct rpmsg_cb_s", "node"
):
output.append(
self.CALLBACK_FORAMTTER.format(
str(cb), str(cb["ns_match"]), str(cb["ns_bind"])
)
)
gdb.write("\n".join(output) + "\n")
def dump_rpmsg(self, args):
for rpmsg in NxList(utils.parse_and_eval("g_rpmsg"), "struct rpmsg_s", "node"):
rdev = utils.Value(int(rpmsg) + utils.sizeof("struct rpmsg_s"))
rdev = rdev.cast(utils.lookup_type("struct rpmsg_device").pointer())
gdb.write(
f"Rpmsg:\n"
f"rpmsg:{rpmsg} localcpu:{rpmsg['local_cpuname']} remotecpu:{rpmsg['cpuname']}\n"
)
if args.full:
self.dump_rdev(rdev)
self.dump_rpmsg_virtio(rdev)
self.dump_rpmsg_port(rdev)
@utils.dont_repeat_decorator
def invoke(self, args, from_tty):
if not (args := self.parse_args(args)):
return
if args.full:
self.dump_rpmsg_cb()
self.dump_rpmsg(args)
def diagnose(self, *args, **kwargs):
return {
"title": "RPMSG Report",
"summary": "RPMSG report",
"command": "rpmsgdump",
"result": "info",
"category": utils.DiagnoseCategory.rpc,
"message": gdb.execute("rpmsgdump", to_string=True),
}