[doc] Generate Doxygen and add DIF listings

This commit adds Doxygen API documentation (for device code) to the docs
site and uses the Doxygen XML to create lists of DIFs for each
peripheral, which can be included on the peripheral page.

The Doxygen site still needs styling and some other work, which will
happen in a follow-up commit.

Signed-off-by: Sam Elliott <selliott@lowrisc.org>
diff --git a/apt-requirements.txt b/apt-requirements.txt
index afb46e8..8987540 100644
--- a/apt-requirements.txt
+++ b/apt-requirements.txt
@@ -8,6 +8,7 @@
 build-essential
 clang-format
 curl
+doxygen
 flex
 g++
 git
@@ -29,4 +30,5 @@
 python3-yaml
 srecord
 tree
+xsltproc
 zlib1g-dev
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 78d9885..aba391b 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -42,6 +42,7 @@
       flake8 --version
       ninja --version
       meson --version
+      doxygen --version
       echo "PATH=$PATH"
     displayName: Display tool versions
   - bash: |
diff --git a/hw/ip/gpio/doc/_index.md b/hw/ip/gpio/doc/_index.md
index 8c83a5d..5626e8e 100644
--- a/hw/ip/gpio/doc/_index.md
+++ b/hw/ip/gpio/doc/_index.md
@@ -275,6 +275,10 @@
 
 ```
 
+## Device Interface Functions (DIFs)
+
+{{< dif_listing "sw/device/lib/dif/dif_gpio.h" >}}
+
 ## Register Table
 
 {{< registers "hw/ip/gpio/data/gpio.hjson" >}}
diff --git a/hw/ip/rv_plic/doc/_index.md b/hw/ip/rv_plic/doc/_index.md
index 82c6aee..983175c 100644
--- a/hw/ip/rv_plic/doc/_index.md
+++ b/hw/ip/rv_plic/doc/_index.md
@@ -226,6 +226,10 @@
 }
 ~~~~
 
+## Device Interface Functions (DIFs)
+
+{{< dif_listing "sw/device/lib/dif/dif_plic.h" >}}
+
 ## Registers
 
 The register description below matches the instance in the [Earl Grey top level
diff --git a/hw/ip/spi_device/doc/_index.md b/hw/ip/spi_device/doc/_index.md
index 25557ed..530c9cc 100644
--- a/hw/ip/spi_device/doc/_index.md
+++ b/hw/ip/spi_device/doc/_index.md
@@ -412,6 +412,10 @@
 the read pointer outside the range 0x000 -  0x1FF (128*4 = 512Bytes ignoring
 the phase bit, bit 11).
 
+## Device Interface Functions (DIFs)
+
+{{< dif_listing "sw/device/lib/dif/dif_spi_device.h" >}}
+
 ## Register Table
 
 {{< registers "hw/ip/spi_device/data/spi_device.hjson" >}}
diff --git a/hw/ip/uart/doc/_index.md b/hw/ip/uart/doc/_index.md
index db1a09c..c59a257 100644
--- a/hw/ip/uart/doc/_index.md
+++ b/hw/ip/uart/doc/_index.md
@@ -473,6 +473,10 @@
 case the host will eventually get a watermark interrupt, this will happen
 `((RXILVL - 1)*timeout)` after the first character was received.
 
+## Device Interface Functions (DIFs)
+
+{{< dif_listing "sw/device/lib/dif/dif_uart.h" >}}
+
 ## Register Table
 
 {{< registers "hw/ip/uart/data/uart.hjson" >}}
diff --git a/site/docs/builder.Dockerfile b/site/docs/builder.Dockerfile
index fca2844..8985dcd 100644
--- a/site/docs/builder.Dockerfile
+++ b/site/docs/builder.Dockerfile
@@ -4,7 +4,8 @@
 
 FROM ubuntu:18.04
 
-RUN apt-get update && apt-get install -y git curl python3 python3-pip && \
+RUN apt-get update && \
+  apt-get install -y git curl doxygen python3 python3-pip xsltproc && \
   apt-get clean; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
 COPY python-requirements.txt ./
 RUN pip3 install -r python-requirements.txt
diff --git a/site/docs/config.toml b/site/docs/config.toml
index ca46d85..4a2739f 100644
--- a/site/docs/config.toml
+++ b/site/docs/config.toml
@@ -52,6 +52,7 @@
   "/opentitan-docs/",
   "/scratch/",
   "/site/*",
+  "/util/doxygen/",
   "Makefile$",
   "CLA",
   "COMMITTERS",
@@ -60,6 +61,10 @@
 pygmentsStyle = "colorful"
 disableKinds = ["taxonomy", "taxonomyTerm", "RSS", "sitemap"]
 googleAnalytics = "UA-151030466-2"
+staticDir = [
+  # This is the generated software APIs
+  "build/docs-generated/sw/public-api",
+]
 
 [params]
 generatedRoot = "build/docs-generated"
diff --git a/site/docs/doxygen/footer.html b/site/docs/doxygen/footer.html
new file mode 100644
index 0000000..24ab3f0
--- /dev/null
+++ b/site/docs/doxygen/footer.html
@@ -0,0 +1,8 @@
+<!-- This has been changed to link back to OpenTitan -->
+<!-- start footer part -->
+<hr class="footer"/>
+<address class="footer">
+  Return to <a href="/">OpenTitan Documentation</a>
+</address>
+</body>
+</html>
diff --git a/site/docs/doxygen/header.html b/site/docs/doxygen/header.html
new file mode 100644
index 0000000..d39dbd4
--- /dev/null
+++ b/site/docs/doxygen/header.html
@@ -0,0 +1,34 @@
+<!-- This has been changed to support OpenTitan (including a link in the Logo) -->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
+<meta http-equiv="X-UA-Compatible" content="IE=9"/>
+<meta name="generator" content="Doxygen $doxygenversion"/>
+<meta name="viewport" content="width=device-width, initial-scale=1"/>
+<title>$title | $projectname | OpenTitan</title>
+<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
+<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
+<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
+<script type="text/javascript" src="$relpath^dynsections.js"></script>
+$mathjax
+<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
+$extrastylesheet
+</head>
+<body>
+<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
+
+<div id="titlearea">
+<table cellspacing="0" cellpadding="0">
+ <tbody>
+ <tr style="height: 56px;">
+  <td id="projectlogo"><a href="$relpath^./../.."><img height=100 alt="Logo" src="$relpath^$projectlogo"/></a></td>
+  <td id="projectalign" style="padding-left: 0.5em;">
+   <div id="projectname">$projectname
+   </div>
+  </td>
+ </tr>
+ </tbody>
+</table>
+</div>
+<!-- end header part -->
diff --git a/site/docs/doxygen/layout.xml b/site/docs/doxygen/layout.xml
new file mode 100644
index 0000000..721a48b
--- /dev/null
+++ b/site/docs/doxygen/layout.xml
@@ -0,0 +1,207 @@
+<doxygenlayout version="1.0">
+  <!--
+    This is almost exactly what gets generated, only a slightly different
+    structure for the navigation index to ensure files and globals come first,
+    as that's mostly what we document in this C project.
+  -->
+
+  <!-- Navigation index tabs for HTML output -->
+  <navindex>
+    <tab type="mainpage" visible="yes" title=""/>
+    <!-- Files and Globals have been moved here -->
+    <tab type="filelist" visible="yes" title="" intro=""/>
+    <tab type="globals" visible="yes" title="" intro=""/>
+
+    <tab type="pages" visible="yes" title="" intro=""/>
+    <tab type="modules" visible="yes" title="" intro=""/>
+    <tab type="namespaces" visible="yes" title="">
+      <tab type="namespacelist" visible="yes" title="" intro=""/>
+      <tab type="namespacemembers" visible="yes" title="" intro=""/>
+    </tab>
+    <tab type="classes" visible="yes" title="">
+      <tab type="classlist" visible="yes" title="" intro=""/>
+      <tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/>
+      <tab type="hierarchy" visible="yes" title="" intro=""/>
+      <tab type="classmembers" visible="yes" title="" intro=""/>
+    </tab>
+
+    <!--
+      Files and Globals used to be in a sub-header here:
+    <tab type="files" visible="yes" title="">
+      <tab type="filelist" visible="yes" title="" intro=""/>
+      <tab type="globals" visible="yes" title="" intro=""/>
+    </tab> -->
+
+    <tab type="examples" visible="yes" title="" intro=""/>
+  </navindex>
+
+  <!-- Layout definition for a class page -->
+  <class>
+    <briefdescription visible="yes"/>
+    <includes visible="$SHOW_INCLUDE_FILES"/>
+    <inheritancegraph visible="$CLASS_GRAPH"/>
+    <collaborationgraph visible="$COLLABORATION_GRAPH"/>
+    <memberdecl>
+      <nestedclasses visible="yes" title=""/>
+      <publictypes title=""/>
+      <services title=""/>
+      <interfaces title=""/>
+      <publicslots title=""/>
+      <signals title=""/>
+      <publicmethods title=""/>
+      <publicstaticmethods title=""/>
+      <publicattributes title=""/>
+      <publicstaticattributes title=""/>
+      <protectedtypes title=""/>
+      <protectedslots title=""/>
+      <protectedmethods title=""/>
+      <protectedstaticmethods title=""/>
+      <protectedattributes title=""/>
+      <protectedstaticattributes title=""/>
+      <packagetypes title=""/>
+      <packagemethods title=""/>
+      <packagestaticmethods title=""/>
+      <packageattributes title=""/>
+      <packagestaticattributes title=""/>
+      <properties title=""/>
+      <events title=""/>
+      <privatetypes title=""/>
+      <privateslots title=""/>
+      <privatemethods title=""/>
+      <privatestaticmethods title=""/>
+      <privateattributes title=""/>
+      <privatestaticattributes title=""/>
+      <friends title=""/>
+      <related title="" subtitle=""/>
+      <membergroups visible="yes"/>
+    </memberdecl>
+    <detaileddescription title=""/>
+    <memberdef>
+      <inlineclasses title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <services title=""/>
+      <interfaces title=""/>
+      <constructors title=""/>
+      <functions title=""/>
+      <related title=""/>
+      <variables title=""/>
+      <properties title=""/>
+      <events title=""/>
+    </memberdef>
+    <allmemberslink visible="yes"/>
+    <usedfiles visible="$SHOW_USED_FILES"/>
+    <authorsection visible="yes"/>
+  </class>
+
+  <!-- Layout definition for a namespace page -->
+  <namespace>
+    <briefdescription visible="yes"/>
+    <memberdecl>
+      <nestednamespaces visible="yes" title=""/>
+      <constantgroups visible="yes" title=""/>
+      <classes visible="yes" title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <functions title=""/>
+      <variables title=""/>
+      <membergroups visible="yes"/>
+    </memberdecl>
+    <detaileddescription title=""/>
+    <memberdef>
+      <inlineclasses title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <functions title=""/>
+      <variables title=""/>
+    </memberdef>
+    <authorsection visible="yes"/>
+  </namespace>
+
+  <!-- Layout definition for a file page -->
+  <file>
+    <briefdescription visible="yes"/>
+    <includes visible="$SHOW_INCLUDE_FILES"/>
+    <includegraph visible="$INCLUDE_GRAPH"/>
+    <includedbygraph visible="$INCLUDED_BY_GRAPH"/>
+    <sourcelink visible="yes"/>
+    <memberdecl>
+      <classes visible="yes" title=""/>
+      <namespaces visible="yes" title=""/>
+      <constantgroups visible="yes" title=""/>
+      <defines title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <functions title=""/>
+      <variables title=""/>
+      <membergroups visible="yes"/>
+    </memberdecl>
+    <detaileddescription title=""/>
+    <memberdef>
+      <inlineclasses title=""/>
+      <defines title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <functions title=""/>
+      <variables title=""/>
+    </memberdef>
+    <authorsection/>
+  </file>
+
+  <!-- Layout definition for a group page -->
+  <group>
+    <briefdescription visible="yes"/>
+    <groupgraph visible="$GROUP_GRAPHS"/>
+    <memberdecl>
+      <nestedgroups visible="yes" title=""/>
+      <dirs visible="yes" title=""/>
+      <files visible="yes" title=""/>
+      <namespaces visible="yes" title=""/>
+      <classes visible="yes" title=""/>
+      <defines title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <enumvalues title=""/>
+      <functions title=""/>
+      <variables title=""/>
+      <signals title=""/>
+      <publicslots title=""/>
+      <protectedslots title=""/>
+      <privateslots title=""/>
+      <events title=""/>
+      <properties title=""/>
+      <friends title=""/>
+      <membergroups visible="yes"/>
+    </memberdecl>
+    <detaileddescription title=""/>
+    <memberdef>
+      <pagedocs/>
+      <inlineclasses title=""/>
+      <defines title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <enumvalues title=""/>
+      <functions title=""/>
+      <variables title=""/>
+      <signals title=""/>
+      <publicslots title=""/>
+      <protectedslots title=""/>
+      <privateslots title=""/>
+      <events title=""/>
+      <properties title=""/>
+      <friends title=""/>
+    </memberdef>
+    <authorsection visible="yes"/>
+  </group>
+
+  <!-- Layout definition for a directory page -->
+  <directory>
+    <briefdescription visible="yes"/>
+    <directorygraph visible="yes"/>
+    <memberdecl>
+      <dirs visible="yes"/>
+      <files visible="yes"/>
+    </memberdecl>
+    <detaileddescription title=""/>
+  </directory>
+</doxygenlayout>
diff --git a/site/docs/doxygen/main_page.md b/site/docs/doxygen/main_page.md
new file mode 100644
index 0000000..d7bcacd
--- /dev/null
+++ b/site/docs/doxygen/main_page.md
@@ -0,0 +1,11 @@
+# OpenTitan Software APIs
+
+This part of the [Opentitan Documentation](../..) contains API documentation for the
+Software APIs that are part of OpenTitan.
+
+The documentation can be browsed using the tabs above, or there are API listings
+for the relevant DIFs in each Comportable Hardware IP specification.
+
+[Return to OpenTitan Software Documentation](..)
+
+[Return to OpenTitan Documentation](../..)
\ No newline at end of file
diff --git a/site/docs/layouts/shortcodes/dif_listing.html b/site/docs/layouts/shortcodes/dif_listing.html
new file mode 100644
index 0000000..2dc6af1
--- /dev/null
+++ b/site/docs/layouts/shortcodes/dif_listing.html
@@ -0,0 +1,6 @@
+{{ $baseName := .Get 0 }}
+{{ $path := path.Join .Site.Params.generatedRoot (printf "sw/difs_listings/%s.html" $baseName) }}
+{{ if not (fileExists $path) }}
+  {{ errorf "DIF listings have not been generated for %s" $baseName }}
+{{ end }}
+{{ readFile $path | safeHTML }}
diff --git a/site/docs/layouts/shortcodes/difref.html b/site/docs/layouts/shortcodes/difref.html
new file mode 100644
index 0000000..589a89f
--- /dev/null
+++ b/site/docs/layouts/shortcodes/difref.html
@@ -0,0 +1,6 @@
+{{ $difName := .Get 0 }}
+{{ $path := path.Join .Site.Params.generatedRoot (printf "sw/difref/%s.html" $difName) }}
+{{ if not (fileExists $path) }}
+  {{ errorf "DIF reference has not been generated for %s" $difName }}
+{{ end }}
+{{ readFile $path | safeHTML }}
diff --git a/sw/_index.md b/sw/_index.md
index b0e95d3..017544c 100644
--- a/sw/_index.md
+++ b/sw/_index.md
@@ -3,9 +3,20 @@
 ---
 
 This is the landing spot for software documentation within the OpenTitan project.
-Primarily these are README files about different software components.
 More description and information can be found within the [Reference Manual]({{< relref "doc/rm" >}}) and [User Guide]({{< relref "doc/ug" >}}) areas.
 
+There are two major parts to the OpenTitan software stack:
+
+* The _device_ software, which runs on the OpenTitan platform.
+* The _host_ software, which is run on a host device and interacts with an OpenTitan device.
+
+## OpenTitan Software API Documentation
+
+The [OpenTitan Software API Documentation](/sw/apis/) contains automatically generated documentation for the public software APIs.
+This includes the Device Interface Functions (DIFs).
+
+All DIFs are also documented on their respective [Hardware IP Specification]({{< relref "hw" >}})
+
 ## Software READMEs
 
 {{% sectionContent %}}
diff --git a/util/build_docs.py b/util/build_docs.py
index bb2038b..57f0192 100755
--- a/util/build_docs.py
+++ b/util/build_docs.py
@@ -23,6 +23,7 @@
 import hjson
 
 import dashboard.gen_dashboard_entry as gen_dashboard_entry
+import difgen.gen_dif_listing as gen_dif_listing
 import reggen.gen_cfg_html as gen_cfg_html
 import reggen.gen_html as gen_html
 import reggen.validate as validate
@@ -95,6 +96,9 @@
     # Pre-generated utility selfdoc
     "selfdoc_tools": ["tlgen", "reggen"],
 
+    # DIF Docs
+    "difs-directory": "sw/device/lib/dif",
+
     # Output directory for documents
     "outdir":
     SRCTREE_TOP.joinpath('build', 'docs'),
@@ -219,6 +223,67 @@
         with open(str(version_path), mode='w') as fout:
             fout.write(__TOOL_REQUIREMENTS__[tool])  # noqa: F821
 
+def generate_dif_docs():
+    """Generate doxygen documentation and DIF listings from DIF source comments.
+
+    This invokes Doxygen, and a few other things. Be careful of changing any
+    paths here, some correspond to paths in other configuration files.
+    """
+
+    logging.info("Generating Software API Documentation (Doxygen)...")
+
+    doxygen_out_path = config["outdir-generated"].joinpath("sw")
+
+    # The next two paths correspond to relative paths specified in the Doxyfile
+    doxygen_xml_path = doxygen_out_path.joinpath("api-xml")
+
+    # We need to prepare this path because doxygen won't `mkdir -p`
+    doxygen_sw_path = doxygen_out_path.joinpath("public-api/sw/apis")
+    doxygen_sw_path.mkdir(parents=True, exist_ok=True)
+
+    doxygen_args = [
+        "doxygen",
+        str(SRCTREE_TOP.joinpath("util/doxygen/Doxyfile")),
+    ]
+
+    doxygen_results = subprocess.run(doxygen_args, check=True,
+        cwd=str(SRCTREE_TOP), stdout=subprocess.PIPE,
+        env=dict(os.environ,
+            SRCTREE_TOP=str(SRCTREE_TOP),
+            DOXYGEN_OUT=str(doxygen_out_path),
+        ))
+
+    logging.info("Generated Software API Documentation (Doxygen)")
+
+    combined_xml = gen_dif_listing.get_combined_xml(doxygen_xml_path)
+
+    dif_paths = []
+    dif_paths.extend(sorted(SRCTREE_TOP.joinpath(config["difs-directory"]).glob("dif_*.h")))
+
+    dif_listings_root_path = config["outdir-generated"].joinpath("sw/difs_listings")
+    difrefs_root_path = config["outdir-generated"].joinpath("sw/difref")
+
+    for dif_header_path in dif_paths:
+        dif_header = str(dif_header_path.relative_to(SRCTREE_TOP))
+
+        dif_listings_filename = dif_listings_root_path.joinpath(dif_header + ".html")
+        dif_listings_filename.parent.mkdir(parents=True, exist_ok=True)
+
+        dif_listings_html = open(str(dif_listings_filename), mode='w')
+        gen_dif_listing.gen_listing_html(combined_xml, dif_header, dif_listings_html)
+        dif_listings_html.close()
+
+        difref_functions = gen_dif_listing.get_difref_info(combined_xml, dif_header)
+        for function in difref_functions:
+            difref_filename = difrefs_root_path.joinpath(function["name"] + '.html')
+            difref_filename.parent.mkdir(parents=True, exist_ok=True)
+
+            difref_html = open(str(difref_filename), mode='w')
+            gen_dif_listing.gen_difref_html(function, difref_html)
+            difref_html.close()
+
+        logging.info("Generated DIF Listing for {}".format(dif_header))
+
 
 def hugo_match_version(hugo_bin_path, version):
     logging.info("Hugo binary path: %s", hugo_bin_path)
@@ -326,6 +391,7 @@
     generate_selfdocs()
     generate_apt_reqs()
     generate_tool_versions()
+    generate_dif_docs()
 
     hugo_localinstall_dir = SRCTREE_TOP / 'build' / 'docs-hugo'
     os.environ["PATH"] += os.pathsep + str(hugo_localinstall_dir)
diff --git a/util/difgen/__init__.py b/util/difgen/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/util/difgen/__init__.py
diff --git a/util/difgen/gen_dif_listing.py b/util/difgen/gen_dif_listing.py
new file mode 100644
index 0000000..1319e68
--- /dev/null
+++ b/util/difgen/gen_dif_listing.py
@@ -0,0 +1,114 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+"""
+Generate HTML documentation for Device Interface Functions (DIFs)
+"""
+
+import logging as log
+import subprocess
+
+import xml.etree.ElementTree as ET
+
+# Turn the Doxygen multi-file XML output into one giant XML file (and parse it
+# into a python object), using the provided XLST file.
+def get_combined_xml(doxygen_xml_path):
+    xsltproc_args = [
+        "xsltproc",
+        str(doxygen_xml_path.joinpath("combine.xslt")),
+        str(doxygen_xml_path.joinpath("index.xml")),
+    ]
+
+    combined_xml_res = subprocess.run(xsltproc_args, check=True,
+        cwd=str(doxygen_xml_path), stdout=subprocess.PIPE,
+        universal_newlines=True)
+    return ET.fromstring(combined_xml_res.stdout)
+
+# Get all information about individual DIF functions that are specified in one
+# DIF header. This returns only the Info from the XML that we require.
+def get_difref_info(combined_xml, dif_header):
+    compound = _get_dif_file_compound(combined_xml, dif_header)
+    if compound == None:
+        return []
+
+    file_id = _get_dif_file_id(compound)
+    functions = _get_dif_function_info(compound, file_id)
+    return functions
+
+# Create HTML List of DIFs, using the info from the combined xml
+def gen_listing_html(combined_xml, dif_header, dif_listings_html):
+    compound = _get_dif_file_compound(combined_xml, dif_header)
+    if compound == None:
+        log.error("Doxygen output not found for {}".format(dif_header))
+        return
+
+    file_id = _get_dif_file_id(compound)
+    functions = _get_dif_function_info(compound, file_id)
+
+    if len(functions) == 0:
+        log.error("No DIF functions found for {}".format(dif_header))
+        return
+
+    # Generate DIF listing header
+    dif_listings_html.write('<p>To use this DIF, include the following C header:</p>')
+    dif_listings_html.write('<pre><code class=language-c data-lang=c>')
+    dif_listings_html.write('#include "<a href="/sw/apis/{}.html">{}</a>"'.format(file_id, dif_header))
+    dif_listings_html.write('</code></pre>\n')
+
+    # Generate DIF function list.
+    dif_listings_html.write('<p>This header provides the following device interface functions:</p>')
+    dif_listings_html.write('<ul>\n')
+    for f in sorted(functions, key=lambda x: x['name']):
+        dif_listings_html.write('<li title="{prototype}" id="Dif_{name}">'.format(**f))
+        dif_listings_html.write('<a href="{full_url}">'.format(**f))
+        dif_listings_html.write('<code>{name}</code>'.format(**f))
+        dif_listings_html.write('</a>\n')
+        dif_listings_html.write(f['description'])
+        dif_listings_html.write('</li>\n')
+    dif_listings_html.write('</ul>\n')
+
+# Generate HTML link for single function, using info returned from
+# get_difref_info
+def gen_difref_html(function_info, difref_html):
+    difref_html.write('<a href="{full_url}" title="{description}">'.format(**function_info))
+    difref_html.write('<code>{name}</code>'.format(**function_info))
+    difref_html.write('</a>\n')
+
+def _get_dif_file_compound(combined_xml, dif_header):
+    for c in combined_xml.findall('compounddef[@kind="file"]'):
+        if c.find("location").attrib["file"] == dif_header:
+            return c
+    return None
+
+def _get_dif_file_id(compound):
+    return compound.attrib["id"]
+
+def _get_dif_function_info(compound, file_id):
+    funcs = compound.find('sectiondef[@kind="func"]')
+    if funcs == None:
+        return []
+
+    # Collect useful info on each function
+    functions = []
+    for m in funcs.findall('memberdef[@kind="function"]'):
+        func_id = m.attrib['id']
+        # Strip refid prefix, which is separated from the funcid by `_1`
+        if func_id.startswith(file_id + '_1'):
+            # The +2 here is because of the weird `_1` separator
+            func_id = func_id[len(file_id) + 2:]
+        else:
+            continue
+
+        func_info = {}
+        func_info["id"] = m.attrib["id"]
+        func_info["file_id"] = file_id
+        func_info["local_id"] = func_id
+        func_info["full_url"] = "/sw/apis/{}.html#{}".format(file_id, func_id)
+
+        func_info["name"] = m.find("name").text
+        func_info["prototype"] = m.find("definition").text + m.find("argsstring").text
+        func_info["description"] = m.find("briefdescription/para").text
+
+        functions.append(func_info)
+
+    return functions
diff --git a/util/doxygen/Doxyfile b/util/doxygen/Doxyfile
new file mode 100644
index 0000000..2038681
--- /dev/null
+++ b/util/doxygen/Doxyfile
@@ -0,0 +1,200 @@
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+PROJECT_NAME           = "Software APIs"
+PROJECT_LOGO           = "$(SRCTREE_TOP)/doc/opentitan-logo.png"
+
+OUTPUT_DIRECTORY       = "$(DOXYGEN_OUT)"
+ALLOW_UNICODE_NAMES    = YES
+
+JAVADOC_AUTOBRIEF      = YES
+
+STRIP_FROM_INC_PATH    = "$(SRCTREE_TOP)"
+
+TAB_SIZE               = 2
+
+OPTIMIZE_OUTPUT_FOR_C  = YES
+
+# We use .c and .h for C
+EXTENSION_MAPPING      = h=C c=C
+
+INLINE_SIMPLE_STRUCTS  = YES
+
+# This doesn't work for enums
+TYPEDEF_HIDES_STRUCT   = NO
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+EXTRACT_ANON_NSPACES   = YES
+HIDE_IN_BODY_DOCS      = YES
+INTERNAL_DOCS          = NO
+CASE_SENSE_NAMES       = NO
+
+FORCE_LOCAL_INCLUDES   = NO
+STRIP_FROM_INC_PATH    = "$(SRCTREE_TOP)" \
+                         "$(SRCTREE_TOP)/sw/device/lib/base/freestanding"
+
+GENERATE_TODOLIST      = NO
+GENERATE_TESTLIST      = NO
+GENERATE_BUGLIST       = NO
+
+SHOW_USED_FILES        = NO
+SHOW_NAMESPACES        = NO
+
+FILE_VERSION_FILTER    = "git log --format='%h' -1 --"
+
+LAYOUT_FILE            = "$(SRCTREE_TOP)/site/docs/doxygen/layout.xml"
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+QUIET                  = YES
+WARN_LOGFILE           = "$(DOXYGEN_OUT)/doxygen_warnings.log"
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# Absolute path using $(SRCTREE_TOP)
+INPUT                  = "$(SRCTREE_TOP)/sw" \
+                         "$(SRCTREE_TOP)/site/docs/doxygen/main_page.md"
+
+USE_MDFILE_AS_MAINPAGE = "$(SRCTREE_TOP)/site/docs/doxygen/main_page.md"
+
+FILE_PATTERNS          = *.h \
+                         *.c \
+                         *.cc
+
+RECURSIVE              = YES
+
+# Absolute paths using $(SRCTREE_TOP)
+EXCLUDE                = "$(SRCTREE_TOP)/sw/vendor" \
+                         "$(SRCTREE_TOP)/sw/device/benchmarks" \
+                         "$(SRCTREE_TOP)/sw/host/vendor"
+
+EXCLUDE_SYMLINKS       = YES
+
+# Absolute path using $(SRCTREE_TOP)
+IMAGE_PATH             = "$(SRCTREE_TOP)/site/docs/doxygen"
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+SOURCE_BROWSER         = YES
+STRIP_CODE_COMMENTS    = NO
+
+REFERENCES_LINK_SOURCE = NO
+
+SOURCE_TOOLTIPS        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+COLS_IN_ALPHA_INDEX    = 2
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+GENERATE_HTML          = YES
+
+# Relative to `OUTPUT_DIRECTORY`
+HTML_OUTPUT            = "public-api/sw/apis"
+
+# Absolute paths using $(SRCTREE_TOP)
+HTML_HEADER            = "$(SRCTREE_TOP)/site/docs/doxygen/header.html"
+HTML_FOOTER            = "$(SRCTREE_TOP)/site/docs/doxygen/footer.html"
+
+HTML_COLORSTYLE_HUE    = 271
+
+HTML_TIMESTAMP         = NO
+
+GENERATE_DOCSET        = NO
+GENERATE_HTMLHELP      = NO
+GENERATE_QHP           = NO
+GENERATE_ECLIPSEHELP   = NO
+
+ENUM_VALUES_PER_LINE   = 1
+
+SEARCHENGINE           = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+GENERATE_LATEX         = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+GENERATE_RTF           = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+GENERATE_MAN           = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+GENERATE_XML           = YES
+
+# Relative to `OUTPUT_DIRECTORY`
+XML_OUTPUT             = "api-xml"
+
+XML_PROGRAMLISTING     = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+GENERATE_DOCBOOK       = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+GENERATE_PERLMOD       = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+INCLUDE_PATH           = "$(SRCTREE_TOP)/sw/device/lib/base/freestanding"
+
+INCLUDE_FILE_PATTERNS  = *.h
+
+PREDEFINED             =
+EXPAND_AS_DEFINED      =
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+CLASS_DIAGRAMS         = NO
+
+HAVE_DOT               = NO
+CLASS_GRAPH            = NO
+COLLABORATION_GRAPH    = NO
+GROUP_GRAPHS           = NO
+INCLUDE_GRAPH          = NO
+INCLUDED_BY_GRAPH      = NO
+GRAPHICAL_HIERARCHY    = NO
+DIRECTORY_GRAPH        = NO