diff --git a/hmp-commands.hx b/hmp-commands.hx
index 7e8e8ab..7f121b4 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -741,6 +741,20 @@ Add device to remote proc.
 ETEXI
 
     {
+        .name       = "rdevice_del",
+        .args_type  = "rdev_id:s,id:s",
+        .params     = "rdev_id id",
+        .help       = "remove device from remote proc",
+        .cmd        = hmp_rdevice_del,
+    },
+
+STEXI
+@item rdevice_del @var{rdev_id} @var{id}
+@findex rdevice_del
+Remove device from remote proc.
+ETEXI
+
+    {
         .name       = "remote_proc_list",
         .args_type  = "",
         .params     = "",
diff --git a/hmp.h b/hmp.h
index 355a27e..52b83c0 100644
--- a/hmp.h
+++ b/hmp.h
@@ -151,5 +151,6 @@ void hmp_info_memory_size_summary(Monitor *mon, const QDict *qdict);
 void hmp_info_sev(Monitor *mon, const QDict *qdict);
 void hmp_remote_proc_list(Monitor *mon, const QDict *qdict);
 void hmp_rdevice_add(Monitor *mon, const QDict *qdict);
+void hmp_rdevice_del(Monitor *mon, const QDict *qdict);
 
 #endif
diff --git a/hw/proxy/monitor.c b/hw/proxy/monitor.c
index 2e2cda0..05c8f8b 100644
--- a/hw/proxy/monitor.c
+++ b/hw/proxy/monitor.c
@@ -158,6 +158,8 @@ static void rdevice_add_del(QDict *qdict, proc_cmd_t cmd, Error **errp)
     if (cmd == DEVICE_ADD) {
         (void)g_hash_table_insert(pcms->remote_devs, (gpointer)g_strdup(id),
                                   (gpointer)pdev);
+    } else {
+        (void)g_hash_table_remove(pcms->remote_devs, (gpointer)id);
     }
 }
 
@@ -179,3 +181,23 @@ void hmp_rdevice_add(Monitor *mon, const QDict *qdict)
         error_free(err);
     }
 }
+
+void qmp_rdevice_del(QDict *qdict, QObject **ret_data, Error **errp)
+{
+    rdevice_add_del(qdict, DEVICE_DEL, errp);
+}
+
+void hmp_rdevice_del(Monitor *mon, const QDict *qdict)
+{
+    Error *err = NULL;
+
+    /* TODO: Is it OK to modify the QDict argument from HMP? */
+    rdevice_add_del((QDict *)qdict, DEVICE_DEL, &err);
+
+    if (err) {
+        monitor_printf(mon, "rdevice_del error: %s\n",
+                       error_get_pretty(err));
+        error_free(err);
+    }
+}
+
diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h
index 17f09aa..bdaa3b9 100644
--- a/include/hw/qdev-core.h
+++ b/include/hw/qdev-core.h
@@ -358,6 +358,7 @@ BusState *qdev_get_parent_bus(DeviceState *dev);
 /*** BUS API. ***/
 
 DeviceState *qdev_find_recursive(BusState *bus, const char *id);
+DeviceState *find_device_state(const char *id, Error **errp);
 
 /* Returns 0 to walk children, > 0 to skip walk, < 0 to terminate walk. */
 typedef int (qbus_walkerfn)(BusState *bus, void *opaque);
diff --git a/include/io/proxy-link.h b/include/io/proxy-link.h
index 8781508..ce2fcc3 100644
--- a/include/io/proxy-link.h
+++ b/include/io/proxy-link.h
@@ -61,6 +61,7 @@ typedef struct ProxyLinkState ProxyLinkState;
  * SET_IRQFD        Sets the IRQFD to be used to raise interrupts directly
  *                  from remote device
  * DEVICE_ADD       QMP/HMP command to hotplug device
+ * DEVICE_DEL       QMP/HMP command to hot-unplug device
  *
  */
 typedef enum {
@@ -72,6 +73,7 @@ typedef enum {
     BAR_READ,
     SET_IRQFD,
     DEVICE_ADD,
+    DEVICE_DEL,
     MAX,
 } proc_cmd_t;
 
diff --git a/include/monitor/qdev.h b/include/monitor/qdev.h
index 065701c..0ca0833 100644
--- a/include/monitor/qdev.h
+++ b/include/monitor/qdev.h
@@ -12,6 +12,7 @@ void qmp_device_add(QDict *qdict, QObject **ret_data, Error **errp);
 
 #ifdef CONFIG_MPQEMU
 void qmp_rdevice_add(QDict *qdict, QObject **ret_data, Error **errp);
+void qmp_rdevice_del(QDict *qdict, QObject **ret_data, Error **errp);
 #endif
 
 int qdev_device_help(QemuOpts *opts);
diff --git a/monitor.c b/monitor.c
index 0ad52e5..6686c80 100644
--- a/monitor.c
+++ b/monitor.c
@@ -1158,6 +1158,8 @@ static void monitor_init_qmp_commands(void)
 #ifdef CONFIG_MPQEMU
     qmp_register_command(&qmp_commands, "rdevice_add", qmp_rdevice_add,
                          QCO_NO_OPTIONS);
+    qmp_register_command(&qmp_commands, "rdevice_del", qmp_rdevice_del,
+                         QCO_NO_OPTIONS);
 #endif
 
     QTAILQ_INIT(&qmp_cap_negotiation_commands);
diff --git a/qapi/misc.json b/qapi/misc.json
index 28d49ea..bc1d2ec 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -1717,6 +1717,33 @@
 { 'command': 'device_del', 'data': {'id': 'str'} }
 
 ##
+# @rdevice_del:
+#
+# Remove a device from a guest
+#
+# @rdev_id: ID of the remote device root
+#
+# @id: the device's ID
+#
+# Returns: Nothing on success
+#          If @id is not a valid device, DeviceNotFound
+#
+# Notes: When this command completes, the device may not be removed from the
+#        guest.  Hot removal is an operation that requires guest cooperation.
+#        This command merely requests that the guest begin the hot removal
+#        process.  Completion of the device removal process is signaled with a
+#        DEVICE_DELETED event. Guest reset will automatically complete removal
+#        for all devices.
+#
+# Since: 3.0
+#
+##
+{ 'command': 'rdevice_del',
+  'data': {'rdev_id': 'str', 'id': 'str'},
+  'if': 'defined(CONFIG_MPQEMU)',
+  'gen': false }
+
+##
 # @DEVICE_DELETED:
 #
 # Emitted whenever the device removal completion is acknowledged by the guest.
diff --git a/qdev-monitor.c b/qdev-monitor.c
index d432098..46d0ad0 100644
--- a/qdev-monitor.c
+++ b/qdev-monitor.c
@@ -828,7 +828,7 @@ void qmp_device_add(QDict *qdict, QObject **ret_data, Error **errp)
     object_unref(OBJECT(dev));
 }
 
-static DeviceState *find_device_state(const char *id, Error **errp)
+DeviceState *find_device_state(const char *id, Error **errp)
 {
     Object *obj;
 
diff --git a/remote/remote-main.c b/remote/remote-main.c
index 03c14de..a9c12a9 100644
--- a/remote/remote-main.c
+++ b/remote/remote-main.c
@@ -53,6 +53,7 @@
 #include "qemu/option.h"
 #include "qemu/config-file.h"
 #include "monitor/qdev.h"
+#include "qapi/qmp/qdict.h"
 
 static ProxyLinkState *proxy_link;
 PCIDevice *remote_pci_dev;
@@ -181,6 +182,47 @@ fail:
     PUT_REMOTE_WAIT(wait);
 }
 
+static void process_device_del_msg(ProcMsg *msg)
+{
+    Error *local_err = NULL;
+    DeviceState *dev = NULL;
+    const char *json = (const char *)msg->data2;
+    int wait = msg->fds[0];
+    QObject *qobj = NULL;
+    QDict *qdict = NULL;
+    const char *id;
+
+    qobj = qobject_from_json(json, &local_err);
+    if (local_err) {
+        goto fail;
+    }
+
+    qdict = qobject_to(QDict, qobj);
+    assert(qdict);
+
+    id = qdict_get_try_str(qdict, "id");
+    assert(id);
+
+    dev = find_device_state(id, &local_err);
+    if (local_err) {
+        goto fail;
+    }
+
+    if (dev) {
+        qdev_unplug(dev, &local_err);
+    }
+
+fail:
+    if (local_err) {
+        error_report_err(local_err);
+        /* TODO: communicate the exact error message to proxy */
+    }
+
+    notify_proxy(wait, 1);
+
+    PUT_REMOTE_WAIT(wait);
+}
+
 static void process_msg(GIOCondition cond)
 {
     ProcMsg *msg = NULL;
@@ -235,6 +277,9 @@ static void process_msg(GIOCondition cond)
     case DEVICE_ADD:
         process_device_add_msg(msg);
         break;
+    case DEVICE_DEL:
+        process_device_del_msg(msg);
+        break;
     default:
         error_setg(&err, "Unknown command");
         goto finalize_loop;
