diff --git a/controller/automake.mk b/controller/automake.mk
index 45e1bdd36..d38ee927c 100644
--- a/controller/automake.mk
+++ b/controller/automake.mk
@@ -1,3 +1,37 @@
+bin_PROGRAMS += controller/ovn-pinctrl
+controller_ovn_pinctrl_SOURCES = \
+    controller/ovn-pinctrl.c \
+    controller/pinctrl.c \
+	controller/pinctrl.h \
+	controller/bfd.c \
+	controller/bfd.h \
+	controller/binding.c \
+	controller/binding.h \
+	controller/chassis.c \
+	controller/chassis.h \
+	controller/encaps.c \
+	controller/encaps.h \
+	controller/ha-chassis.c \
+	controller/ha-chassis.h \
+	controller/ip-mcast.c \
+	controller/ip-mcast.h \
+	controller/lflow.c \
+	controller/lflow.h \
+	controller/lport.c \
+	controller/lport.h \
+	controller/ofctrl.c \
+	controller/ofctrl.h \
+	controller/patch.c \
+	controller/patch.h \
+	controller/controller-utils.c \
+	controller/ovn-controller.h \
+	controller/physical.c \
+	controller/physical.h
+controller_ovn_pinctrl_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la
+man_MANS += controller/ovn-pinctrl.8
+EXTRA_DIST += controller/ovn-pinctrl.8.xml
+CLEANFILES += controller/ovn-pinctrl.8
+
 bin_PROGRAMS += controller/ovn-controller
 controller_ovn_controller_SOURCES = \
 	controller/bfd.c \
@@ -18,11 +52,10 @@ controller_ovn_controller_SOURCES = \
 	controller/lport.h \
 	controller/ofctrl.c \
 	controller/ofctrl.h \
-	controller/pinctrl.c \
-	controller/pinctrl.h \
 	controller/patch.c \
 	controller/patch.h \
 	controller/ovn-controller.c \
+	controller/controller-utils.c \
 	controller/ovn-controller.h \
 	controller/physical.c \
 	controller/physical.h
diff --git a/controller/binding.c b/controller/binding.c
index aad9d39e6..ec97b13f6 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -485,7 +485,8 @@ consider_local_datapath(struct ovsdb_idl_txn *ovnsb_idl_txn,
                         struct hmap *local_datapaths,
                         struct shash *lport_to_iface,
                         struct sset *local_lports,
-                        struct sset *local_lport_ids)
+                        struct sset *local_lport_ids,
+                        bool do_bind)
 {
     const struct ovsrec_interface *iface_rec
         = shash_find_data(lport_to_iface, binding_rec->logical_port);
@@ -554,6 +555,10 @@ consider_local_datapath(struct ovsdb_idl_txn *ovnsb_idl_txn,
         update_local_lport_ids(local_lport_ids, binding_rec);
     }
 
+    if (!do_bind) {
+        return;
+    }
+
     ovs_assert(ovnsb_idl_txn);
     if (ovnsb_idl_txn) {
         const char *vif_chassis = smap_get(&binding_rec->options,
@@ -588,6 +593,9 @@ consider_local_datapath(struct ovsdb_idl_txn *ovnsb_idl_txn,
         } else if (binding_rec->chassis == chassis_rec) {
             if (!strcmp(binding_rec->type, "virtual")) {
                 /* pinctrl module takes care of binding the ports
+                 *
+                 * struct hmap local_datapaths = HMAP_INITIALIZER(&local_datapaths);
+                 * struct sset
                  * of type 'virtual'.
                  * Release such ports if their virtual parents are no
                  * longer claimed by this chassis. */
@@ -700,7 +708,8 @@ binding_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
             const struct ovsrec_bridge_table *bridge_table,
             const struct ovsrec_open_vswitch_table *ovs_table,
             struct hmap *local_datapaths, struct sset *local_lports,
-            struct sset *local_lport_ids)
+            struct sset *local_lport_ids,
+            bool do_bind)
 {
     if (!chassis_rec) {
         return;
@@ -727,9 +736,10 @@ binding_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
                                 sbrec_port_binding_by_datapath,
                                 sbrec_port_binding_by_name,
                                 active_tunnels, chassis_rec, binding_rec,
-                                sset_is_empty(&egress_ifaces) ? NULL :
-                                &qos_map, local_datapaths, &lport_to_iface,
-                                local_lports, local_lport_ids);
+                                do_bind && sset_is_empty(&egress_ifaces) ?
+                                NULL : &qos_map, local_datapaths,
+                                &lport_to_iface, local_lports, local_lport_ids,
+                                do_bind);
 
     }
 
@@ -746,7 +756,7 @@ binding_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
     }
     shash_destroy(&bridge_mappings);
 
-    if (!sset_is_empty(&egress_ifaces)
+    if (do_bind && !sset_is_empty(&egress_ifaces)
         && set_noop_qos(ovs_idl_txn, port_table, qos_table, &egress_ifaces)) {
         const char *entry;
         SSET_FOR_EACH (entry, &egress_ifaces) {
diff --git a/controller/binding.h b/controller/binding.h
index 924891c1b..1a6a6d4b5 100644
--- a/controller/binding.h
+++ b/controller/binding.h
@@ -47,7 +47,8 @@ void binding_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
                  const struct ovsrec_bridge_table *bridge_table,
                  const struct ovsrec_open_vswitch_table *ovs_table,
                  struct hmap *local_datapaths,
-                 struct sset *local_lports, struct sset *local_lport_ids);
+                 struct sset *local_lports, struct sset *local_lport_ids,
+                 bool do_bind);
 bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
                      const struct sbrec_port_binding_table *,
                      const struct sbrec_chassis *);
diff --git a/controller/controller-utils.c b/controller/controller-utils.c
new file mode 100644
index 000000000..2ec4df1ac
--- /dev/null
+++ b/controller/controller-utils.c
@@ -0,0 +1,54 @@
+/* Copyright (c) 2015, 2016, 2017 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovn-controller.h"
+#include "lib/vswitch-idl.h"
+
+struct local_datapath *
+get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key)
+{
+    struct hmap_node *node = hmap_first_with_hash(local_datapaths, tunnel_key);
+    return (node
+            ? CONTAINER_OF(node, struct local_datapath, hmap_node)
+            : NULL);
+}
+
+uint32_t
+get_tunnel_type(const char *name)
+{
+    if (!strcmp(name, "geneve")) {
+        return GENEVE;
+    } else if (!strcmp(name, "stt")) {
+        return STT;
+    } else if (!strcmp(name, "vxlan")) {
+        return VXLAN;
+    }
+
+    return 0;
+}
+
+const struct ovsrec_bridge *
+get_bridge(const struct ovsrec_bridge_table *bridge_table, const char *br_name)
+{
+    const struct ovsrec_bridge *br;
+    OVSREC_BRIDGE_TABLE_FOR_EACH (br, bridge_table) {
+        if (!strcmp(br->name, br_name)) {
+            return br;
+        }
+    }
+    return NULL;
+}
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index b46a1d151..532a9a482 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -50,7 +50,6 @@
 #include "lib/ovn-util.h"
 #include "patch.h"
 #include "physical.h"
-#include "pinctrl.h"
 #include "openvswitch/poll-loop.h"
 #include "lib/bitmap.h"
 #include "lib/hash.h"
@@ -90,41 +89,6 @@ struct pending_pkt {
     char *flow_s;
 };
 
-struct local_datapath *
-get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key)
-{
-    struct hmap_node *node = hmap_first_with_hash(local_datapaths, tunnel_key);
-    return (node
-            ? CONTAINER_OF(node, struct local_datapath, hmap_node)
-            : NULL);
-}
-
-uint32_t
-get_tunnel_type(const char *name)
-{
-    if (!strcmp(name, "geneve")) {
-        return GENEVE;
-    } else if (!strcmp(name, "stt")) {
-        return STT;
-    } else if (!strcmp(name, "vxlan")) {
-        return VXLAN;
-    }
-
-    return 0;
-}
-
-const struct ovsrec_bridge *
-get_bridge(const struct ovsrec_bridge_table *bridge_table, const char *br_name)
-{
-    const struct ovsrec_bridge *br;
-    OVSREC_BRIDGE_TABLE_FOR_EACH (br, bridge_table) {
-        if (!strcmp(br->name, br_name)) {
-            return br;
-        }
-    }
-    return NULL;
-}
-
 static void
 update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
                    const struct sbrec_chassis *chassis,
@@ -1084,7 +1048,7 @@ en_runtime_data_run(struct engine_node *node)
                 br_int, chassis,
                 active_tunnels, bridge_table,
                 ovs_table, local_datapaths,
-                local_lports, local_lport_ids);
+                local_lports, local_lport_ids, true);
 
     update_ct_zones(local_lports, local_datapaths, ct_zones,
                     ct_zone_bitmap, pending_ct_zones);
@@ -1769,7 +1733,6 @@ main(int argc, char *argv[])
 
     daemonize_complete();
 
-    pinctrl_init();
     lflow_init();
 
     /* Connect to OVS OVSDB instance. */
@@ -1803,15 +1766,10 @@ main(int argc, char *argv[])
     struct ovsdb_idl_index *sbrec_datapath_binding_by_key
         = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
                                   &sbrec_datapath_binding_col_tunnel_key);
-    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip
-        = ovsdb_idl_index_create2(ovnsb_idl_loop.idl,
-                                  &sbrec_mac_binding_col_logical_port,
-                                  &sbrec_mac_binding_col_ip);
-    struct ovsdb_idl_index *sbrec_ip_multicast
-        = ip_mcast_index_create(ovnsb_idl_loop.idl);
     struct ovsdb_idl_index *sbrec_igmp_group
         = igmp_group_index_create(ovnsb_idl_loop.idl);
 
+
     ovsdb_idl_track_add_all(ovnsb_idl_loop.idl);
     ovsdb_idl_omit_alert(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg);
 
@@ -2070,20 +2028,6 @@ main(int argc, char *argv[])
                                get_nb_cfg(sbrec_sb_global_table_get(
                                               ovnsb_idl_loop.idl)),
                                en_flow_output.changed);
-                    pinctrl_run(ovnsb_idl_txn,
-                                sbrec_datapath_binding_by_key,
-                                sbrec_port_binding_by_datapath,
-                                sbrec_port_binding_by_key,
-                                sbrec_port_binding_by_name,
-                                sbrec_mac_binding_by_lport_ip,
-                                sbrec_igmp_group,
-                                sbrec_ip_multicast,
-                                sbrec_dns_table_get(ovnsb_idl_loop.idl),
-                                sbrec_controller_event_table_get(
-                                    ovnsb_idl_loop.idl),
-                                br_int, chassis,
-                                &ed_runtime_data.local_datapaths,
-                                &ed_runtime_data.active_tunnels);
 
                     if (en_runtime_data.changed) {
                         update_sb_monitors(ovnsb_idl_loop.idl, chassis,
@@ -2141,7 +2085,6 @@ main(int argc, char *argv[])
 
             if (br_int) {
                 ofctrl_wait();
-                pinctrl_wait(ovnsb_idl_txn);
             }
         }
 
@@ -2227,7 +2170,6 @@ main(int argc, char *argv[])
     unixctl_server_destroy(unixctl);
     lflow_destroy();
     ofctrl_destroy();
-    pinctrl_destroy();
 
     ovsdb_idl_loop_destroy(&ovs_idl_loop);
     ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
diff --git a/controller/ovn-pinctrl.c b/controller/ovn-pinctrl.c
new file mode 100644
index 000000000..e2010661f
--- /dev/null
+++ b/controller/ovn-pinctrl.c
@@ -0,0 +1,590 @@
+/* Copyright (c) 2015, 2016, 2017 Red Hat, Inc.
+ * Copyright (c) 2017 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <getopt.h>
+
+#include "pinctrl.h"
+
+#include "coverage.h"
+#include "csum.h"
+#include "dirs.h"
+#include "dp-packet.h"
+#include "controller/encaps.h"
+#include "flow.h"
+#include "controller/ha-chassis.h"
+#include "controller/lport.h"
+#include "nx-match.h"
+#include "controller/ovn-controller.h"
+#include "chassis-index.h"
+#include "latch.h"
+#include "lib/packets.h"
+#include "lib/sset.h"
+#include "openvswitch/ofp-actions.h"
+#include "openvswitch/ofp-msgs.h"
+#include "openvswitch/ofp-packet.h"
+#include "openvswitch/ofp-print.h"
+#include "openvswitch/ofp-switch.h"
+#include "openvswitch/ofp-util.h"
+#include "openvswitch/vlog.h"
+
+#include "lib/dhcp.h"
+#include "ovn/actions.h"
+#include "ovn/lex.h"
+#include "lib/acl-log.h"
+#include "lib/ip-mcast-index.h"
+#include "lib/mcast-group-index.h"
+#include "lib/ovn-l7.h"
+#include "lib/ovn-util.h"
+#include "ovn/logical-fields.h"
+#include "openvswitch/poll-loop.h"
+#include "openvswitch/rconn.h"
+#include "socket-util.h"
+#include "seq.h"
+#include "timeval.h"
+#include "vswitch-idl.h"
+#include "controller/lflow.h"
+#include "controller/ip-mcast.h"
+#include "stream.h"
+#include "stream-ssl.h"
+#include "unixctl.h"
+#include "command-line.h"
+#include "daemon.h"
+#include "controller/binding.h"
+#include "fatal-signal.h"
+#include "controller/chassis.h"
+#include "controller/physical.h"
+#include "controller/bfd.h"
+
+VLOG_DEFINE_THIS_MODULE(main);
+
+#define DEFAULT_BRIDGE_NAME "br-int"
+#define DEFAULT_PROBE_INTERVAL_MSEC 5000
+
+// XXX Copied directly from ovn-controller
+/* Retrieves the pointer to the OVN Southbound database from 'ovs_idl' and
+ * updates 'sbdb_idl' with that pointer. */
+static void
+update_sb_db(struct ovsdb_idl *ovs_idl, struct ovsdb_idl *ovnsb_idl)
+{
+    const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl);
+
+    /* Set remote based on user configuration. */
+    const char *remote = NULL;
+    if (cfg) {
+        remote = smap_get(&cfg->external_ids, "ovn-remote");
+    }
+    ovsdb_idl_set_remote(ovnsb_idl, remote, true);
+
+    /* Set probe interval, based on user configuration and the remote. */
+    int default_interval = (remote && !stream_or_pstream_needs_probes(remote)
+                            ? 0 : DEFAULT_PROBE_INTERVAL_MSEC);
+    int interval = smap_get_int(&cfg->external_ids,
+                                "ovn-remote-probe-interval", default_interval);
+    ovsdb_idl_set_probe_interval(ovnsb_idl, interval);
+}
+
+// XXX copied directly from ovn-controller
+static void
+update_ssl_config(const struct ovsrec_ssl_table *ssl_table)
+{
+    const struct ovsrec_ssl *ssl = ovsrec_ssl_table_first(ssl_table);
+
+    if (ssl) {
+        stream_ssl_set_key_and_cert(ssl->private_key, ssl->certificate);
+        stream_ssl_set_ca_cert_file(ssl->ca_cert, ssl->bootstrap_ca_cert);
+    }
+}
+
+// XXX Copied directly from ovn-controller
+static void
+ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
+{
+    /* We do not monitor all tables by default, so modules must register
+     * their interest explicitly. */
+    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_open_vswitch);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_external_ids);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_bridges);
+    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface);
+    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name);
+    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd);
+    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd_status);
+    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_type);
+    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_options);
+    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_ofport);
+    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port);
+    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
+    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
+    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
+    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_name);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_fail_mode);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_other_config);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_external_ids);
+    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_ssl);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_bootstrap_ca_cert);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_ca_cert);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_certificate);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_private_key);
+    chassis_register_ovs_idl(ovs_idl);
+    encaps_register_ovs_idl(ovs_idl);
+    binding_register_ovs_idl(ovs_idl);
+    bfd_register_ovs_idl(ovs_idl);
+    physical_register_ovs_idl(ovs_idl);
+}
+
+// XXX copied directly from ovn-controller
+static void
+update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
+                   const struct sbrec_chassis *chassis,
+                   const struct sset *local_ifaces,
+                   struct hmap *local_datapaths)
+{
+    /* Monitor Port_Bindings rows for local interfaces and local datapaths.
+     *
+     * Monitor Logical_Flow, MAC_Binding, Multicast_Group, and DNS tables for
+     * local datapaths.
+     *
+     * Monitor Controller_Event rows for local chassis.
+     *
+     * Monitor IP_Multicast for local datapaths.
+     *
+     * Monitor IGMP_Groups for local chassis.
+     *
+     * We always monitor patch ports because they allow us to see the linkages
+     * between related logical datapaths.  That way, when we know that we have
+     * a VIF on a particular logical switch, we immediately know to monitor all
+     * the connected logical routers and logical switches. */
+    struct ovsdb_idl_condition pb = OVSDB_IDL_CONDITION_INIT(&pb);
+    struct ovsdb_idl_condition lf = OVSDB_IDL_CONDITION_INIT(&lf);
+    struct ovsdb_idl_condition mb = OVSDB_IDL_CONDITION_INIT(&mb);
+    struct ovsdb_idl_condition mg = OVSDB_IDL_CONDITION_INIT(&mg);
+    struct ovsdb_idl_condition dns = OVSDB_IDL_CONDITION_INIT(&dns);
+    struct ovsdb_idl_condition ce =  OVSDB_IDL_CONDITION_INIT(&ce);
+    struct ovsdb_idl_condition ip_mcast = OVSDB_IDL_CONDITION_INIT(&ip_mcast);
+    struct ovsdb_idl_condition igmp = OVSDB_IDL_CONDITION_INIT(&igmp);
+    sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "patch");
+    /* XXX: We can optimize this, if we find a way to only monitor
+     * ports that have a Gateway_Chassis that point's to our own
+     * chassis */
+    sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "chassisredirect");
+    sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "external");
+    if (chassis) {
+        /* This should be mostly redundant with the other clauses for port
+         * bindings, but it allows us to catch any ports that are assigned to
+         * us but should not be.  That way, we can clear their chassis
+         * assignments. */
+        sbrec_port_binding_add_clause_chassis(&pb, OVSDB_F_EQ,
+                                              &chassis->header_.uuid);
+
+        /* Ensure that we find out about l2gateway and l3gateway ports that
+         * should be present on this chassis.  Otherwise, we might never find
+         * out about those ports, if their datapaths don't otherwise have a VIF
+         * in this chassis. */
+        const char *id = chassis->name;
+        const struct smap l2 = SMAP_CONST1(&l2, "l2gateway-chassis", id);
+        sbrec_port_binding_add_clause_options(&pb, OVSDB_F_INCLUDES, &l2);
+        const struct smap l3 = SMAP_CONST1(&l3, "l3gateway-chassis", id);
+        sbrec_port_binding_add_clause_options(&pb, OVSDB_F_INCLUDES, &l3);
+
+        sbrec_controller_event_add_clause_chassis(&ce, OVSDB_F_EQ,
+                                                  &chassis->header_.uuid);
+        sbrec_igmp_group_add_clause_chassis(&igmp, OVSDB_F_EQ,
+                                            &chassis->header_.uuid);
+    }
+    if (local_ifaces) {
+        const char *name;
+        SSET_FOR_EACH (name, local_ifaces) {
+            sbrec_port_binding_add_clause_logical_port(&pb, OVSDB_F_EQ, name);
+            sbrec_port_binding_add_clause_parent_port(&pb, OVSDB_F_EQ, name);
+        }
+    }
+    if (local_datapaths) {
+        const struct local_datapath *ld;
+        HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
+            struct uuid *uuid = CONST_CAST(struct uuid *,
+                                           &ld->datapath->header_.uuid);
+            sbrec_port_binding_add_clause_datapath(&pb, OVSDB_F_EQ, uuid);
+            sbrec_logical_flow_add_clause_logical_datapath(&lf, OVSDB_F_EQ,
+                                                           uuid);
+            sbrec_mac_binding_add_clause_datapath(&mb, OVSDB_F_EQ, uuid);
+            sbrec_multicast_group_add_clause_datapath(&mg, OVSDB_F_EQ, uuid);
+            sbrec_dns_add_clause_datapaths(&dns, OVSDB_F_INCLUDES, &uuid, 1);
+            sbrec_ip_multicast_add_clause_datapath(&ip_mcast, OVSDB_F_EQ,
+                                                   uuid);
+        }
+    }
+    sbrec_port_binding_set_condition(ovnsb_idl, &pb);
+    sbrec_logical_flow_set_condition(ovnsb_idl, &lf);
+    sbrec_mac_binding_set_condition(ovnsb_idl, &mb);
+    sbrec_multicast_group_set_condition(ovnsb_idl, &mg);
+    sbrec_dns_set_condition(ovnsb_idl, &dns);
+    sbrec_controller_event_set_condition(ovnsb_idl, &ce);
+    sbrec_ip_multicast_set_condition(ovnsb_idl, &ip_mcast);
+    sbrec_igmp_group_set_condition(ovnsb_idl, &igmp);
+    ovsdb_idl_condition_destroy(&pb);
+    ovsdb_idl_condition_destroy(&lf);
+    ovsdb_idl_condition_destroy(&mb);
+    ovsdb_idl_condition_destroy(&mg);
+    ovsdb_idl_condition_destroy(&dns);
+    ovsdb_idl_condition_destroy(&ce);
+    ovsdb_idl_condition_destroy(&ip_mcast);
+    ovsdb_idl_condition_destroy(&igmp);
+}
+
+// XXX Copied directly from ovn-controller
+static const char *
+get_ovs_chassis_id(const struct ovsrec_open_vswitch_table *ovs_table)
+{
+    const struct ovsrec_open_vswitch *cfg
+        = ovsrec_open_vswitch_table_first(ovs_table);
+    const char *chassis_id = cfg ? smap_get(&cfg->external_ids, "system-id")
+                                 : NULL;
+
+    if (!chassis_id) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+        VLOG_WARN_RL(&rl, "'system-id' in Open_vSwitch database is missing.");
+    }
+
+    return chassis_id;
+}
+
+static unixctl_cb_func ovn_pinctrl_exit;
+
+OVS_NO_RETURN static void
+usage(void)
+{
+    printf("%s: OVN pinctrl\n"
+           "usage %s [OPTIONS] [OVS-DATABASE]\n"
+           "where OVS-DATABASE is a socket on which the OVS OVSDB server is listening.\n",
+               program_name, program_name);
+    stream_usage("OVS-DATABASE", true, false, true);
+    daemon_usage();
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help              display this help message\n"
+           "  -V, --version           display version information\n");
+    exit(EXIT_SUCCESS);
+}
+
+// XXX Copied directly from ovn-controller
+static char *
+parse_options(int argc, char *argv[])
+{
+    enum {
+        OPT_PEER_CA_CERT = UCHAR_MAX + 1,
+        OPT_BOOTSTRAP_CA_CERT,
+        VLOG_OPTION_ENUMS,
+        DAEMON_OPTION_ENUMS,
+        SSL_OPTION_ENUMS,
+    };
+
+    static struct option long_options[] = {
+        {"help", no_argument, NULL, 'h'},
+        {"version", no_argument, NULL, 'V'},
+        VLOG_LONG_OPTIONS,
+        DAEMON_LONG_OPTIONS,
+        STREAM_SSL_LONG_OPTIONS,
+        {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT},
+        {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT},
+        {NULL, 0, NULL, 0}
+    };
+    char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
+
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'h':
+            usage();
+
+        case 'V':
+            ovs_print_version(OFP13_VERSION, OFP13_VERSION);
+            exit(EXIT_SUCCESS);
+
+        VLOG_OPTION_HANDLERS
+        DAEMON_OPTION_HANDLERS
+        STREAM_SSL_OPTION_HANDLERS
+
+        case OPT_PEER_CA_CERT:
+            stream_ssl_set_peer_ca_cert_file(optarg);
+            break;
+
+        case OPT_BOOTSTRAP_CA_CERT:
+            stream_ssl_set_ca_cert_file(optarg, true);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+
+    argc -= optind;
+    argv += optind;
+
+    char *ovs_remote;
+    if (argc == 0) {
+        ovs_remote = xasprintf("unix:%s/db.sock", ovs_rundir());
+    } else if (argc == 1) {
+        ovs_remote = xstrdup(argv[0]);
+    } else {
+        VLOG_FATAL("exactly zero or one non-option argument required; "
+                   "use --help for usage");
+    }
+    return ovs_remote;
+}
+
+// XXX Copied directly from ovn-controller
+static const char *
+br_int_name(const struct ovsrec_open_vswitch *cfg)
+{
+    return smap_get_def(&cfg->external_ids, "ovn-bridge", DEFAULT_BRIDGE_NAME);
+}
+
+// XXX Copied directly from ovn-controller
+static const struct ovsrec_bridge *
+get_br_int(const struct ovsrec_bridge_table *bridge_table,
+           const struct ovsrec_open_vswitch_table *ovs_table)
+{
+    const struct ovsrec_open_vswitch *cfg;
+    cfg = ovsrec_open_vswitch_table_first(ovs_table);
+    if (!cfg) {
+        return NULL;
+    }
+
+    return get_bridge(bridge_table, br_int_name(cfg));
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct unixctl_server *unixctl;
+    bool exiting;
+    int retval;
+
+    ovs_cmdl_proctitle_init(argc, argv);
+    set_program_name(argv[0]);
+    service_start(&argc, &argv);
+    char *ovs_remote = parse_options(argc, argv);
+    fatal_ignore_sigpipe();
+
+    daemonize_start(false);
+
+    retval = unixctl_server_create(NULL, &unixctl);
+    if (retval) {
+        exit(EXIT_FAILURE);
+    }
+
+    unixctl_command_register("exit", "", 0, 1, ovn_pinctrl_exit,
+                             &exiting);
+
+    daemonize_complete();
+    pinctrl_init();
+
+    /* Connect to OVS OVSDB instance. */
+    struct ovsdb_idl_loop ovs_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
+        ovsdb_idl_create(ovs_remote, &ovsrec_idl_class, false, true));
+    ctrl_register_ovs_idl(ovs_idl_loop.idl);
+    ovsdb_idl_get_initial_snapshot(ovs_idl_loop.idl);
+
+    struct ovsdb_idl_loop ovnsb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
+        ovsdb_idl_create_unconnected(&sbrec_idl_class, true));
+    ovsdb_idl_set_leader_only(ovnsb_idl_loop.idl, false);
+
+    struct ovsdb_idl_index *sbrec_chassis_by_name
+        = chassis_index_create(ovnsb_idl_loop.idl);
+    struct ovsdb_idl_index *sbrec_datapath_binding_by_key
+        = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
+                                  &sbrec_datapath_binding_col_tunnel_key);
+    struct ovsdb_idl_index *sbrec_port_binding_by_datapath
+        = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
+                                  &sbrec_port_binding_col_datapath);
+    struct ovsdb_idl_index *sbrec_port_binding_by_key
+        = ovsdb_idl_index_create2(ovnsb_idl_loop.idl,
+                                  &sbrec_port_binding_col_tunnel_key,
+                                  &sbrec_port_binding_col_datapath);
+    struct ovsdb_idl_index *sbrec_port_binding_by_name
+        = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
+                                  &sbrec_port_binding_col_logical_port);
+    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip
+        = ovsdb_idl_index_create2(ovnsb_idl_loop.idl,
+                                  &sbrec_mac_binding_col_logical_port,
+                                  &sbrec_mac_binding_col_ip);
+    struct ovsdb_idl_index *sbrec_igmp_group
+        = igmp_group_index_create(ovnsb_idl_loop.idl);
+    struct ovsdb_idl_index *sbrec_ip_multicast
+        = ip_mcast_index_create(ovnsb_idl_loop.idl);
+
+    ovsdb_idl_omit_alert(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg);
+
+    /* Omit the external_ids column of all the tables except for -
+     *  - DNS. pinctrl.c uses the external_ids column of DNS,
+     *    which it shouldn't. This should be removed.
+     *
+     *  - Chassis - chassis.c copies the chassis configuration from
+     *              local open_vswitch table to the external_ids of
+     *              chassis.
+     *
+     *  - Datapath_binding - lflow.c is using this to check if the datapath
+     *                       is switch or not. This should be removed.
+     * */
+    ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_sb_global_col_external_ids);
+    ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_external_ids);
+    ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_port_binding_col_external_ids);
+    ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_connection_col_external_ids);
+    ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_ssl_col_external_ids);
+    ovsdb_idl_omit(ovnsb_idl_loop.idl,
+                   &sbrec_gateway_chassis_col_external_ids);
+    ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_ha_chassis_col_external_ids);
+    ovsdb_idl_omit(ovnsb_idl_loop.idl,
+                   &sbrec_ha_chassis_group_col_external_ids);
+
+    update_sb_monitors(ovnsb_idl_loop.idl, NULL, NULL, NULL);
+
+    unsigned int ovs_cond_seqno = UINT_MAX;
+    unsigned int ovnsb_cond_seqno = UINT_MAX;
+
+    /* Main loop. */
+    exiting = false;
+    while (!exiting) {
+        update_sb_db(ovs_idl_loop.idl, ovnsb_idl_loop.idl);
+        update_ssl_config(ovsrec_ssl_table_get(ovs_idl_loop.idl));
+
+        struct ovsdb_idl_txn *ovs_idl_txn = ovsdb_idl_loop_run(&ovs_idl_loop);
+        unsigned int new_ovs_cond_seqno
+            = ovsdb_idl_get_condition_seqno(ovs_idl_loop.idl);
+        if (new_ovs_cond_seqno != ovs_cond_seqno) {
+            ovs_cond_seqno = new_ovs_cond_seqno;
+        }
+
+        struct ovsdb_idl_txn *ovnsb_idl_txn
+            = ovsdb_idl_loop_run(&ovnsb_idl_loop);
+        unsigned int new_ovnsb_cond_seqno
+            = ovsdb_idl_get_condition_seqno(ovnsb_idl_loop.idl);
+        if (new_ovnsb_cond_seqno != ovnsb_cond_seqno) {
+            ovnsb_cond_seqno = new_ovnsb_cond_seqno;
+        }
+
+        if (ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl)) {
+            const struct ovsrec_bridge_table *bridge_table =
+                ovsrec_bridge_table_get(ovs_idl_loop.idl);
+            const struct ovsrec_open_vswitch_table *ovs_table =
+                ovsrec_open_vswitch_table_get(ovs_idl_loop.idl);
+            const struct ovsrec_bridge *br_int =
+                get_br_int(bridge_table, ovs_table);
+            const char *chassis_id = get_ovs_chassis_id(ovs_table);
+            const struct sbrec_chassis *chassis = NULL;
+            if (chassis_id) {
+                chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
+            }
+
+            if (br_int && chassis) {
+                struct hmap local_datapaths = HMAP_INITIALIZER(&local_datapaths);
+                struct sset active_tunnels = SSET_INITIALIZER(&active_tunnels);
+                struct sset local_lports = SSET_INITIALIZER(&local_lports);
+                struct sset local_lport_ids = SSET_INITIALIZER(&local_lport_ids);
+
+                bfd_calculate_active_tunnels(br_int, &active_tunnels);
+
+                binding_run(ovnsb_idl_txn, ovs_idl_txn,
+                            sbrec_datapath_binding_by_key,
+                            sbrec_port_binding_by_datapath,
+                            sbrec_port_binding_by_name, NULL, NULL,
+                            sbrec_port_binding_table_get(ovnsb_idl_loop.idl),
+                            br_int, chassis, &active_tunnels, bridge_table,
+                            ovs_table, &local_datapaths, &local_lports,
+                            &local_lport_ids, false);
+
+                pinctrl_run(ovnsb_idl_txn,
+                            sbrec_datapath_binding_by_key,
+                            sbrec_port_binding_by_datapath,
+                            sbrec_port_binding_by_key,
+                            sbrec_port_binding_by_name,
+                            sbrec_mac_binding_by_lport_ip,
+                            sbrec_igmp_group,
+                            sbrec_ip_multicast,
+                            sbrec_dns_table_get(ovnsb_idl_loop.idl),
+                            sbrec_controller_event_table_get(
+                                ovnsb_idl_loop.idl),
+                            br_int, chassis,
+                            &local_datapaths,
+                            &active_tunnels);
+
+                // XXX Can we do this only if data has updated, like with ovn-controller?
+                update_sb_monitors(ovnsb_idl_loop.idl, chassis, &local_lports,
+                                   &local_datapaths);
+
+                sset_destroy(&local_lports);
+                sset_destroy(&local_lport_ids);
+                sset_destroy(&active_tunnels);
+                struct local_datapath *cur_node, *next_node;
+                HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
+                                    &local_datapaths) {
+                    free(cur_node->peer_ports);
+                    hmap_remove(&local_datapaths, &cur_node->hmap_node);
+                    free(cur_node);
+                }
+                hmap_destroy(&local_datapaths);
+                
+                pinctrl_wait(ovnsb_idl_txn);
+            }
+        }
+
+        unixctl_server_run(unixctl);
+
+        unixctl_server_wait(unixctl);
+        if (exiting) {
+            poll_immediate_wake();
+        }
+
+        ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop);
+        ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
+        poll_block();
+
+        if (should_service_stop()) {
+            exiting = true;
+        }
+    }
+
+    unixctl_server_destroy(unixctl);
+    pinctrl_destroy();
+
+    ovsdb_idl_loop_destroy(&ovs_idl_loop);
+    ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
+
+    service_stop();
+
+    exit(retval);
+}
+
+static void
+ovn_pinctrl_exit(struct unixctl_conn *conn, int argc OVS_UNUSED,
+             const char *argv[] OVS_UNUSED, void *exiting_)
+{
+    bool *exiting = exiting_;
+    *exiting = true;
+    unixctl_command_reply(conn, NULL);
+}
diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at
index 536da8dd7..6ce65b125 100644
--- a/tests/ofproto-macros.at
+++ b/tests/ofproto-macros.at
@@ -133,9 +133,11 @@ m4_define([OVN_CLEANUP_SBOX],[
     as $1
     if test "$1" = "vtep"; then
         OVS_APP_EXIT_AND_WAIT([ovn-controller-vtep])
+        OVS_APP_EXIT_AND_WAIT([ovn-pinctrl])
         OVS_APP_EXIT_AND_WAIT([ovs-vtep])
     else
         OVS_APP_EXIT_AND_WAIT([ovn-controller])
+        OVS_APP_EXIT_AND_WAIT([ovn-pinctrl])
     fi
     OVN_CLEANUP_VSWITCH([$1])
 ])
@@ -269,6 +271,7 @@ ovn_attach() {
         -- set bridge br-int fail-mode=secure other-config:disable-in-band=true \
         || return 1
     start_daemon ovn-controller || return 1
+    start_daemon ovn-pinctrl || return 1
 }
 
 # OVN_POPULATE_ARP
diff --git a/tests/ovn.at b/tests/ovn.at
index 22b272a60..ade13a281 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -8418,9 +8418,9 @@ packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
         tcp && tcp.flags==2 && tcp.src==4367 && tcp.dst==87"
 as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
 
-OVS_WAIT_UNTIL([ test 4 = $(grep -c 'acl_log' hv/ovn-controller.log) ])
+OVS_WAIT_UNTIL([ test 4 = $(grep -c 'acl_log' hv/ovn-pinctrl.log) ])
 
-AT_CHECK([grep 'acl_log' hv/ovn-controller.log | sed 's/.*name=/name=/'], [0], [dnl
+AT_CHECK([grep 'acl_log' hv/ovn-pinctrl.log | sed 's/.*name=/name=/'], [0], [dnl
 name="drop-flow", verdict=drop, severity=alert: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4361,tp_dst=81,tcp_flags=syn
 name="allow-flow", verdict=allow, severity=info: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4363,tp_dst=83,tcp_flags=syn
 name="<unnamed>", verdict=allow, severity=info: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4365,tp_dst=85,tcp_flags=syn
@@ -8487,7 +8487,7 @@ done
 # can't count on precise drop counts.  To work around that, we just
 # check that exactly 100 "http-acl3" actions were logged and that there
 # were more "http-acl1" actions than "http-acl2" ones.
-OVS_WAIT_UNTIL([ test 100 = $(grep -c 'http-acl3' hv/ovn-controller.log) ])
+OVS_WAIT_UNTIL([ test 100 = $(grep -c 'http-acl3' hv/ovn-pinctrl.log) ])
 
 # On particularly slow or overloaded systems, the transmission rate may
 # be lower than the configured meter rate.  To prevent false test
@@ -8502,9 +8502,9 @@ AT_SKIP_IF([test $d_secs -gt 9])
 as hv ovs-appctl -t ovn-controller meter-table-list
 as hv ovs-ofctl -O OpenFlow13 meter-stats br-int
 
-n_acl1=$(grep -c 'http-acl1' hv/ovn-controller.log)
-n_acl2=$(grep -c 'http-acl2' hv/ovn-controller.log)
-n_acl3=$(grep -c 'http-acl3' hv/ovn-controller.log)
+n_acl1=$(grep -c 'http-acl1' hv/ovn-pinctrl.log)
+n_acl2=$(grep -c 'http-acl2' hv/ovn-pinctrl.log)
+n_acl3=$(grep -c 'http-acl3' hv/ovn-pinctrl.log)
 
 AT_CHECK([ test $n_acl3 -gt $n_acl1 ], [0], [])
 AT_CHECK([ test $n_acl1 -gt $n_acl2 ], [0], [])
@@ -10615,6 +10615,7 @@ ovs-vsctl \
     -- set Open_vSwitch . external-ids:ovn-bridge-mappings=public:br-ex
 
 start_daemon ovn-controller
+start_daemon ovn-pinctrl
 ovs-vsctl -- add-port br-int hv1-vif1 -- \
     set interface hv1-vif1 external-ids:iface-id=foo1 \
     options:tx_pcap=hv1/vif1-tx.pcap \
diff --git a/tutorial/ovs-sandbox b/tutorial/ovs-sandbox
index a19dea2f1..a3c19f4dc 100755
--- a/tutorial/ovs-sandbox
+++ b/tutorial/ovs-sandbox
@@ -126,6 +126,7 @@ General options:
   -d, --gdb-ovsdb      run ovsdb-server under gdb
   --gdb-ovn-northd     run ovn-northd under gdb
   --gdb-ovn-controller run ovn-controller under gdb
+  --gdb-ovn-pinctrl    run ovn-pinctrl under gdb
   --gdb-ovn-controller-vtep run ovn-controller-vtep under gdb
   --dummy=ARG          pass --enable-dummy=ARG to vswitchd (default: override)
   -R, --gdb-run        automatically start running the daemon in gdb
@@ -213,6 +214,9 @@ EOF
         --gdb-ovn-controller)
             gdb_ovn_controller=true
             ;;
+        --gdb-ovn-pinctrl)
+            gdb_ovn_pinctrl=true
+            ;;
         --gdb-ovn-controller-vtep)
             gdb_ovn_controller_vtep=true
             ;;
@@ -264,6 +268,7 @@ EOF
             gdb_ovsdb_ex=true
             gdb_ovn_northd_ex=true
             gdb_ovn_controller_ex=true
+            gdb_ovn_pinctrl_ex=true
             gdb_ovn_controller_vtep_ex=true
             ;;
         -*)
diff --git a/utilities/ovn-ctl b/utilities/ovn-ctl
index 481e28fc9..31ff62933 100755
--- a/utilities/ovn-ctl
+++ b/utilities/ovn-ctl
@@ -385,6 +385,27 @@ start_controller () {
     OVS_RUNDIR=${OVS_RUNDIR} start_ovn_daemon "$OVN_CONTROLLER_PRIORITY" "$OVN_CONTROLLER_WRAPPER" "$@"
 }
 
+start_pinctrl () {
+    set ovn-pinctrl "unix:$DB_SOCK"
+    set "$@" $OVN_PINCTRL_LOG
+    if test X"$OVN_PINCTRL_SSL_KEY" != X; then
+        set "$@" --private-key=$OVN_PINCTRL_SSL_KEY
+    fi
+    if test X"$OVN_PINCTRL_SSL_CERT" != X; then
+        set "$@" --certificate=$OVN_PINCTRL_SSL_CERT
+    fi
+    if test X"$OVN_PINCTRL_SSL_CA_CERT" != X; then
+        set "$@" --ca-cert=$OVN_PINCTRL_SSL_CA_CERT
+    fi
+    if test X"$OVN_PINCTRL_SSL_BOOTSTRAP_CA_CERT" != X; then
+        set "$@" --bootstrap-ca-cert=$OVN_PINCTRL_SSL_BOOTSTRAP_CA_CERT
+    fi
+
+    [ "$OVN_USER" != "" ] && set "$@" --user "$OVN_USER"
+
+    OVS_RUNDIR=${OVN_RUNDIR} start_daemon "$OVN_PINCTRL_PRIORITY" "$OVN_PINCTRL_WRAPPER" "$@"
+}
+
 start_controller_vtep () {
     set ovn-controller-vtep
     set "$@" -vconsole:emer -vsyslog:err -vfile:info
@@ -430,6 +451,10 @@ stop_controller () {
     OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn-controller "$@"
 }
 
+stop_pinctrl() {
+    OVS_RUNDIR=${OVS_RUNDIR} stop_daemon ovn-pinctrl "$@"
+}
+
 stop_controller_vtep () {
     OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn-controller-vtep
 }
@@ -453,6 +478,11 @@ restart_controller_vtep () {
     start_controller_vtep
 }
 
+restart_pinctrl () {
+    stop_pinctrl
+    start_pinctrl
+}
+
 restart_ovsdb () {
     stop_ovsdb
     start_ovsdb
@@ -508,10 +538,13 @@ set_defaults () {
     OVN_NORTHD_WRAPPER=
     OVN_CONTROLLER_PRIORITY=-10
     OVN_CONTROLLER_WRAPPER=
+    OVN_PINCTRL_PRIORITY=-10
+    OVN_PINCTRL_WRAPPER=
 
     OVN_USER=
 
     OVN_CONTROLLER_LOG="-vconsole:emer -vsyslog:err -vfile:info"
+    OVN_PINCTRL_LOG="-vconsole:emer -vsyslog:err -vfile:info"
     OVN_NORTHD_LOG="-vconsole:emer -vsyslog:err -vfile:info"
     OVN_NORTHD_LOGFILE=""
     OVN_NB_LOG="-vconsole:off -vfile:info"
@@ -523,6 +556,10 @@ set_defaults () {
     OVN_CONTROLLER_SSL_CERT=""
     OVN_CONTROLLER_SSL_CA_CERT=""
     OVN_CONTROLLER_SSL_BOOTSTRAP_CA_CERT=""
+    OVN_PINCTRL_SSL_KEY=""
+    OVN_PINCTRL_SSL_CERT=""
+    OVN_PINCTRL_SSL_CA_CERT=""
+    OVN_PINCTRL_SSL_BOOTSTRAP_CA_CERT=""
 
     OVN_NORTHD_SSL_KEY=""
     OVN_NORTHD_SSL_CERT=""
@@ -768,6 +805,7 @@ case $command in
         ;;
     start_controller)
         start_controller
+        start_pinctrl
         ;;
     start_controller_vtep)
         start_controller_vtep
@@ -786,6 +824,7 @@ case $command in
         ;;
     stop_controller)
         stop_controller
+        stop_pinctrl
         ;;
     stop_controller_vtep)
         stop_controller_vtep
@@ -804,6 +843,7 @@ case $command in
         ;;
     restart_controller)
         restart_controller
+        restart_pinctrl
         ;;
     restart_controller_vtep)
         restart_controller_vtep
