]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/base/firmware_class.c
firmware loader: fix one reqeust_firmware race
[karo-tx-linux.git] / drivers / base / firmware_class.c
index 81541452887bd3ce5d868909e3fb753fb3d6b124..f2882511a9c1993bd708be3979803688e292f812 100644 (file)
@@ -423,6 +423,18 @@ static void firmware_free_data(const struct firmware *fw)
 #ifndef PAGE_KERNEL_RO
 #define PAGE_KERNEL_RO PAGE_KERNEL
 #endif
+
+/* one pages buffer should be mapped/unmapped only once */
+static int fw_map_pages_buf(struct firmware_buf *buf)
+{
+       if (buf->data)
+               vunmap(buf->data);
+       buf->data = vmap(buf->pages, buf->nr_pages, 0, PAGE_KERNEL_RO);
+       if (!buf->data)
+               return -ENOMEM;
+       return 0;
+}
+
 /**
  * firmware_loading_store - set value in the 'loading' control file
  * @dev: device pointer
@@ -467,6 +479,14 @@ static ssize_t firmware_loading_store(struct device *dev,
                if (test_bit(FW_STATUS_LOADING, &fw_buf->status)) {
                        set_bit(FW_STATUS_DONE, &fw_buf->status);
                        clear_bit(FW_STATUS_LOADING, &fw_buf->status);
+
+                       /*
+                        * Several loading requests may be pending on
+                        * one same firmware buf, so let all requests
+                        * see the mapped 'buf->data' once the loading
+                        * is completed.
+                        * */
+                       fw_map_pages_buf(fw_buf);
                        complete_all(&fw_buf->completion);
                        break;
                }
@@ -670,15 +690,6 @@ exit:
        return fw_priv;
 }
 
-/* one pages buffer is mapped/unmapped only once */
-static int fw_map_pages_buf(struct firmware_buf *buf)
-{
-       buf->data = vmap(buf->pages, buf->nr_pages, 0, PAGE_KERNEL_RO);
-       if (!buf->data)
-               return -ENOMEM;
-       return 0;
-}
-
 /* store the pages buffer info firmware from buf */
 static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw)
 {
@@ -884,9 +895,6 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
        if (!retval && f_dev->parent)
                fw_add_devm_name(f_dev->parent, buf->fw_id);
 
-       if (!retval)
-               retval = fw_map_pages_buf(buf);
-
        /*
         * After caching firmware image is started, let it piggyback
         * on request firmware.
@@ -1142,17 +1150,27 @@ exit:
        return fce;
 }
 
-static int fw_cache_piggyback_on_request(const char *name)
+static int __fw_entry_found(const char *name)
 {
        struct firmware_cache *fwc = &fw_cache;
        struct fw_cache_entry *fce;
-       int ret = 0;
 
-       spin_lock(&fwc->name_lock);
        list_for_each_entry(fce, &fwc->fw_names, list) {
                if (!strcmp(fce->name, name))
-                       goto found;
+                       return 1;
        }
+       return 0;
+}
+
+static int fw_cache_piggyback_on_request(const char *name)
+{
+       struct firmware_cache *fwc = &fw_cache;
+       struct fw_cache_entry *fce;
+       int ret = 0;
+
+       spin_lock(&fwc->name_lock);
+       if (__fw_entry_found(name))
+               goto found;
 
        fce = alloc_fw_cache_entry(name);
        if (fce) {
@@ -1229,11 +1247,19 @@ static void dev_cache_fw_image(struct device *dev, void *data)
                list_del(&fce->list);
 
                spin_lock(&fwc->name_lock);
-               fwc->cnt++;
-               list_add(&fce->list, &fwc->fw_names);
+               /* only one cache entry for one firmware */
+               if (!__fw_entry_found(fce->name)) {
+                       fwc->cnt++;
+                       list_add(&fce->list, &fwc->fw_names);
+               } else {
+                       free_fw_cache_entry(fce);
+                       fce = NULL;
+               }
                spin_unlock(&fwc->name_lock);
 
-               async_schedule(__async_dev_cache_fw_image, (void *)fce);
+               if (fce)
+                       async_schedule(__async_dev_cache_fw_image,
+                                      (void *)fce);
        }
 }
 
@@ -1275,6 +1301,9 @@ static void device_cache_fw_images(void)
 
        pr_debug("%s\n", __func__);
 
+       /* cancel uncache work */
+       cancel_delayed_work_sync(&fwc->work);
+
        /*
         * use small loading timeout for caching devices' firmware
         * because all these firmware images have been loaded