diff --git a/man/man8/ip-link.8.in b/man/man8/ip-link.8.in
index 8f07de9a8a25..cbf6d9a9d9c4 100644
--- a/man/man8/ip-link.8.in
+++ b/man/man8/ip-link.8.in
@@ -1611,6 +1611,7 @@ For a link of type
 the following additional arguments are supported:
 
 .BI "ip link add " DEVICE " type bridge "
+.\" bridge man doc starts
 [
 .BI ageing_time " AGEING_TIME "
 ] [
@@ -1887,6 +1888,7 @@ arptables hooks on the bridge.
 
 
 .in -8
+.\" bridge man doc ends
 
 .TP
 MACsec Type Support
diff --git a/tools/generate_bridge_man.py b/tools/generate_bridge_man.py
new file mode 100755
index 000000000000..45947abda382
--- /dev/null
+++ b/tools/generate_bridge_man.py
@@ -0,0 +1,188 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+
+import re, sys, os
+import argparse
+from docutils import core
+
+man_bridge = ""
+
+def handle_args():
+    parser = argparse.ArgumentParser(description="""Convert bridge header
+                                     comments to iproute2 man doc.""")
+    parser.add_argument('-i', '--iproute2-dir', required=True,
+                        help='iproute code path')
+    parser.add_argument('-n', '--net-dir', required=True,
+                        help='net-next code path')
+    args = parser.parse_args()
+    return args
+
+def write_man_doc(input_file, output_file):
+    try:
+        with open(input_file, 'r') as infile, open(output_file, 'w') as outfile:
+            remove_flag = False
+            for line in infile:
+                if line.find("bridge man doc starts") != -1:
+                    remove_flag = True
+                    outfile.write(line)
+                    outfile.write(man_bridge + '\n')
+                elif remove_flag and line.find("bridge man doc ends") != -1:
+                    remove_flag = False
+                elif not remove_flag:
+                    outfile.write(line)
+    except Exception as e:
+        sys.exit(e)
+
+def append_man_line(line, end = '\n'):
+    global man_bridge
+    man_bridge = man_bridge + line + end
+
+def rst2man(node):
+    if not hasattr(node, 'astext'):
+        return
+
+    if node.tagname == 'bullet_list':
+        append_man_line('.in +8\n.sp')
+    if node.tagname == 'list_item':
+        append_man_line('')
+    elif node.tagname == 'emphasis':
+        append_man_line('.I ', end = '')
+    elif node.tagname == 'strong':
+        append_man_line('.B ', end = '')
+
+    for child_node in node.children:
+        rst2man(child_node)
+
+    if len(node.children) == 0:
+        text = node.astext().strip()
+        append_man_line(text)
+
+    if node.tagname == 'bullet_list':
+        append_man_line('.in -8')
+
+def convert2man(params):
+    first = True
+    for p in params:
+        if first:
+            first = False
+            append_man_line('[')
+        else:
+            append_man_line(' [')
+        append_man_line('.BI ' + p + ' " ' + params[p]['param'] + ' "')
+        append_man_line(']', end = '')
+
+    append_man_line('\n\n.in +8\n.sp')
+
+    for p in params:
+        if 'alias' in params[p]:
+            # TODO: fix the vlan_protocol output, e.g. {|}
+            append_man_line('\n.BR ' + p + ' " ' + params[p]['alias'] + ' "')
+        else:
+            append_man_line('\n.BI ' + p + ' " ' + params[p]['param'] + ' "')
+        # Add the '- ' at begining of each description
+        append_man_line('- ', end = '')
+        if 'doc' in params[p]:
+            rst_nodes = core.publish_doctree(params[p]['doc'])
+            rst2man(rst_nodes)
+
+def strip_doc_line(line, param):
+    # Remove the first " *   "
+    line = line[5:-1]
+    # replace attr with param, e.g. IFLA_BR_STP_STATE -> STP_STATE
+    line = line.replace(param['attr'], param['param'])
+    return line
+
+def get_attr(line):
+    match = re.search(r'@(\w+):', line)
+    if match:
+        return match.group(1)
+    return None
+
+# Deal some irregular namings
+def param2attr(param):
+    param = param.replace('count', 'cnt')
+    param = param.replace('interval', 'intvl')
+    param = param.replace('group_address', 'group_addr')
+    return param.upper()
+
+def get_bridge_doc(bridge_header, params):
+    find_attr = False
+    p = None
+    try:
+        with open(bridge_header, 'r') as f:
+            line = f.readline()
+            # Find the start of the doc
+            while line.find("DOC: The bridge emum defination") == -1:
+                if not line:
+                    print("Unable to find bridge DOC");
+                    return None
+                line = f.readline()
+            # Till the end of the doc
+            while line.find("*/") == -1:
+                line = f.readline()
+                # Start of a parameter
+                if line.find(' * @') == 0:
+                    find_attr = False
+                    for p in params:
+                        if line.find(param2attr(p) + ':') != -1:
+                            attr = get_attr(line)
+                            if attr is not None:
+                                params[p]['attr'] = attr
+                                find_attr = True
+                            break
+                elif find_attr and p:
+                    if 'doc' in params[p]:
+                        params[p]['doc'] = params[p]['doc'] + '\n' + strip_doc_line(line, params[p])
+                    else:
+                        params[p]['doc'] = strip_doc_line(line, params[p])
+
+    except Exception as e:
+        sys.exit(e)
+
+# replace multi text
+def strip_line(line):
+    return line.replace(' ', '').replace('"', '').replace('\\n', '').strip()
+
+def get_bridge_parameter(bridge_file):
+    params = {}
+    try:
+        with open(bridge_file, 'r') as f:
+            line = f.readline()
+            # Find the start of parameters
+            while line.find("Usage: ... bridge") == -1:
+                line = f.readline()
+            # Till the end of the parameters
+            while line.find("Where:") == -1:
+                line = f.readline()
+                parts = line.strip().split()
+                if len(parts) >= 4:
+                    if parts[1] == '[':
+                        params[parts[2]] = {}
+                        params[parts[2]]['param'] = parts[3]
+            # Till the end of usage function to get the alias
+            while line.find(");") == -1:
+                parts = line.strip().replace('Where:', '').split(':=')
+                alias = strip_line(parts[0])
+                for p in params:
+                    if params[p]['param'] == alias:
+                        params[p]['alias'] = strip_line(parts[1])
+                line = f.readline()
+    except Exception as e:
+        sys.exit(e)
+
+    return params
+
+def main():
+    args = handle_args()
+    bridge_file = args.iproute2_dir.rstrip('/') + "/ip/iplink_bridge.c"
+    ip_link_in = args.iproute2_dir.rstrip('/') + "/man/man8/ip-link.8.in"
+    ip_link_out = args.iproute2_dir.rstrip('/') + "/man/man8/ip-link.8.out"
+    bridge_header = args.net_dir.rstrip('/') + '/include/uapi/linux/if_link.h'
+
+    bridge_params = get_bridge_parameter(bridge_file)
+    get_bridge_doc(bridge_header, bridge_params)
+    convert2man(bridge_params)
+    write_man_doc(ip_link_in, ip_link_out)
+
+if __name__ == '__main__':
+    main()
