if (gb_connection_is_static(connection))
return;
- if (gb_connection_is_control(connection))
+ control = connection->intf->control;
+
+ if (gb_connection_is_control(connection)) {
+ if (connection->mode_switch) {
+ ret = gb_control_mode_switch_operation(control);
+ if (ret) {
+ /*
+ * Allow mode switch to time out waiting for
+ * mailbox event.
+ */
+ return;
+ }
+ }
+
return;
+ }
- control = connection->intf->control;
ret = gb_control_disconnected_operation(control, cport_id);
if (ret) {
}
EXPORT_SYMBOL_GPL(gb_connection_disable_rx);
+void gb_connection_mode_switch_prepare(struct gb_connection *connection)
+{
+ connection->mode_switch = true;
+}
+
+void gb_connection_mode_switch_complete(struct gb_connection *connection)
+{
+ gb_connection_svc_connection_destroy(connection);
+ gb_connection_hd_cport_disable(connection);
+ connection->mode_switch = false;
+}
+
void gb_connection_disable(struct gb_connection *connection)
{
mutex_lock(&connection->mutex);
connection->state = GB_CONNECTION_STATE_DISABLED;
- gb_connection_svc_connection_destroy(connection);
- gb_connection_hd_cport_disable(connection);
+ /* control-connection tear down is deferred when mode switching */
+ if (!connection->mode_switch) {
+ gb_connection_svc_connection_destroy(connection);
+ gb_connection_hd_cport_disable(connection);
+ }
out_unlock:
mutex_unlock(&connection->mutex);
*/
#include "greybus.h"
-
#include "greybus_trace.h"
+#define GB_INTERFACE_MODE_SWITCH_TIMEOUT 1000
+
#define GB_INTERFACE_DEVICE_ID_BAD 0xff
/* Don't-care selector index */
intf->device_id = GB_INTERFACE_DEVICE_ID_BAD;
}
+/* Locking: Caller holds the interface mutex. */
+static int gb_interface_legacy_mode_switch(struct gb_interface *intf)
+{
+ int ret;
+
+ dev_info(&intf->dev, "legacy mode switch detected\n");
+
+ /* Mark as disconnected to prevent I/O during disable. */
+ intf->disconnected = true;
+ gb_interface_disable(intf);
+ intf->disconnected = false;
+
+ ret = gb_interface_enable(intf);
+ if (ret) {
+ dev_err(&intf->dev, "failed to re-enable interface: %d\n", ret);
+ gb_interface_deactivate(intf);
+ }
+
+ return ret;
+}
+
+void gb_interface_mailbox_event(struct gb_interface *intf, u16 result,
+ u32 mailbox)
+{
+ mutex_lock(&intf->mutex);
+
+ if (result) {
+ dev_warn(&intf->dev,
+ "mailbox event with UniPro error: 0x%04x\n",
+ result);
+ goto err_disable;
+ }
+
+ if (mailbox != GB_SVC_INTF_MAILBOX_GREYBUS) {
+ dev_warn(&intf->dev,
+ "mailbox event with unexpected value: 0x%08x\n",
+ mailbox);
+ goto err_disable;
+ }
+
+ if (intf->quirks & GB_INTERFACE_QUIRK_LEGACY_MODE_SWITCH) {
+ gb_interface_legacy_mode_switch(intf);
+ goto out_unlock;
+ }
+
+ if (!intf->mode_switch) {
+ dev_warn(&intf->dev, "unexpected mailbox event: 0x%08x\n",
+ mailbox);
+ goto err_disable;
+ }
+
+ dev_info(&intf->dev, "mode switch detected\n");
+
+ complete(&intf->mode_switch_completion);
+
+out_unlock:
+ mutex_unlock(&intf->mutex);
+
+ return;
+
+err_disable:
+ gb_interface_disable(intf);
+ gb_interface_deactivate(intf);
+ mutex_unlock(&intf->mutex);
+}
+
+static void gb_interface_mode_switch_work(struct work_struct *work)
+{
+ struct gb_interface *intf;
+ struct gb_control *control;
+ unsigned long timeout;
+ int ret;
+
+ intf = container_of(work, struct gb_interface, mode_switch_work);
+
+ mutex_lock(&intf->mutex);
+ /* Make sure interface is still enabled. */
+ if (!intf->enabled) {
+ dev_dbg(&intf->dev, "mode switch aborted\n");
+ intf->mode_switch = false;
+ mutex_unlock(&intf->mutex);
+ goto out_interface_put;
+ }
+
+ /*
+ * Prepare the control device for mode switch and make sure to get an
+ * extra reference before it goes away during interface disable.
+ */
+ control = gb_control_get(intf->control);
+ gb_control_mode_switch_prepare(control);
+ gb_interface_disable(intf);
+ mutex_unlock(&intf->mutex);
+
+ timeout = msecs_to_jiffies(GB_INTERFACE_MODE_SWITCH_TIMEOUT);
+ ret = wait_for_completion_interruptible_timeout(
+ &intf->mode_switch_completion, timeout);
+
+ /* Finalise control-connection mode switch. */
+ gb_control_mode_switch_complete(control);
+ gb_control_put(control);
+
+ if (ret < 0) {
+ dev_err(&intf->dev, "mode switch interrupted\n");
+ goto err_deactivate;
+ } else if (ret == 0) {
+ dev_err(&intf->dev, "mode switch timed out\n");
+ goto err_deactivate;
+ }
+
+ /* Re-enable (re-enumerate) interface if still active. */
+ mutex_lock(&intf->mutex);
+ intf->mode_switch = false;
+ if (intf->active) {
+ ret = gb_interface_enable(intf);
+ if (ret) {
+ dev_err(&intf->dev, "failed to re-enable interface: %d\n",
+ ret);
+ gb_interface_deactivate(intf);
+ }
+ }
+ mutex_unlock(&intf->mutex);
+
+out_interface_put:
+ gb_interface_put(intf);
+
+ return;
+
+err_deactivate:
+ mutex_lock(&intf->mutex);
+ intf->mode_switch = false;
+ gb_interface_deactivate(intf);
+ mutex_unlock(&intf->mutex);
+
+ gb_interface_put(intf);
+}
+
+int gb_interface_request_mode_switch(struct gb_interface *intf)
+{
+ int ret = 0;
+
+ mutex_lock(&intf->mutex);
+ if (intf->mode_switch) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ intf->mode_switch = true;
+ reinit_completion(&intf->mode_switch_completion);
+
+ /*
+ * Get a reference to the interface device, which will be put once the
+ * mode switch is complete.
+ */
+ get_device(&intf->dev);
+
+ if (!queue_work(system_long_wq, &intf->mode_switch_work)) {
+ put_device(&intf->dev);
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+out_unlock:
+ mutex_unlock(&intf->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gb_interface_request_mode_switch);
+
/*
* T_TstSrcIncrement is written by the module on ES2 as a stand-in for the
* init-status attribute DME_TOSHIBA_INIT_STATUS. The AP needs to read and
* for example, requires E2EFC, CSD and CSV to be disabled.
*/
bootrom_quirks = GB_INTERFACE_QUIRK_NO_CPORT_FEATURES |
- GB_INTERFACE_QUIRK_FORCED_DISABLE;
+ GB_INTERFACE_QUIRK_FORCED_DISABLE |
+ GB_INTERFACE_QUIRK_LEGACY_MODE_SWITCH;
switch (init_status) {
case GB_INIT_BOOTROM_UNIPRO_BOOT_STARTED:
case GB_INIT_BOOTROM_FALLBACK_UNIPRO_BOOT_STARTED:
INIT_LIST_HEAD(&intf->bundles);
INIT_LIST_HEAD(&intf->manifest_descs);
mutex_init(&intf->mutex);
+ INIT_WORK(&intf->mode_switch_work, gb_interface_mode_switch_work);
+ init_completion(&intf->mode_switch_completion);
/* Invalid device id to start with */
intf->device_id = GB_INTERFACE_DEVICE_ID_BAD;
trace_gb_interface_deactivate(intf);
+ /* Abort any ongoing mode switch. */
+ if (intf->mode_switch)
+ complete(&intf->mode_switch_completion);
+
gb_interface_route_destroy(intf);
gb_interface_hibernate_link(intf);
gb_interface_unipro_set(intf, false);
return NULL;
}
-static void gb_svc_intf_reenable(struct gb_svc *svc, struct gb_interface *intf)
-{
- int ret;
-
- mutex_lock(&intf->mutex);
-
- /* Mark as disconnected to prevent I/O during disable. */
- intf->disconnected = true;
- gb_interface_disable(intf);
- intf->disconnected = false;
-
- ret = gb_interface_enable(intf);
- if (ret) {
- dev_err(&svc->dev, "failed to enable interface %u: %d\n",
- intf->interface_id, ret);
-
- gb_interface_deactivate(intf);
- }
-
- mutex_unlock(&intf->mutex);
-}
-
static void gb_svc_process_hello_deferred(struct gb_operation *operation)
{
struct gb_connection *connection = operation->connection;
/* All modules are considered 1x2 for now */
module = gb_svc_module_lookup(svc, intf_id);
if (module) {
- dev_info(&svc->dev, "mode switch detected on interface %u\n",
- intf_id);
-
- return gb_svc_intf_reenable(svc, module->interfaces[0]);
+ /* legacy mode switch */
+ return gb_interface_mailbox_event(module->interfaces[0], 0,
+ GB_SVC_INTF_MAILBOX_GREYBUS);
}
module = gb_module_create(hd, intf_id, 1);
return;
}
- if (result_code) {
- dev_warn(&svc->dev,
- "mailbox event %u with UniPro error: 0x%04x\n",
- intf_id, result_code);
- goto err_disable_interface;
- }
-
- if (mailbox != GB_SVC_INTF_MAILBOX_GREYBUS) {
- dev_warn(&svc->dev,
- "mailbox event %u with unexected value: 0x%08x\n",
- intf_id, mailbox);
- goto err_disable_interface;
- }
-
- dev_info(&svc->dev, "mode switch detected on interface %u\n", intf_id);
-
- gb_svc_intf_reenable(svc, intf);
-
- return;
-
-err_disable_interface:
- mutex_lock(&intf->mutex);
- gb_interface_disable(intf);
- gb_interface_deactivate(intf);
- mutex_unlock(&intf->mutex);
+ gb_interface_mailbox_event(intf, result_code, mailbox);
}
static void gb_svc_process_deferred_request(struct work_struct *work)