OSDN Git Service

usb: pd: Make select_pdo_store() wait until request goes through
authorJack Pham <jackp@codeaurora.org>
Thu, 26 Jan 2017 21:28:19 +0000 (13:28 -0800)
committerJack Pham <jackp@codeaurora.org>
Sat, 11 Feb 2017 01:42:06 +0000 (17:42 -0800)
When sending a request through the 'select_pdo' sysfs file,
make sure the request has gone through and is acknowledged
by the source before returning. This allows for checking if
the request was rejected so that an error can be returned.

Rename the 'swap_complete' completion variable to 'is_ready'
so it can be reused in this context. Also add a mutex so that
select_pdo will not contend with a dual_role swap.

Change-Id: Ie8b088faa15c67915e3bd96972b4f59f0cc66afe
Signed-off-by: Jack Pham <jackp@codeaurora.org>
drivers/usb/pd/policy_engine.c

index 611750e..43fbc5a 100644 (file)
@@ -338,8 +338,10 @@ struct usbpd {
        enum power_role         current_pr;
        bool                    in_pr_swap;
        bool                    pd_phy_opened;
-       struct completion       swap_complete;
+       bool                    send_request;
+       struct completion       is_ready;
 
+       struct mutex            swap_lock;
        struct dual_role_phy_instance   *dual_role;
        struct dual_role_phy_desc       dr_desc;
        bool                    send_pr_swap;
@@ -463,6 +465,9 @@ static inline void pd_reset_protocol(struct usbpd *pd)
         */
        pd->rx_msgid = -1;
        pd->tx_msgid = 0;
+       pd->send_request = false;
+       pd->send_pr_swap = false;
+       pd->send_dr_swap = false;
 }
 
 static int pd_send_msg(struct usbpd *pd, u8 hdr_type, const u32 *data,
@@ -842,7 +847,7 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
                }
 
                kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
-               complete(&pd->swap_complete);
+               complete(&pd->is_ready);
                dual_role_instance_changed(pd->dual_role);
                break;
 
@@ -977,7 +982,7 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
        case PE_SNK_READY:
                pd->in_explicit_contract = true;
                kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
-               complete(&pd->swap_complete);
+               complete(&pd->is_ready);
                dual_role_instance_changed(pd->dual_role);
                break;
 
@@ -2075,6 +2080,9 @@ static void usbpd_sm(struct work_struct *w)
                        vconn_swap(pd);
                } else if (IS_DATA(rx_msg, MSG_VDM)) {
                        handle_vdm_rx(pd, rx_msg);
+               } else if (pd->send_request) {
+                       pd->send_request = false;
+                       usbpd_set_state(pd, PE_SNK_SELECT_CAPABILITY);
                } else if (pd->send_pr_swap && is_sink_tx_ok(pd)) {
                        pd->send_pr_swap = false;
                        ret = pd_send_msg(pd, MSG_PR_SWAP, NULL, 0, SOP_MSG);
@@ -2540,17 +2548,21 @@ static int usbpd_dr_set_property(struct dual_role_phy_instance *dual_role,
                                return -EAGAIN;
                        }
 
-                       reinit_completion(&pd->swap_complete);
+                       mutex_lock(&pd->swap_lock);
+                       reinit_completion(&pd->is_ready);
                        pd->send_dr_swap = true;
                        kick_sm(pd, 0);
 
                        /* wait for operation to complete */
-                       if (!wait_for_completion_timeout(&pd->swap_complete,
+                       if (!wait_for_completion_timeout(&pd->is_ready,
                                        msecs_to_jiffies(100))) {
                                usbpd_err(&pd->dev, "data_role swap timed out\n");
+                               mutex_unlock(&pd->swap_lock);
                                return -ETIMEDOUT;
                        }
 
+                       mutex_unlock(&pd->swap_lock);
+
                        if ((*val == DUAL_ROLE_PROP_DR_HOST &&
                                        pd->current_dr != DR_DFP) ||
                                (*val == DUAL_ROLE_PROP_DR_DEVICE &&
@@ -2591,17 +2603,21 @@ static int usbpd_dr_set_property(struct dual_role_phy_instance *dual_role,
                                return -EAGAIN;
                        }
 
-                       reinit_completion(&pd->swap_complete);
+                       mutex_lock(&pd->swap_lock);
+                       reinit_completion(&pd->is_ready);
                        pd->send_pr_swap = true;
                        kick_sm(pd, 0);
 
                        /* wait for operation to complete */
-                       if (!wait_for_completion_timeout(&pd->swap_complete,
+                       if (!wait_for_completion_timeout(&pd->is_ready,
                                        msecs_to_jiffies(2000))) {
                                usbpd_err(&pd->dev, "power_role swap timed out\n");
+                               mutex_unlock(&pd->swap_lock);
                                return -ETIMEDOUT;
                        }
 
+                       mutex_unlock(&pd->swap_lock);
+
                        if ((*val == DUAL_ROLE_PROP_PR_SRC &&
                                        pd->current_pr != PR_SRC) ||
                                (*val == DUAL_ROLE_PROP_PR_SNK &&
@@ -2864,36 +2880,62 @@ static ssize_t select_pdo_store(struct device *dev,
        int pdo, uv = 0, ua = 0;
        int ret;
 
+       mutex_lock(&pd->swap_lock);
+
        /* Only allowed if we are already in explicit sink contract */
        if (pd->current_state != PE_SNK_READY || !is_sink_tx_ok(pd)) {
                usbpd_err(&pd->dev, "select_pdo: Cannot select new PDO yet\n");
-               return -EBUSY;
+               ret = -EBUSY;
+               goto out;
        }
 
        ret = sscanf(buf, "%d %d %d %d", &src_cap_id, &pdo, &uv, &ua);
        if (ret != 2 && ret != 4) {
                usbpd_err(&pd->dev, "select_pdo: Must specify <src cap id> <PDO> [<uV> <uA>]\n");
-               return -EINVAL;
+               ret = -EINVAL;
+               goto out;
        }
 
        if (src_cap_id != pd->src_cap_id) {
                usbpd_err(&pd->dev, "select_pdo: src_cap_id mismatch.  Requested:%d, current:%d\n",
                                src_cap_id, pd->src_cap_id);
-               return -EINVAL;
+               ret = -EINVAL;
+               goto out;
        }
 
        if (pdo < 1 || pdo > 7) {
                usbpd_err(&pd->dev, "select_pdo: invalid PDO:%d\n", pdo);
-               return -EINVAL;
+               ret = -EINVAL;
+               goto out;
        }
 
        ret = pd_select_pdo(pd, pdo, uv, ua);
        if (ret)
-               return ret;
+               goto out;
 
-       usbpd_set_state(pd, PE_SNK_SELECT_CAPABILITY);
+       reinit_completion(&pd->is_ready);
+       pd->send_request = true;
+       kick_sm(pd, 0);
 
-       return size;
+       /* wait for operation to complete */
+       if (!wait_for_completion_timeout(&pd->is_ready,
+                       msecs_to_jiffies(1000))) {
+               usbpd_err(&pd->dev, "select_pdo: request timed out\n");
+               ret = -ETIMEDOUT;
+               goto out;
+       }
+
+       /* determine if request was accepted/rejected */
+       if (pd->selected_pdo != pd->requested_pdo ||
+                       pd->current_voltage != pd->requested_voltage) {
+               usbpd_err(&pd->dev, "select_pdo: request rejected\n");
+               ret = -EINVAL;
+       }
+
+out:
+       pd->send_request = false;
+       mutex_unlock(&pd->swap_lock);
+       return ret ? ret : size;
 }
 
 static ssize_t select_pdo_show(struct device *dev,
@@ -3123,6 +3165,7 @@ struct usbpd *usbpd_create(struct device *parent)
        INIT_WORK(&pd->sm_work, usbpd_sm);
        hrtimer_init(&pd->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
        pd->timer.function = pd_timeout;
+       mutex_init(&pd->swap_lock);
 
        pd->usb_psy = power_supply_get_by_name("usb");
        if (!pd->usb_psy) {
@@ -3233,7 +3276,7 @@ struct usbpd *usbpd_create(struct device *parent)
        spin_lock_init(&pd->rx_lock);
        INIT_LIST_HEAD(&pd->rx_q);
        INIT_LIST_HEAD(&pd->svid_handlers);
-       init_completion(&pd->swap_complete);
+       init_completion(&pd->is_ready);
 
        pd->psy_nb.notifier_call = psy_changed;
        ret = power_supply_reg_notifier(&pd->psy_nb);