soc: qcom: smd: Split discovery and state change work
authorBjorn Andersson <bjorn.andersson@sonymobile.com>
Thu, 18 Feb 2016 06:39:03 +0000 (22:39 -0800)
committerAndy Gross <andy.gross@linaro.org>
Wed, 30 Mar 2016 22:20:51 +0000 (17:20 -0500)
Split the two steps of channel discovery and state change handling into
two different workers. This allows for new channels to be found while
we're are probing, which is required as we introduce multi-channel
support.

Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com>
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
Signed-off-by: Andy Gross <andy.gross@linaro.org>
drivers/soc/qcom/smd.c

index c357842b92e1111c026a064c45a10acb000a7e04..e8972ddfee85de8d5d7a3a495b49197c4d2ef609 100644 (file)
@@ -106,9 +106,9 @@ static const struct {
  * @channels:          list of all channels detected on this edge
  * @channels_lock:     guard for modifications of @channels
  * @allocated:         array of bitmaps representing already allocated channels
- * @need_rescan:       flag that the @work needs to scan smem for new channels
  * @smem_available:    last available amount of smem triggering a channel scan
- * @work:              work item for edge house keeping
+ * @scan_work:         work item for discovering new channels
+ * @state_work:                work item for edge state changes
  */
 struct qcom_smd_edge {
        struct qcom_smd *smd;
@@ -127,10 +127,10 @@ struct qcom_smd_edge {
 
        DECLARE_BITMAP(allocated[SMD_ALLOC_TBL_COUNT], SMD_ALLOC_TBL_SIZE);
 
-       bool need_rescan;
        unsigned smem_available;
 
-       struct work_struct work;
+       struct work_struct scan_work;
+       struct work_struct state_work;
 };
 
 /*
@@ -614,7 +614,8 @@ static irqreturn_t qcom_smd_edge_intr(int irq, void *data)
        struct qcom_smd_edge *edge = data;
        struct qcom_smd_channel *channel;
        unsigned available;
-       bool kick_worker = false;
+       bool kick_scanner = false;
+       bool kick_state = false;
 
        /*
         * Handle state changes or data on each of the channels on this edge
@@ -622,7 +623,7 @@ static irqreturn_t qcom_smd_edge_intr(int irq, void *data)
        spin_lock(&edge->channels_lock);
        list_for_each_entry(channel, &edge->channels, list) {
                spin_lock(&channel->recv_lock);
-               kick_worker |= qcom_smd_channel_intr(channel);
+               kick_state |= qcom_smd_channel_intr(channel);
                spin_unlock(&channel->recv_lock);
        }
        spin_unlock(&edge->channels_lock);
@@ -635,12 +636,13 @@ static irqreturn_t qcom_smd_edge_intr(int irq, void *data)
        available = qcom_smem_get_free_space(edge->remote_pid);
        if (available != edge->smem_available) {
                edge->smem_available = available;
-               edge->need_rescan = true;
-               kick_worker = true;
+               kick_scanner = true;
        }
 
-       if (kick_worker)
-               schedule_work(&edge->work);
+       if (kick_scanner)
+               schedule_work(&edge->scan_work);
+       if (kick_state)
+               schedule_work(&edge->state_work);
 
        return IRQ_HANDLED;
 }
@@ -1098,8 +1100,9 @@ free_name_and_channel:
  * qcom_smd_create_channel() to create representations of these and add
  * them to the edge's list of channels.
  */
-static void qcom_discover_channels(struct qcom_smd_edge *edge)
+static void qcom_channel_scan_worker(struct work_struct *work)
 {
+       struct qcom_smd_edge *edge = container_of(work, struct qcom_smd_edge, scan_work);
        struct qcom_smd_alloc_entry *alloc_tbl;
        struct qcom_smd_alloc_entry *entry;
        struct qcom_smd_channel *channel;
@@ -1152,7 +1155,7 @@ static void qcom_discover_channels(struct qcom_smd_edge *edge)
                }
        }
 
-       schedule_work(&edge->work);
+       schedule_work(&edge->state_work);
 }
 
 /*
@@ -1160,29 +1163,23 @@ static void qcom_discover_channels(struct qcom_smd_edge *edge)
  * then scans all registered channels for state changes that should be handled
  * by creating or destroying smd client devices for the registered channels.
  *
- * LOCKING: edge->channels_lock is not needed to be held during the traversal
- * of the channels list as it's done synchronously with the only writer.
+ * LOCKING: edge->channels_lock only needs to cover the list operations, as the
+ * worker is killed before any channels are deallocated
  */
 static void qcom_channel_state_worker(struct work_struct *work)
 {
        struct qcom_smd_channel *channel;
        struct qcom_smd_edge *edge = container_of(work,
                                                  struct qcom_smd_edge,
-                                                 work);
+                                                 state_work);
        unsigned remote_state;
-
-       /*
-        * Rescan smem if we have reason to belive that there are new channels.
-        */
-       if (edge->need_rescan) {
-               edge->need_rescan = false;
-               qcom_discover_channels(edge);
-       }
+       unsigned long flags;
 
        /*
         * Register a device for any closed channel where the remote processor
         * is showing interest in opening the channel.
         */
+       spin_lock_irqsave(&edge->channels_lock, flags);
        list_for_each_entry(channel, &edge->channels, list) {
                if (channel->state != SMD_CHANNEL_CLOSED)
                        continue;
@@ -1192,7 +1189,9 @@ static void qcom_channel_state_worker(struct work_struct *work)
                    remote_state != SMD_CHANNEL_OPENED)
                        continue;
 
+               spin_unlock_irqrestore(&edge->channels_lock, flags);
                qcom_smd_create_device(channel);
+               spin_lock_irqsave(&edge->channels_lock, flags);
        }
 
        /*
@@ -1209,8 +1208,11 @@ static void qcom_channel_state_worker(struct work_struct *work)
                    remote_state == SMD_CHANNEL_OPENED)
                        continue;
 
+               spin_unlock_irqrestore(&edge->channels_lock, flags);
                qcom_smd_destroy_device(channel);
+               spin_lock_irqsave(&edge->channels_lock, flags);
        }
+       spin_unlock_irqrestore(&edge->channels_lock, flags);
 }
 
 /*
@@ -1228,7 +1230,8 @@ static int qcom_smd_parse_edge(struct device *dev,
        INIT_LIST_HEAD(&edge->channels);
        spin_lock_init(&edge->channels_lock);
 
-       INIT_WORK(&edge->work, qcom_channel_state_worker);
+       INIT_WORK(&edge->scan_work, qcom_channel_scan_worker);
+       INIT_WORK(&edge->state_work, qcom_channel_state_worker);
 
        edge->of_node = of_node_get(node);
 
@@ -1317,8 +1320,7 @@ static int qcom_smd_probe(struct platform_device *pdev)
                if (ret)
                        continue;
 
-               edge->need_rescan = true;
-               schedule_work(&edge->work);
+               schedule_work(&edge->scan_work);
        }
 
        platform_set_drvdata(pdev, smd);
@@ -1341,8 +1343,10 @@ static int qcom_smd_remove(struct platform_device *pdev)
                edge = &smd->edges[i];
 
                disable_irq(edge->irq);
-               cancel_work_sync(&edge->work);
+               cancel_work_sync(&edge->scan_work);
+               cancel_work_sync(&edge->state_work);
 
+               /* No need to lock here, because the writer is gone */
                list_for_each_entry(channel, &edge->channels, list) {
                        if (!channel->qsdev)
                                continue;