struct vport_class {
     struct netdev_class netdev_class;
-    int (*parse_config)(const struct netdev_dev *, const struct shash *args,
-                        void *config);
+    int (*parse_config)(const char *name, const char *type,
+                        const struct shash *args, void *config);
+    int (*unparse_config)(const char *name, const char *type,
+                          const void *config, struct shash *args);
 };
 
 static struct shash netdev_vport_notifiers =
                     struct netdev_dev **netdev_devp)
 {
     const struct vport_class *vport_class = vport_class_cast(netdev_class);
-    struct netdev_dev_vport *dev;
+    uint64_t config[VPORT_CONFIG_SIZE / 8];
+    struct shash fetched_args;
     int error;
 
-    dev = xmalloc(sizeof *dev);
-    *netdev_devp = &dev->netdev_dev;
-    netdev_dev_init(&dev->netdev_dev, name, netdev_class);
+    memset(config, 0, sizeof config);
+    shash_init(&fetched_args);
+
+    if (!shash_is_empty(args)) {
+        /* Parse the provided configuration. */
+        error = vport_class->parse_config(name, netdev_class->type,
+                                          args, config);
+    } else {
+        /* Fetch an existing configuration from the kernel.
+         *
+         * This case could be ambiguous with initializing a new vport with an
+         * empty configuration, but none of the existing vport classes accept
+         * an empty configuration. */
+        struct odp_port odp_port;
+
+        memset(&odp_port, 0, sizeof odp_port);
+        strncpy(odp_port.devname, name, sizeof odp_port.devname);
+        error = netdev_vport_do_ioctl(ODP_VPORT_QUERY, &odp_port);
+        if (!error) {
+            /* XXX verify correct type */
+            memcpy(config, odp_port.config, sizeof config);
+            error = vport_class->unparse_config(name, netdev_class->type,
+                                                odp_port.config,
+                                                &fetched_args);
+            if (error) {
+                VLOG_ERR_RL(&rl, "%s: failed to parse kernel config (%s)",
+                            name, strerror(error));
+            }
+        } else {
+            VLOG_ERR_RL(&rl, "%s: vport query failed (%s)",
+                        name, strerror(error));
+        }
+    }
+
+    if (!error) {
+        struct netdev_dev_vport *dev;
 
-    memset(dev->config, 0, sizeof dev->config);
-    error = vport_class->parse_config(&dev->netdev_dev, args, dev->config);
+        dev = xmalloc(sizeof *dev);
+        netdev_dev_init(&dev->netdev_dev, name,
+                        shash_is_empty(&fetched_args) ? args : &fetched_args,
+                        netdev_class);
+        memcpy(dev->config, config, sizeof dev->config);
 
-    if (error) {
-        netdev_dev_uninit(&dev->netdev_dev, true);
+        *netdev_devp = &dev->netdev_dev;
     }
+
+    shash_destroy(&fetched_args);
+
     return error;
 }
 
 }
 
 static int
-netdev_vport_reconfigure(struct netdev_dev *dev_,
-                         const struct shash *args)
+netdev_vport_set_config(struct netdev_dev *dev_, const struct shash *args)
 {
     const struct netdev_class *netdev_class = netdev_dev_get_class(dev_);
     const struct vport_class *vport_class = vport_class_cast(netdev_class);
     memset(&port, 0, sizeof port);
     strncpy(port.devname, netdev_dev_get_name(dev_), sizeof port.devname);
     strncpy(port.type, netdev_dev_get_type(dev_), sizeof port.type);
-    error = vport_class->parse_config(dev_, args, port.config);
+    error = vport_class->parse_config(netdev_dev_get_name(dev_),
+                                      netdev_dev_get_type(dev_),
+                                      args, port.config);
     if (!error && memcmp(port.config, dev->config, sizeof dev->config)) {
         error = netdev_vport_do_ioctl(ODP_VPORT_MOD, &port);
         if (!error || error == ENODEV) {
 /* Code specific to individual vport types. */
 
 static int
-parse_tunnel_config(const struct netdev_dev *dev, const struct shash *args,
-                    void *configp)
+parse_tunnel_config(const char *name, const char *type,
+                    const struct shash *args, void *configp)
 {
-    const char *name = netdev_dev_get_name(dev);
-    const char *type = netdev_dev_get_type(dev);
     bool is_gre = false;
     bool is_ipsec = false;
     struct tnl_port_config config;
 }
 
 static int
-parse_patch_config(const struct netdev_dev *dev, const struct shash *args,
-                   void *configp)
+unparse_tunnel_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED,
+                      const void *config_, struct shash *args)
+{
+    const struct tnl_port_config *config = config_;
+
+    if (!(config->flags & TNL_F_HDR_CACHE) == !(config->flags & TNL_F_IPSEC)) {
+        smap_add(args, "header_cache",
+                 config->flags & TNL_F_HDR_CACHE ? "true" : "false");
+    }
+    shash_add(args, "remote_ip", xasprintf(IP_FMT, IP_ARGS(&config->daddr)));
+    if (config->saddr) {
+        shash_add(args, "local_ip",
+                  xasprintf(IP_FMT, IP_ARGS(&config->saddr)));
+    }
+
+    if ((config->flags & (TNL_F_IN_KEY_MATCH | TNL_F_OUT_KEY_ACTION))
+                      == (TNL_F_IN_KEY_MATCH | TNL_F_OUT_KEY_ACTION)) {
+        smap_add(args, "key", "flow");
+    } else if (config->in_key && config->in_key == config->out_key) {
+        shash_add(args, "key",
+                  xasprintf("%"PRIu64, ntohll(config->in_key)));
+    } else {
+        if (config->flags & TNL_F_IN_KEY_MATCH) {
+            smap_add(args, "in_key", "flow");
+        } else if (config->in_key) {
+            shash_add(args, "in_key",
+                      xasprintf("%"PRIu64, ntohll(config->in_key)));
+        }
+
+        if (config->flags & TNL_F_OUT_KEY_ACTION) {
+            smap_add(args, "out_key", "flow");
+        } else if (config->out_key) {
+            shash_add(args, "out_key",
+                      xasprintf("%"PRIu64, ntohll(config->out_key)));
+        }
+    }
+
+    if (config->flags & TNL_F_TTL_INHERIT) {
+        smap_add(args, "tos", "inherit");
+    } else if (config->ttl) {
+        shash_add(args, "tos", xasprintf("%"PRIu8, config->ttl));
+    }
+
+    if (config->flags & TNL_F_CSUM) {
+        smap_add(args, "csum", "true");
+    }
+    if (!(config->flags & TNL_F_PMTUD)) {
+        smap_add(args, "pmtud", "false");
+    }
+
+    return 0;
+}
+
+static int
+parse_patch_config(const char *name, const char *type OVS_UNUSED,
+                   const struct shash *args, void *configp)
 {
-    const char *name = netdev_dev_get_name(dev);
     const char *peer;
 
     peer = shash_find_data(args, "peer");
 
     return 0;
 }
+
+static int
+unparse_patch_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED,
+                     const void *config_, struct shash *args)
+{
+    char peer[IFNAMSIZ];
+
+    ovs_strlcpy(peer, config_, MIN(sizeof peer, VPORT_CONFIG_SIZE));
+    if (peer[0]) {
+        smap_add(args, "peer", peer);
+    }
+
+    return 0;
+}
 \f
 #define VPORT_FUNCTIONS(GET_STATUS)                         \
     netdev_vport_init,                                      \
                                                             \
     netdev_vport_create,                                    \
     netdev_vport_destroy,                                   \
-    netdev_vport_reconfigure,                               \
+    netdev_vport_set_config,                                \
                                                             \
     netdev_vport_open,                                      \
     netdev_vport_close,                                     \
 {
     static const struct vport_class vport_classes[] = {
         { { "gre", VPORT_FUNCTIONS(netdev_vport_get_status) },
-            parse_tunnel_config },
+          parse_tunnel_config, unparse_tunnel_config },
         { { "ipsec_gre", VPORT_FUNCTIONS(netdev_vport_get_status) },
-            parse_tunnel_config },
+          parse_tunnel_config, unparse_tunnel_config },
         { { "capwap", VPORT_FUNCTIONS(netdev_vport_get_status) },
-            parse_tunnel_config },
-        { { "patch", VPORT_FUNCTIONS(NULL) }, parse_patch_config }
+          parse_tunnel_config, unparse_tunnel_config },
+        { { "patch", VPORT_FUNCTIONS(NULL) },
+          parse_patch_config, unparse_patch_config }
     };
 
     int i;
 
             return error;
         }
         assert(netdev_dev->netdev_class == class);
-        update_device_args(netdev_dev, options->args);
 
     } else if (!shash_is_empty(options->args) &&
                !smap_equal(&netdev_dev->args, options->args)) {
 /* Reconfigures the device 'netdev' with 'args'.  'args' may be empty
  * or NULL if none are needed. */
 int
-netdev_reconfigure(struct netdev *netdev, const struct shash *args)
+netdev_set_config(struct netdev *netdev, const struct shash *args)
 {
     struct shash empty_args = SHASH_INITIALIZER(&empty_args);
     struct netdev_dev *netdev_dev = netdev_get_dev(netdev);
         args = &empty_args;
     }
 
-    if (netdev_dev->netdev_class->reconfigure) {
+    if (netdev_dev->netdev_class->set_config) {
         if (!smap_equal(&netdev_dev->args, args)) {
             update_device_args(netdev_dev, args);
-            return netdev_dev->netdev_class->reconfigure(netdev_dev, args);
+            return netdev_dev->netdev_class->set_config(netdev_dev, args);
         }
     } else if (!shash_is_empty(args)) {
-        VLOG_WARN("%s: arguments provided to device that does not have a "
-                  "reconfigure function", netdev_get_name(netdev));
+        VLOG_WARN("%s: arguments provided to device whose configuration "
+                  "cannot be changed", netdev_get_name(netdev));
     }
 
     return 0;
 }
 
+/* Returns the current configuration for 'netdev'.  This is either the
+ * configuration passed to netdev_open() or netdev_set_config(), or it is a
+ * configuration retrieved from the device itself if no configuration was
+ * passed to those functions.
+ *
+ * 'netdev' retains ownership of the returned configuration. */
+const struct shash *
+netdev_get_config(const struct netdev *netdev)
+{
+    return &netdev_get_dev(netdev)->args;
+}
+
 /* Closes and destroys 'netdev'. */
 void
 netdev_close(struct netdev *netdev)
     return netdev;
 }
 \f
-/* Initializes 'netdev_dev' as a netdev device named 'name' of the
- * specified 'netdev_class'.
+/* Initializes 'netdev_dev' as a netdev device named 'name' of the specified
+ * 'netdev_class'.  This function is ordinarily called from a netdev provider's
+ * 'create' function.
+ *
+ * 'args' should be the arguments that were passed to the netdev provider's
+ * 'create'.  If an empty set of arguments was passed, and 'name' is the name
+ * of a network device that existed before the 'create' call, then 'args' may
+ * instead be the configuration for that existing device.
  *
  * This function adds 'netdev_dev' to a netdev-owned shash, so it is
  * very important that 'netdev_dev' only be freed after calling
  * the refcount drops to zero.  */
 void
 netdev_dev_init(struct netdev_dev *netdev_dev, const char *name,
+                const struct shash *args,
                 const struct netdev_class *netdev_class)
 {
     assert(!shash_find(&netdev_dev_shash, name));
     netdev_dev->netdev_class = netdev_class;
     netdev_dev->name = xstrdup(name);
     netdev_dev->node = shash_add(&netdev_dev_shash, name, netdev_dev);
-    shash_init(&netdev_dev->args);
+    smap_clone(&netdev_dev->args, args);
 }
 
 /* Undoes the results of initialization.