mirror of
https://github.com/archlinuxarm/PKGBUILDs.git
synced 2024-11-28 22:57:37 +00:00
2620 lines
79 KiB
Diff
2620 lines
79 KiB
Diff
From c1715f7de4f2b7c42f432d4a7eb02f3f954d34ca Mon Sep 17 00:00:00 2001
|
|
From: Kevin Mihelich <kevin@archlinuxarm.org>
|
|
Date: Sat, 6 Dec 2014 10:44:46 -0700
|
|
Subject: [PATCH] Backport firmware loader
|
|
|
|
commit 390ac45ceb37d70308194820e25b1cb885b67260
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Fri Aug 17 22:06:59 2012 +0800
|
|
|
|
PM / Sleep: introduce dpm_for_each_dev
|
|
|
|
dpm_list and its pm lock provide a good way to iterate all
|
|
devices in system. Except this way, there is no other easy
|
|
way to iterate devices in system.
|
|
|
|
firmware loader need to cache firmware images for devices
|
|
before system sleep, so introduce the function to meet its
|
|
demand.
|
|
|
|
Reported-by: Fengguang Wu <fengguang.wu@intel.com>
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 1dc17a9704a49c008a8d4cd8bdf03eccc54bcf84
|
|
Author: Kevin Mihelich <kevin@archlinuxarm.org>
|
|
Date: Wed Nov 26 13:03:08 2014 -0700
|
|
|
|
Revert "firmware loader: sync firmware cache by async_synchronize_full_domain"
|
|
|
|
This reverts commit d28d3882bd1fdb88ae4e02f11b091a92b0e5068b.
|
|
|
|
commit 6cf1b1b3016fb279d6a866151e75c94986be032b
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:26 2012 +0800
|
|
|
|
driver core: devres: introduce devres_for_each_res
|
|
|
|
This patch introduces one devres API of devres_for_each_res
|
|
so that the device's driver can iterate each resource it has
|
|
interest in.
|
|
|
|
The firmware loader will use the API to get each firmware name
|
|
from the device instance.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 9373da78351d15876f7508734147c25fa018f512
|
|
Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
|
Date: Thu May 3 18:15:13 2012 +0100
|
|
|
|
devres: Add devres_release()
|
|
|
|
APIs using devres frequently want to implement a "remove and free the
|
|
resource" operation so it seems sensible that they should be able to
|
|
just have devres do the freeing for them since that's a big part of what
|
|
devres is all about.
|
|
|
|
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 6183261d20682701b6ddd189060da4f8325e775c
|
|
Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
|
Date: Thu May 3 18:15:12 2012 +0100
|
|
|
|
devres: Clarify documentation for devres_destroy()
|
|
|
|
It's not massively obvious (at least to me) that removing and freeing a
|
|
resource does not involve calling the release function for the resource
|
|
but rather only removes the management of it. Make the documentation more
|
|
explicit.
|
|
|
|
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit bf4725b4926af17c246faf3b6dda250890b3e1ae
|
|
Author: Takashi Iwai <tiwai@suse.de>
|
|
Date: Thu Jan 31 11:13:57 2013 +0100
|
|
|
|
firmware: Ignore abort check when no user-helper is used
|
|
|
|
FW_STATUS_ABORT can be set only during the user-helper invocation,
|
|
thus we can ignore the check when CONFIG_HW_LOADER_USER_HELPER is
|
|
disabled.
|
|
|
|
Acked-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Takashi Iwai <tiwai@suse.de>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 5d5c8b23919037f575fb6370d87b875c86793834
|
|
Author: Takashi Iwai <tiwai@suse.de>
|
|
Date: Thu Jan 31 11:13:56 2013 +0100
|
|
|
|
firmware: Reduce ifdef CONFIG_FW_LOADER_USER_HELPER
|
|
|
|
By shuffling the code, reduce a few ifdefs in firmware_class.c.
|
|
Also, firmware_buf fmt field is changed to is_pages_buf boolean for
|
|
simplification.
|
|
|
|
Acked-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Takashi Iwai <tiwai@suse.de>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 861d2fc3539af167b5c4c9e81b5bf67300465cef
|
|
Author: Takashi Iwai <tiwai@suse.de>
|
|
Date: Thu Jan 31 11:13:55 2013 +0100
|
|
|
|
firmware: Make user-mode helper optional
|
|
|
|
This patch adds a new kconfig, CONFIG_FW_LOADER_USER_HELPER, and
|
|
guards the user-helper codes in firmware_class.c with ifdefs.
|
|
|
|
Yeah, yeah, there are lots of ifdefs in this patch. The further
|
|
clean-up with code shuffling follows in the next.
|
|
|
|
Acked-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Takashi Iwai <tiwai@suse.de>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit eb2e7c184b7e7bf2ae1968bd32c4354d28c75e69
|
|
Author: Takashi Iwai <tiwai@suse.de>
|
|
Date: Thu Jan 31 11:13:54 2013 +0100
|
|
|
|
firmware: Refactoring for splitting user-mode helper code
|
|
|
|
Since 3.7 kernel, the firmware loader can read the firmware files
|
|
directly, and the traditional user-mode helper is invoked only as a
|
|
fallback. This seems working pretty well, and the next step would be
|
|
to reduce the redundant user-mode helper stuff in future.
|
|
|
|
This patch is a preparation for that: refactor the code for splitting
|
|
user-mode helper stuff more easily. No functional change.
|
|
|
|
Acked-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Takashi Iwai <tiwai@suse.de>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 542061e36122107363fcf7c9aeffa957dee8bffc
|
|
Author: Luciano Coelho <coelho@ti.com>
|
|
Date: Tue Jan 15 10:43:43 2013 +0200
|
|
|
|
firmware: make sure the fw file size is not 0
|
|
|
|
If the requested firmware file size is 0 bytes in the filesytem, we
|
|
will try to vmalloc(0), which causes a warning:
|
|
|
|
vmalloc: allocation failure: 0 bytes
|
|
kworker/1:1: page allocation failure: order:0, mode:0xd2
|
|
__vmalloc_node_range+0x164/0x208
|
|
__vmalloc_node+0x4c/0x58
|
|
vmalloc+0x38/0x44
|
|
_request_firmware_load+0x220/0x6b0
|
|
request_firmware+0x64/0xc8
|
|
wl18xx_setup+0xb4/0x570 [wl18xx]
|
|
wlcore_nvs_cb+0x64/0x9f8 [wlcore]
|
|
request_firmware_work_func+0x94/0x100
|
|
process_one_work+0x1d0/0x750
|
|
worker_thread+0x184/0x4ac
|
|
kthread+0xb4/0xc0
|
|
|
|
To fix this, check whether the file size is less than or equal to zero
|
|
in fw_read_file_contents().
|
|
|
|
Cc: stable <stable@vger.kernel.org> [3.7]
|
|
Signed-off-by: Luciano Coelho <coelho@ti.com>
|
|
Acked-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
|
|
|
|
commit 906aa734d888dfeb855d50ebdaa1abafb4e16880
|
|
Author: Ming Lei <tom.leiming@gmail.com>
|
|
Date: Sat Nov 3 17:48:16 2012 +0800
|
|
|
|
firmware loader: document firmware cache mechanism
|
|
|
|
This patch documents the firmware cache mechanism so that
|
|
users of request_firmware() know that it can be called
|
|
safely inside device's suspend and resume callback, and
|
|
the device's firmware needn't be cached any more by individual
|
|
driver itself to deal with firmware loss during system resume.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 2a2f0be2bb74403c0079a823271e6ff7c2434735
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Nov 3 17:47:58 2012 +0800
|
|
|
|
firmware loader: introduce module parameter to customize(v4) fw search path
|
|
|
|
This patch introduces one module parameter of 'path' in firmware_class
|
|
to support customizing firmware image search path, so that people can
|
|
use its own firmware path if the default built-in paths can't meet their
|
|
demand[1], and the typical usage is passing the below from kernel command
|
|
parameter when 'firmware_class' is built in kernel:
|
|
|
|
firmware_class.path=$CUSTOMIZED_PATH
|
|
|
|
[1], https://lkml.org/lkml/2012/10/11/337
|
|
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
Conflicts:
|
|
Documentation/firmware_class/README
|
|
|
|
commit d6c6925a1c5091163df48d68ba38d2ec2003302e
|
|
Author: Cesar Eduardo Barros <cesarb@cesarb.net>
|
|
Date: Sat Oct 27 20:37:10 2012 -0200
|
|
|
|
firmware: use noinline_for_stack
|
|
|
|
The comment above fw_file_size() suggests it is noinline for stack size
|
|
reasons. Use noinline_for_stack to make this more clear.
|
|
|
|
Signed-off-by: Cesar Eduardo Barros <cesarb@cesarb.net>
|
|
Acked-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit f0d798c22f43f008b0b86ff58f2c68b522f1be90
|
|
Author: Chuansheng Liu <chuansheng.liu@intel.com>
|
|
Date: Sat Nov 10 01:27:22 2012 +0800
|
|
|
|
firmware loader: Fix the concurrent request_firmware() race for kref_get/put
|
|
|
|
There is one race that both request_firmware() with the same
|
|
firmware name.
|
|
|
|
The race scenerio is as below:
|
|
CPU1 CPU2
|
|
request_firmware() -->
|
|
_request_firmware_load() return err another request_firmware() is coming -->
|
|
_request_firmware_cleanup is called --> _request_firmware_prepare -->
|
|
release_firmware ---> fw_lookup_and_allocate_buf -->
|
|
spin_lock(&fwc->lock)
|
|
... __fw_lookup_buf() return true
|
|
fw_free_buf() will be called --> ...
|
|
kref_put -->
|
|
decrease the refcount to 0
|
|
kref_get(&tmp->ref) ==> it will trigger warning
|
|
due to refcount == 0
|
|
__fw_free_buf() -->
|
|
... spin_unlock(&fwc->lock)
|
|
spin_lock(&fwc->lock)
|
|
list_del(&buf->list)
|
|
spin_unlock(&fwc->lock)
|
|
kfree(buf)
|
|
After that, the freed buf will be used.
|
|
|
|
The key race is decreasing refcount to 0 and list_del is not protected together by
|
|
fwc->lock, and it is possible another thread try to get it between refcount==0
|
|
and list_del.
|
|
|
|
Fix it here to protect it together.
|
|
|
|
Acked-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: liu chuansheng <chuansheng.liu@intel.com>
|
|
Cc: stable <stable@vger.kernel.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 58af61921c9d1f6a8ffa097043bf78a2ff0bc44e
|
|
Author: Chuansheng Liu <chuansheng.liu@intel.com>
|
|
Date: Thu Nov 8 19:14:40 2012 +0800
|
|
|
|
firmware loader: Fix the race FW_STATUS_DONE is followed by class_timeout
|
|
|
|
There is a race as below when calling request_firmware():
|
|
CPU1 CPU2
|
|
write 0 > loading
|
|
mutex_lock(&fw_lock)
|
|
...
|
|
set_bit FW_STATUS_DONE class_timeout is coming
|
|
set_bit FW_STATUS_ABORT
|
|
complete_all &completion
|
|
...
|
|
mutex_unlock(&fw_lock)
|
|
|
|
In this time, the bit FW_STATUS_DONE and FW_STATUS_ABORT are set,
|
|
and request_firmware() will return failure due to condition in
|
|
_request_firmware_load():
|
|
if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status))
|
|
retval = -ENOENT;
|
|
|
|
But from the above scenerio, it should be a successful requesting.
|
|
So we need judge if the bit FW_STATUS_DONE is already set before
|
|
calling fw_load_abort() in timeout function.
|
|
|
|
As Ming's proposal, we need change the timer into sched_work to
|
|
benefit from using &fw_lock mutex also.
|
|
|
|
Signed-off-by: liu chuansheng <chuansheng.liu@intel.com>
|
|
Acked-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: stable <stable@vger.kernel.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit c6a3b74d6c42ca8aead23788768cdd358f672ed3
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Tue Oct 9 12:01:04 2012 +0800
|
|
|
|
firmware loader: sync firmware cache by async_synchronize_full_domain
|
|
|
|
async.c has provided synchronization mechanism on async_schedule_*,
|
|
so use async_synchronize_full_domain to sync caching firmware instead
|
|
of reinventing the wheel.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 9a27bd527d58a887a65bad24fd864122f860ef79
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Tue Oct 9 12:01:03 2012 +0800
|
|
|
|
firmware loader: let direct loading back on 'firmware_buf'
|
|
|
|
Firstly 'firmware_buf' is introduced to make all loading requests
|
|
to share one firmware kernel buffer, so firmware_buf should
|
|
be used in direct loading for saving memory and speedup firmware
|
|
loading.
|
|
|
|
Secondly, the commit below
|
|
|
|
abb139e75c2cdbb955e840d6331cb5863e409d0e(firmware:teach
|
|
the kernel to load firmware files directly from the filesystem)
|
|
|
|
introduces direct loading for fixing udev regression, but it
|
|
bypasses the firmware cache meachnism, so this patch enables
|
|
caching firmware for direct loading case since it is still needed
|
|
to solve drivers' dependency during system resume.
|
|
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 451768e107b4bd2beb054f3ec44b666910a8f6ce
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Tue Oct 9 12:01:02 2012 +0800
|
|
|
|
firmware loader: fix one reqeust_firmware race
|
|
|
|
Several loading requests may be pending on one same
|
|
firmware buf, and this patch moves fw_map_pages_buf()
|
|
before complete_all(&fw_buf->completion) and let all
|
|
requests see the mapped 'buf->data' once the loading
|
|
is completed.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 31159a04875c46fe2f8634479f5c2c07b3ebd6aa
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Tue Oct 9 12:01:01 2012 +0800
|
|
|
|
firmware loader: cancel uncache work before caching firmware
|
|
|
|
Under 'Opportunistic sleep' situation, system sleep might be
|
|
triggered very frequently, so the uncahce work may not be completed
|
|
before caching firmware during next suspend.
|
|
|
|
This patch cancels the uncache work before caching firmware to
|
|
fix the problem above.
|
|
|
|
Also this patch optimizes the cacheing firmware mechanism a bit by
|
|
only storing one firmware cache entry for one firmware image.
|
|
|
|
So if the firmware is still cached during suspend, it doesn't need
|
|
to be loaded from user space any more.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 4c7559d930c34c76a33b51f413c13f27367e95ff
|
|
Author: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Date: Thu Oct 4 09:19:02 2012 -0700
|
|
|
|
firmware: use 'kernel_read()' to read firmware into kernel buffer
|
|
|
|
Fengguang correctly points out that the firmware reading should not use
|
|
vfs_read(), since the buffer is in kernel space.
|
|
|
|
The vfs_read() just happened to work for kernel threads, but sparse
|
|
warns about the incorrect address spaces, and it's definitely incorrect
|
|
and could fail for other users of the firmware loading.
|
|
|
|
Reported-by: Fengguang Wu <fengguang.wu@intel.com>
|
|
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
|
|
|
|
commit 2047dd6d95ac6066e518c26cb5133f4787aefb52
|
|
Author: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Date: Wed Oct 3 15:58:32 2012 -0700
|
|
|
|
firmware: teach the kernel to load firmware files directly from the filesystem
|
|
|
|
This is a first step in allowing people to by-pass udev for loading
|
|
device firmware. Current versions of udev will deadlock (causing us to
|
|
block for the 30 second timeout) under some circumstances if the
|
|
firmware is loaded as part of the module initialization path, and this
|
|
is causing problems for media drivers in particular.
|
|
|
|
The current patch hardcodes the firmware path that udev uses by default,
|
|
and will fall back to the legacy udev mode if the firmware cannot be
|
|
found there. We'd like to add support for both configuring the paths
|
|
and the fallback behaviour, but in the meantime this hopefully fixes the
|
|
immediate problem, while also giving us a way forward.
|
|
|
|
[ v2: Some VFS layer interface cleanups suggested by Al Viro ]
|
|
[ v3: use the default udev paths suggested by Kay Sievers ]
|
|
|
|
Suggested-by: Ivan Kalvachev <ikalvachev@gmail.com>
|
|
Acked-by: Greg KH <gregkh@linuxfoundation.org>
|
|
Acked-by: Al Viro <viro@zeniv.linux.org.uk>
|
|
Cc: Mauro Carvalho Chehab <mchehab@redhat.com>
|
|
Cc: Kay Sievers <kay@redhat.com>
|
|
Cc: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
|
|
|
|
commit 7dc9f6ac529be96085a096c440823fe49f95f1cd
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Sep 8 17:32:30 2012 +0800
|
|
|
|
firmware loader: fix compile warning when CONFIG_PM=n
|
|
|
|
This patch replaces the previous macro of CONFIG_PM with
|
|
CONFIG_PM_SLEEP becasue firmware cache is only used in
|
|
system sleep situations.
|
|
|
|
Also this patch fixes the below compile warning when
|
|
CONFIG_PM=n:
|
|
|
|
drivers/base/firmware_class.c:1147: warning: 'device_cache_fw_images'
|
|
defined but not used
|
|
drivers/base/firmware_class.c:1212: warning:
|
|
'device_uncache_fw_images_delay' defined but not used
|
|
|
|
Reported-by: Andrew Morton <akpm@linux-foundation.org>
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit d4da2957c44390959fea1003188f449b3f38aba3
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Mon Aug 20 19:04:16 2012 +0800
|
|
|
|
firmware loader: let caching firmware piggyback on loading firmware
|
|
|
|
After starting caching firmware, there is still some time left
|
|
before devices are suspended, during the period, request_firmware
|
|
or its nowait version may still be triggered by the below situations
|
|
to load firmware images which can't be cached during suspend/resume
|
|
cycle.
|
|
|
|
- new devices added
|
|
- driver bind
|
|
- or device open kind of things
|
|
|
|
This patch utilizes the piggyback trick to cache firmware for
|
|
this kind of situation: just increase the firmware buf's reference
|
|
count and add the fw name entry into cache entry list after starting
|
|
caching firmware and before syscore_suspend() is called.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 580eb13b0fb703c49e729d42c313dc901145c278
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Mon Aug 20 19:04:15 2012 +0800
|
|
|
|
firmware loader: fix firmware -ENOENT situations
|
|
|
|
If the requested firmware image doesn't exist, firmware->priv
|
|
should be set for the later concurrent requests, otherwise
|
|
warning and oops will be triggered inside firmware_free_data().
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 529f2b00940c82abdf17bdc902e2b02d1904a789
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Fri Aug 17 22:07:00 2012 +0800
|
|
|
|
firmware loader: fix build failure if FW_LOADER is m
|
|
|
|
device_cache_fw_images need to iterate devices in system,
|
|
so this patch applies the introduced dpm_for_each_dev to
|
|
avoid link failure if CONFIG_FW_LOADER is m.
|
|
|
|
Reported-by: Fengguang Wu <fengguang.wu@intel.com>
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit e51f2ca3d0d22b9cd08b6262d6e8423888a339e6
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Fri Aug 17 22:06:58 2012 +0800
|
|
|
|
firmware loader: fix compile failure if !PM
|
|
|
|
'return 0' should be added to fw_pm_notify if !PM because
|
|
return value of the funcion is defined as 'int'.
|
|
|
|
Reported-by: Fengguang Wu <fengguang.wu@intel.com>
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 402bbcd0a468c1388fe9fd98a6bd0d4d57f1f5e4
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:29 2012 +0800
|
|
|
|
firmware loader: cache devices firmware during suspend/resume cycle
|
|
|
|
This patch implements caching devices' firmware automatically
|
|
during system syspend/resume cycle, so any device drivers can
|
|
call request_firmware or request_firmware_nowait inside resume
|
|
path to get the cached firmware if they have loaded firmwares
|
|
successfully at least once before entering suspend.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 6b5ef252737d7e46cee1471de1f8f778816ed432
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:28 2012 +0800
|
|
|
|
firmware loader: use small timeout for cache device firmware
|
|
|
|
Because device_cache_fw_images only cache the firmware which has been
|
|
loaded sucessfully at leat once, using a small loading timeout should
|
|
be reasonable.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 3a78247eb488608da2dbf2c0b5f312abc7fb36bf
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:27 2012 +0800
|
|
|
|
firmware: introduce device_cache/uncache_fw_images
|
|
|
|
This patch introduces the three helpers below:
|
|
|
|
void device_cache_fw_images(void)
|
|
void device_uncache_fw_images(void)
|
|
void device_uncache_fw_images_delay(unsigned long)
|
|
|
|
so we can use device_cache_fw_images() to cache firmware for
|
|
all devices which need firmware to work, and the device driver
|
|
can get the firmware easily from kernel memory when system isn't
|
|
ready for completing requests of loading firmware.
|
|
|
|
After system is ready for completing firmware loading, driver core
|
|
will call device_uncache_fw_images() or its delay version to free
|
|
the cached firmware.
|
|
|
|
The above helpers will be used to cache device firmware during
|
|
system suspend/resume cycle in the following patches.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit fec2fa2ad35e9b5ac3b556b57f2ce0d93d88dad3
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:25 2012 +0800
|
|
|
|
firmware loader: store firmware name into devres list
|
|
|
|
This patch will store firmware name into devres list of the device
|
|
which is requesting firmware loading, so that we can implement
|
|
auto cache and uncache firmware for devices in need.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 82502e82fdb519661d280f9a2ff7888a7e11719d
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:24 2012 +0800
|
|
|
|
firmware loader: fix comments on request_firmware_nowait
|
|
|
|
request_firmware_nowait is allowed to be called in atomic
|
|
context now if @gfp is GFP_ATOMIC, so fix the obsolete
|
|
comments and states which situations are suitable for using
|
|
it.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 885f2a4d3e79590a5ac56c600ef21c7f33edf1ff
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:23 2012 +0800
|
|
|
|
firmware loader: fix device lifetime
|
|
|
|
Callers of request_firmware* must hold the reference count of
|
|
@device, otherwise it is easy to trigger oops since the firmware
|
|
loader device is the child of @device.
|
|
|
|
This patch adds comments about the usage. In fact, most of drivers
|
|
call request_firmware* in its probe() or open(), so the constraint
|
|
should be reasonable and can be satisfied.
|
|
|
|
Also this patch holds the reference count of @device before
|
|
schedule_work() in request_firmware_nowait() to avoid that
|
|
the @device is released after request_firmware_nowait returns
|
|
and before the worker function is scheduled.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit f8c40c19896e07c5e24596d1c73e0004b83bf564
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:22 2012 +0800
|
|
|
|
firmware loader: introduce cache_firmware and uncache_firmware
|
|
|
|
This patches introduce two kernel APIs of cache_firmware and
|
|
uncache_firmware, both of which take the firmware file name
|
|
as the only parameter.
|
|
|
|
So any drivers can call cache_firmware to cache the specified
|
|
firmware file into kernel memory, and can use the cached firmware
|
|
in situations which can't request firmware from user space.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 33f7f4606f2add663f0f055d9e22224b2ed927e6
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:21 2012 +0800
|
|
|
|
firmware loader: always let firmware_buf own the pages buffer
|
|
|
|
This patch always let firmware_buf own the pages buffer allocated
|
|
inside firmware_data_write, and add all instances of firmware_buf
|
|
into the firmware cache global list. Also introduce one private field
|
|
in 'struct firmware', so release_firmware will see the instance of
|
|
firmware_buf associated with the current firmware instance, then just
|
|
'free' the instance of firmware_buf.
|
|
|
|
The firmware_buf instance represents one pages buffer for one
|
|
firmware image, so lots of firmware loading requests can share
|
|
the same firmware_buf instance if they request the same firmware
|
|
image file.
|
|
|
|
This patch will make implementation of the following cache_firmware/
|
|
uncache_firmware very easy and simple.
|
|
|
|
In fact, the patch improves request_formware/release_firmware:
|
|
|
|
- only request userspace to write firmware image once if
|
|
several devices share one same firmware image and its drivers
|
|
call request_firmware concurrently.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 23f444643c50ab3dc66e2cd699ef03d90dbdccc8
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:20 2012 +0800
|
|
|
|
firmware loader: introduce firmware_buf
|
|
|
|
This patch introduces struct firmware_buf to describe the buffer
|
|
which holds the firmware data, which will make the following
|
|
cache_firmware/uncache_firmware implemented easily.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit c341f46309619686830be4b608c9db06b68bafc1
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:19 2012 +0800
|
|
|
|
firmware loader: fix creation failure of fw loader device
|
|
|
|
If one device driver calls request_firmware_nowait() to request
|
|
several different firmwares' loading, device_add() will return
|
|
failure since all firmware loader device use same name of the
|
|
device who is requesting firmware.
|
|
|
|
This patch always use the name of firmware image as the firmware
|
|
loader device name to fix the problem since the following patches
|
|
for caching firmware will make sure only one loading for same
|
|
firmware is alllowd at the same time.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit fca2060e5dac12511c0860878e89e0e5ad8e7a3e
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:18 2012 +0800
|
|
|
|
firmware loader: remove unnecessary wmb()
|
|
|
|
The wmb() inside fw_load_abort is not necessary, since
|
|
complete() and wait_on_completion() has implied one pair
|
|
of memory barrier.
|
|
|
|
Also wmb() isn't a correct usage, so just remove it.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit 4eaaa8782ff182e0de3ac050aeb0305879dc89eb
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:17 2012 +0800
|
|
|
|
firmware loader: fix races during loading firmware
|
|
|
|
This patch fixes two races in loading firmware:
|
|
|
|
1, FW_STATUS_DONE should be set before waking up the task waitting
|
|
on _request_firmware_load, otherwise FW_STATUS_ABORT may be
|
|
thought as DONE mistakenly.
|
|
|
|
2, Inside _request_firmware_load(), there is a small window between
|
|
wait_for_completion() and mutex_lock(&fw_lock), and 'echo 1 > loading'
|
|
still may happen during the period, so this patch checks FW_STATUS_DONE
|
|
to prevent pages' buffer completed from being freed in firmware_loading_store.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit dc3ca115481cc50a7d4aa866f607b470280b1e5e
|
|
Author: Ming Lei <ming.lei@canonical.com>
|
|
Date: Sat Aug 4 12:01:16 2012 +0800
|
|
|
|
firmware loader: simplify pages ownership transfer
|
|
|
|
This patch doesn't transfer ownership of pages' buffer to the
|
|
instance of firmware until the firmware loading is completed,
|
|
which will simplify firmware_loading_store a lot, so help
|
|
to introduce the following cache_firmware and uncache_firmware
|
|
mechanism during system suspend-resume cycle.
|
|
|
|
In fact, this patch fixes one bug: if writing data into
|
|
firmware loader device is bypassed between writting 1 and 0 to
|
|
'loading', OOPS will be triggered without the patch.
|
|
|
|
Also handle the vmap failure case, and add some comments to make
|
|
code more readable.
|
|
|
|
Signed-off-by: Ming Lei <ming.lei@canonical.com>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
|
|
commit e728c819e3666bb37e08a49255b24e6933bb72e5
|
|
Author: Lars-Peter Clausen <lars@metafoo.de>
|
|
Date: Tue Jul 3 18:49:36 2012 +0200
|
|
|
|
driver-core: Use kobj_to_dev instead of re-implementing it
|
|
|
|
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
---
|
|
Documentation/firmware_class/README | 7 +
|
|
drivers/base/Kconfig | 11 +
|
|
drivers/base/core.c | 17 +-
|
|
drivers/base/devres.c | 77 +++
|
|
drivers/base/firmware_class.c | 1216 +++++++++++++++++++++++++++++------
|
|
drivers/base/power/main.c | 22 +
|
|
include/linux/device.h | 6 +
|
|
include/linux/firmware.h | 15 +
|
|
include/linux/pm.h | 5 +
|
|
9 files changed, 1177 insertions(+), 199 deletions(-)
|
|
|
|
diff --git a/Documentation/firmware_class/README b/Documentation/firmware_class/README
|
|
index 7eceaff..5fde1ef 100644
|
|
--- a/Documentation/firmware_class/README
|
|
+++ b/Documentation/firmware_class/README
|
|
@@ -106,3 +106,10 @@
|
|
on the setup, so I think that the choice on what firmware to make
|
|
persistent should be left to userspace.
|
|
|
|
+ about firmware cache:
|
|
+ --------------------
|
|
+ After firmware cache mechanism is introduced during system sleep,
|
|
+ request_firmware can be called safely inside device's suspend and
|
|
+ resume callback, and callers need't cache the firmware by
|
|
+ themselves any more for dealing with firmware loss during system
|
|
+ resume.
|
|
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
|
|
index 7c96a3a..b7af956 100644
|
|
--- a/drivers/base/Kconfig
|
|
+++ b/drivers/base/Kconfig
|
|
@@ -145,6 +145,17 @@ config EXTRA_FIRMWARE_DIR
|
|
this option you can point it elsewhere, such as /lib/firmware/ or
|
|
some other directory containing the firmware files.
|
|
|
|
+config FW_LOADER_USER_HELPER
|
|
+ bool "Fallback user-helper invocation for firmware loading"
|
|
+ depends on FW_LOADER
|
|
+ default y
|
|
+ help
|
|
+ This option enables / disables the invocation of user-helper
|
|
+ (e.g. udev) for loading firmware files as a fallback after the
|
|
+ direct file loading in kernel fails. The user-mode helper is
|
|
+ no longer required unless you have a special firmware file that
|
|
+ resides in a non-standard path.
|
|
+
|
|
config DEBUG_DRIVER
|
|
bool "Driver Core verbose debug messages"
|
|
depends on DEBUG_KERNEL
|
|
diff --git a/drivers/base/core.c b/drivers/base/core.c
|
|
index f428321..c360a22 100644
|
|
--- a/drivers/base/core.c
|
|
+++ b/drivers/base/core.c
|
|
@@ -84,14 +84,13 @@ const char *dev_driver_string(const struct device *dev)
|
|
}
|
|
EXPORT_SYMBOL(dev_driver_string);
|
|
|
|
-#define to_dev(obj) container_of(obj, struct device, kobj)
|
|
#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)
|
|
|
|
static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct device_attribute *dev_attr = to_dev_attr(attr);
|
|
- struct device *dev = to_dev(kobj);
|
|
+ struct device *dev = kobj_to_dev(kobj);
|
|
ssize_t ret = -EIO;
|
|
|
|
if (dev_attr->show)
|
|
@@ -107,7 +106,7 @@ static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct device_attribute *dev_attr = to_dev_attr(attr);
|
|
- struct device *dev = to_dev(kobj);
|
|
+ struct device *dev = kobj_to_dev(kobj);
|
|
ssize_t ret = -EIO;
|
|
|
|
if (dev_attr->store)
|
|
@@ -181,7 +180,7 @@ EXPORT_SYMBOL_GPL(device_show_int);
|
|
*/
|
|
static void device_release(struct kobject *kobj)
|
|
{
|
|
- struct device *dev = to_dev(kobj);
|
|
+ struct device *dev = kobj_to_dev(kobj);
|
|
struct device_private *p = dev->p;
|
|
|
|
if (dev->release)
|
|
@@ -199,7 +198,7 @@ static void device_release(struct kobject *kobj)
|
|
|
|
static const void *device_namespace(struct kobject *kobj)
|
|
{
|
|
- struct device *dev = to_dev(kobj);
|
|
+ struct device *dev = kobj_to_dev(kobj);
|
|
const void *ns = NULL;
|
|
|
|
if (dev->class && dev->class->ns_type)
|
|
@@ -220,7 +219,7 @@ static int dev_uevent_filter(struct kset *kset, struct kobject *kobj)
|
|
struct kobj_type *ktype = get_ktype(kobj);
|
|
|
|
if (ktype == &device_ktype) {
|
|
- struct device *dev = to_dev(kobj);
|
|
+ struct device *dev = kobj_to_dev(kobj);
|
|
if (dev->bus)
|
|
return 1;
|
|
if (dev->class)
|
|
@@ -231,7 +230,7 @@ static int dev_uevent_filter(struct kset *kset, struct kobject *kobj)
|
|
|
|
static const char *dev_uevent_name(struct kset *kset, struct kobject *kobj)
|
|
{
|
|
- struct device *dev = to_dev(kobj);
|
|
+ struct device *dev = kobj_to_dev(kobj);
|
|
|
|
if (dev->bus)
|
|
return dev->bus->name;
|
|
@@ -243,7 +242,7 @@ static const char *dev_uevent_name(struct kset *kset, struct kobject *kobj)
|
|
static int dev_uevent(struct kset *kset, struct kobject *kobj,
|
|
struct kobj_uevent_env *env)
|
|
{
|
|
- struct device *dev = to_dev(kobj);
|
|
+ struct device *dev = kobj_to_dev(kobj);
|
|
int retval = 0;
|
|
|
|
/* add device node properties if present */
|
|
@@ -1131,7 +1130,7 @@ int device_register(struct device *dev)
|
|
*/
|
|
struct device *get_device(struct device *dev)
|
|
{
|
|
- return dev ? to_dev(kobject_get(&dev->kobj)) : NULL;
|
|
+ return dev ? kobj_to_dev(kobject_get(&dev->kobj)) : NULL;
|
|
}
|
|
|
|
/**
|
|
diff --git a/drivers/base/devres.c b/drivers/base/devres.c
|
|
index 524bf96..8731979 100644
|
|
--- a/drivers/base/devres.c
|
|
+++ b/drivers/base/devres.c
|
|
@@ -144,6 +144,48 @@ EXPORT_SYMBOL_GPL(devres_alloc);
|
|
#endif
|
|
|
|
/**
|
|
+ * devres_for_each_res - Resource iterator
|
|
+ * @dev: Device to iterate resource from
|
|
+ * @release: Look for resources associated with this release function
|
|
+ * @match: Match function (optional)
|
|
+ * @match_data: Data for the match function
|
|
+ * @fn: Function to be called for each matched resource.
|
|
+ * @data: Data for @fn, the 3rd parameter of @fn
|
|
+ *
|
|
+ * Call @fn for each devres of @dev which is associated with @release
|
|
+ * and for which @match returns 1.
|
|
+ *
|
|
+ * RETURNS:
|
|
+ * void
|
|
+ */
|
|
+void devres_for_each_res(struct device *dev, dr_release_t release,
|
|
+ dr_match_t match, void *match_data,
|
|
+ void (*fn)(struct device *, void *, void *),
|
|
+ void *data)
|
|
+{
|
|
+ struct devres_node *node;
|
|
+ struct devres_node *tmp;
|
|
+ unsigned long flags;
|
|
+
|
|
+ if (!fn)
|
|
+ return;
|
|
+
|
|
+ spin_lock_irqsave(&dev->devres_lock, flags);
|
|
+ list_for_each_entry_safe_reverse(node, tmp,
|
|
+ &dev->devres_head, entry) {
|
|
+ struct devres *dr = container_of(node, struct devres, node);
|
|
+
|
|
+ if (node->release != release)
|
|
+ continue;
|
|
+ if (match && !match(dev, dr->data, match_data))
|
|
+ continue;
|
|
+ fn(dev, dr->data, data);
|
|
+ }
|
|
+ spin_unlock_irqrestore(&dev->devres_lock, flags);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(devres_for_each_res);
|
|
+
|
|
+/**
|
|
* devres_free - Free device resource data
|
|
* @res: Pointer to devres data to free
|
|
*
|
|
@@ -309,6 +351,10 @@ EXPORT_SYMBOL_GPL(devres_remove);
|
|
* which @match returns 1. If @match is NULL, it's considered to
|
|
* match all. If found, the resource is removed atomically and freed.
|
|
*
|
|
+ * Note that the release function for the resource will not be called,
|
|
+ * only the devres-allocated data will be freed. The caller becomes
|
|
+ * responsible for freeing any other data.
|
|
+ *
|
|
* RETURNS:
|
|
* 0 if devres is found and freed, -ENOENT if not found.
|
|
*/
|
|
@@ -326,6 +372,37 @@ int devres_destroy(struct device *dev, dr_release_t release,
|
|
}
|
|
EXPORT_SYMBOL_GPL(devres_destroy);
|
|
|
|
+
|
|
+/**
|
|
+ * devres_release - Find a device resource and destroy it, calling release
|
|
+ * @dev: Device to find resource from
|
|
+ * @release: Look for resources associated with this release function
|
|
+ * @match: Match function (optional)
|
|
+ * @match_data: Data for the match function
|
|
+ *
|
|
+ * Find the latest devres of @dev associated with @release and for
|
|
+ * which @match returns 1. If @match is NULL, it's considered to
|
|
+ * match all. If found, the resource is removed atomically, the
|
|
+ * release function called and the resource freed.
|
|
+ *
|
|
+ * RETURNS:
|
|
+ * 0 if devres is found and freed, -ENOENT if not found.
|
|
+ */
|
|
+int devres_release(struct device *dev, dr_release_t release,
|
|
+ dr_match_t match, void *match_data)
|
|
+{
|
|
+ void *res;
|
|
+
|
|
+ res = devres_remove(dev, release, match, match_data);
|
|
+ if (unlikely(!res))
|
|
+ return -ENOENT;
|
|
+
|
|
+ (*release)(dev, res);
|
|
+ devres_free(res);
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(devres_release);
|
|
+
|
|
static int remove_nodes(struct device *dev,
|
|
struct list_head *first, struct list_head *end,
|
|
struct list_head *todo)
|
|
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
|
|
index 5401814..15b36b7 100644
|
|
--- a/drivers/base/firmware_class.c
|
|
+++ b/drivers/base/firmware_class.c
|
|
@@ -21,8 +21,16 @@
|
|
#include <linux/firmware.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sched.h>
|
|
+#include <linux/file.h>
|
|
+#include <linux/list.h>
|
|
+#include <linux/async.h>
|
|
+#include <linux/pm.h>
|
|
+#include <linux/suspend.h>
|
|
+#include <linux/syscore_ops.h>
|
|
|
|
-#define to_dev(obj) container_of(obj, struct device, kobj)
|
|
+#include <generated/utsrelease.h>
|
|
+
|
|
+#include "base.h"
|
|
|
|
MODULE_AUTHOR("Manuel Estrada Sainz");
|
|
MODULE_DESCRIPTION("Multi purpose firmware loading support");
|
|
@@ -87,23 +95,354 @@ static inline long firmware_loading_timeout(void)
|
|
return loading_timeout > 0 ? loading_timeout * HZ : MAX_SCHEDULE_TIMEOUT;
|
|
}
|
|
|
|
-/* fw_lock could be moved to 'struct firmware_priv' but since it is just
|
|
- * guarding for corner cases a global lock should be OK */
|
|
-static DEFINE_MUTEX(fw_lock);
|
|
+struct firmware_cache {
|
|
+ /* firmware_buf instance will be added into the below list */
|
|
+ spinlock_t lock;
|
|
+ struct list_head head;
|
|
+ int state;
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+ /*
|
|
+ * Names of firmware images which have been cached successfully
|
|
+ * will be added into the below list so that device uncache
|
|
+ * helper can trace which firmware images have been cached
|
|
+ * before.
|
|
+ */
|
|
+ spinlock_t name_lock;
|
|
+ struct list_head fw_names;
|
|
+
|
|
+ wait_queue_head_t wait_queue;
|
|
+ int cnt;
|
|
+ struct delayed_work work;
|
|
+
|
|
+ struct notifier_block pm_notify;
|
|
+#endif
|
|
+};
|
|
|
|
-struct firmware_priv {
|
|
+struct firmware_buf {
|
|
+ struct kref ref;
|
|
+ struct list_head list;
|
|
struct completion completion;
|
|
- struct firmware *fw;
|
|
+ struct firmware_cache *fwc;
|
|
unsigned long status;
|
|
+ void *data;
|
|
+ size_t size;
|
|
+#ifdef CONFIG_FW_LOADER_USER_HELPER
|
|
+ bool is_paged_buf;
|
|
struct page **pages;
|
|
int nr_pages;
|
|
int page_array_size;
|
|
- struct timer_list timeout;
|
|
- struct device dev;
|
|
- bool nowait;
|
|
+#endif
|
|
char fw_id[];
|
|
};
|
|
|
|
+struct fw_cache_entry {
|
|
+ struct list_head list;
|
|
+ char name[];
|
|
+};
|
|
+
|
|
+struct fw_name_devm {
|
|
+ unsigned long magic;
|
|
+ char name[];
|
|
+};
|
|
+
|
|
+#define to_fwbuf(d) container_of(d, struct firmware_buf, ref)
|
|
+
|
|
+#define FW_LOADER_NO_CACHE 0
|
|
+#define FW_LOADER_START_CACHE 1
|
|
+
|
|
+static int fw_cache_piggyback_on_request(const char *name);
|
|
+
|
|
+/* fw_lock could be moved to 'struct firmware_priv' but since it is just
|
|
+ * guarding for corner cases a global lock should be OK */
|
|
+static DEFINE_MUTEX(fw_lock);
|
|
+
|
|
+static struct firmware_cache fw_cache;
|
|
+
|
|
+static struct firmware_buf *__allocate_fw_buf(const char *fw_name,
|
|
+ struct firmware_cache *fwc)
|
|
+{
|
|
+ struct firmware_buf *buf;
|
|
+
|
|
+ buf = kzalloc(sizeof(*buf) + strlen(fw_name) + 1 , GFP_ATOMIC);
|
|
+
|
|
+ if (!buf)
|
|
+ return buf;
|
|
+
|
|
+ kref_init(&buf->ref);
|
|
+ strcpy(buf->fw_id, fw_name);
|
|
+ buf->fwc = fwc;
|
|
+ init_completion(&buf->completion);
|
|
+
|
|
+ pr_debug("%s: fw-%s buf=%p\n", __func__, fw_name, buf);
|
|
+
|
|
+ return buf;
|
|
+}
|
|
+
|
|
+static struct firmware_buf *__fw_lookup_buf(const char *fw_name)
|
|
+{
|
|
+ struct firmware_buf *tmp;
|
|
+ struct firmware_cache *fwc = &fw_cache;
|
|
+
|
|
+ list_for_each_entry(tmp, &fwc->head, list)
|
|
+ if (!strcmp(tmp->fw_id, fw_name))
|
|
+ return tmp;
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static int fw_lookup_and_allocate_buf(const char *fw_name,
|
|
+ struct firmware_cache *fwc,
|
|
+ struct firmware_buf **buf)
|
|
+{
|
|
+ struct firmware_buf *tmp;
|
|
+
|
|
+ spin_lock(&fwc->lock);
|
|
+ tmp = __fw_lookup_buf(fw_name);
|
|
+ if (tmp) {
|
|
+ kref_get(&tmp->ref);
|
|
+ spin_unlock(&fwc->lock);
|
|
+ *buf = tmp;
|
|
+ return 1;
|
|
+ }
|
|
+ tmp = __allocate_fw_buf(fw_name, fwc);
|
|
+ if (tmp)
|
|
+ list_add(&tmp->list, &fwc->head);
|
|
+ spin_unlock(&fwc->lock);
|
|
+
|
|
+ *buf = tmp;
|
|
+
|
|
+ return tmp ? 0 : -ENOMEM;
|
|
+}
|
|
+
|
|
+static struct firmware_buf *fw_lookup_buf(const char *fw_name)
|
|
+{
|
|
+ struct firmware_buf *tmp;
|
|
+ struct firmware_cache *fwc = &fw_cache;
|
|
+
|
|
+ spin_lock(&fwc->lock);
|
|
+ tmp = __fw_lookup_buf(fw_name);
|
|
+ spin_unlock(&fwc->lock);
|
|
+
|
|
+ return tmp;
|
|
+}
|
|
+
|
|
+static void __fw_free_buf(struct kref *ref)
|
|
+{
|
|
+ struct firmware_buf *buf = to_fwbuf(ref);
|
|
+ struct firmware_cache *fwc = buf->fwc;
|
|
+
|
|
+ pr_debug("%s: fw-%s buf=%p data=%p size=%u\n",
|
|
+ __func__, buf->fw_id, buf, buf->data,
|
|
+ (unsigned int)buf->size);
|
|
+
|
|
+ list_del(&buf->list);
|
|
+ spin_unlock(&fwc->lock);
|
|
+
|
|
+#ifdef CONFIG_FW_LOADER_USER_HELPER
|
|
+ if (buf->is_paged_buf) {
|
|
+ int i;
|
|
+ vunmap(buf->data);
|
|
+ for (i = 0; i < buf->nr_pages; i++)
|
|
+ __free_page(buf->pages[i]);
|
|
+ kfree(buf->pages);
|
|
+ } else
|
|
+#endif
|
|
+ vfree(buf->data);
|
|
+ kfree(buf);
|
|
+}
|
|
+
|
|
+static void fw_free_buf(struct firmware_buf *buf)
|
|
+{
|
|
+ struct firmware_cache *fwc = buf->fwc;
|
|
+ spin_lock(&fwc->lock);
|
|
+ if (!kref_put(&buf->ref, __fw_free_buf))
|
|
+ spin_unlock(&fwc->lock);
|
|
+}
|
|
+
|
|
+/* direct firmware loading support */
|
|
+static char fw_path_para[256];
|
|
+static const char * const fw_path[] = {
|
|
+ fw_path_para,
|
|
+ "/lib/firmware/updates/" UTS_RELEASE,
|
|
+ "/lib/firmware/updates",
|
|
+ "/lib/firmware/" UTS_RELEASE,
|
|
+ "/lib/firmware"
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Typical usage is that passing 'firmware_class.path=$CUSTOMIZED_PATH'
|
|
+ * from kernel command line because firmware_class is generally built in
|
|
+ * kernel instead of module.
|
|
+ */
|
|
+module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644);
|
|
+MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path");
|
|
+
|
|
+/* Don't inline this: 'struct kstat' is biggish */
|
|
+static noinline_for_stack long fw_file_size(struct file *file)
|
|
+{
|
|
+ struct kstat st;
|
|
+ if (vfs_getattr(file->f_path.mnt, file->f_path.dentry, &st))
|
|
+ return -1;
|
|
+ if (!S_ISREG(st.mode))
|
|
+ return -1;
|
|
+ if (st.size != (long)st.size)
|
|
+ return -1;
|
|
+ return st.size;
|
|
+}
|
|
+
|
|
+static bool fw_read_file_contents(struct file *file, struct firmware_buf *fw_buf)
|
|
+{
|
|
+ long size;
|
|
+ char *buf;
|
|
+
|
|
+ size = fw_file_size(file);
|
|
+ if (size <= 0)
|
|
+ return false;
|
|
+ buf = vmalloc(size);
|
|
+ if (!buf)
|
|
+ return false;
|
|
+ if (kernel_read(file, 0, buf, size) != size) {
|
|
+ vfree(buf);
|
|
+ return false;
|
|
+ }
|
|
+ fw_buf->data = buf;
|
|
+ fw_buf->size = size;
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static bool fw_get_filesystem_firmware(struct device *device,
|
|
+ struct firmware_buf *buf)
|
|
+{
|
|
+ int i;
|
|
+ bool success = false;
|
|
+ char *path = __getname();
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(fw_path); i++) {
|
|
+ struct file *file;
|
|
+
|
|
+ /* skip the unset customized path */
|
|
+ if (!fw_path[i][0])
|
|
+ continue;
|
|
+
|
|
+ snprintf(path, PATH_MAX, "%s/%s", fw_path[i], buf->fw_id);
|
|
+
|
|
+ file = filp_open(path, O_RDONLY, 0);
|
|
+ if (IS_ERR(file))
|
|
+ continue;
|
|
+ success = fw_read_file_contents(file, buf);
|
|
+ fput(file);
|
|
+ if (success)
|
|
+ break;
|
|
+ }
|
|
+ __putname(path);
|
|
+
|
|
+ if (success) {
|
|
+ dev_dbg(device, "firmware: direct-loading firmware %s\n",
|
|
+ buf->fw_id);
|
|
+ mutex_lock(&fw_lock);
|
|
+ set_bit(FW_STATUS_DONE, &buf->status);
|
|
+ complete_all(&buf->completion);
|
|
+ mutex_unlock(&fw_lock);
|
|
+ }
|
|
+
|
|
+ return success;
|
|
+}
|
|
+
|
|
+/* firmware holds the ownership of pages */
|
|
+static void firmware_free_data(const struct firmware *fw)
|
|
+{
|
|
+ /* Loaded directly? */
|
|
+ if (!fw->priv) {
|
|
+ vfree(fw->data);
|
|
+ return;
|
|
+ }
|
|
+ fw_free_buf(fw->priv);
|
|
+}
|
|
+
|
|
+/* store the pages buffer info firmware from buf */
|
|
+static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw)
|
|
+{
|
|
+ fw->priv = buf;
|
|
+#ifdef CONFIG_FW_LOADER_USER_HELPER
|
|
+ fw->pages = buf->pages;
|
|
+#endif
|
|
+ fw->size = buf->size;
|
|
+ fw->data = buf->data;
|
|
+
|
|
+ pr_debug("%s: fw-%s buf=%p data=%p size=%u\n",
|
|
+ __func__, buf->fw_id, buf, buf->data,
|
|
+ (unsigned int)buf->size);
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+static void fw_name_devm_release(struct device *dev, void *res)
|
|
+{
|
|
+ struct fw_name_devm *fwn = res;
|
|
+
|
|
+ if (fwn->magic == (unsigned long)&fw_cache)
|
|
+ pr_debug("%s: fw_name-%s devm-%p released\n",
|
|
+ __func__, fwn->name, res);
|
|
+}
|
|
+
|
|
+static int fw_devm_match(struct device *dev, void *res,
|
|
+ void *match_data)
|
|
+{
|
|
+ struct fw_name_devm *fwn = res;
|
|
+
|
|
+ return (fwn->magic == (unsigned long)&fw_cache) &&
|
|
+ !strcmp(fwn->name, match_data);
|
|
+}
|
|
+
|
|
+static struct fw_name_devm *fw_find_devm_name(struct device *dev,
|
|
+ const char *name)
|
|
+{
|
|
+ struct fw_name_devm *fwn;
|
|
+
|
|
+ fwn = devres_find(dev, fw_name_devm_release,
|
|
+ fw_devm_match, (void *)name);
|
|
+ return fwn;
|
|
+}
|
|
+
|
|
+/* add firmware name into devres list */
|
|
+static int fw_add_devm_name(struct device *dev, const char *name)
|
|
+{
|
|
+ struct fw_name_devm *fwn;
|
|
+
|
|
+ fwn = fw_find_devm_name(dev, name);
|
|
+ if (fwn)
|
|
+ return 1;
|
|
+
|
|
+ fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm) +
|
|
+ strlen(name) + 1, GFP_KERNEL);
|
|
+ if (!fwn)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ fwn->magic = (unsigned long)&fw_cache;
|
|
+ strcpy(fwn->name, name);
|
|
+ devres_add(dev, fwn);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#else
|
|
+static int fw_add_devm_name(struct device *dev, const char *name)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+/*
|
|
+ * user-mode helper code
|
|
+ */
|
|
+#ifdef CONFIG_FW_LOADER_USER_HELPER
|
|
+struct firmware_priv {
|
|
+ struct delayed_work timeout_work;
|
|
+ bool nowait;
|
|
+ struct device dev;
|
|
+ struct firmware_buf *buf;
|
|
+ struct firmware *fw;
|
|
+};
|
|
+
|
|
static struct firmware_priv *to_firmware_priv(struct device *dev)
|
|
{
|
|
return container_of(dev, struct firmware_priv, dev);
|
|
@@ -111,11 +450,15 @@ static struct firmware_priv *to_firmware_priv(struct device *dev)
|
|
|
|
static void fw_load_abort(struct firmware_priv *fw_priv)
|
|
{
|
|
- set_bit(FW_STATUS_ABORT, &fw_priv->status);
|
|
- wmb();
|
|
- complete(&fw_priv->completion);
|
|
+ struct firmware_buf *buf = fw_priv->buf;
|
|
+
|
|
+ set_bit(FW_STATUS_ABORT, &buf->status);
|
|
+ complete_all(&buf->completion);
|
|
}
|
|
|
|
+#define is_fw_load_aborted(buf) \
|
|
+ test_bit(FW_STATUS_ABORT, &(buf)->status)
|
|
+
|
|
static ssize_t firmware_timeout_show(struct class *class,
|
|
struct class_attribute *attr,
|
|
char *buf)
|
|
@@ -156,11 +499,7 @@ static struct class_attribute firmware_class_attrs[] = {
|
|
static void fw_dev_release(struct device *dev)
|
|
{
|
|
struct firmware_priv *fw_priv = to_firmware_priv(dev);
|
|
- int i;
|
|
|
|
- for (i = 0; i < fw_priv->nr_pages; i++)
|
|
- __free_page(fw_priv->pages[i]);
|
|
- kfree(fw_priv->pages);
|
|
kfree(fw_priv);
|
|
|
|
module_put(THIS_MODULE);
|
|
@@ -170,7 +509,7 @@ static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env)
|
|
{
|
|
struct firmware_priv *fw_priv = to_firmware_priv(dev);
|
|
|
|
- if (add_uevent_var(env, "FIRMWARE=%s", fw_priv->fw_id))
|
|
+ if (add_uevent_var(env, "FIRMWARE=%s", fw_priv->buf->fw_id))
|
|
return -ENOMEM;
|
|
if (add_uevent_var(env, "TIMEOUT=%i", loading_timeout))
|
|
return -ENOMEM;
|
|
@@ -191,26 +530,30 @@ static ssize_t firmware_loading_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct firmware_priv *fw_priv = to_firmware_priv(dev);
|
|
- int loading = test_bit(FW_STATUS_LOADING, &fw_priv->status);
|
|
+ int loading = test_bit(FW_STATUS_LOADING, &fw_priv->buf->status);
|
|
|
|
return sprintf(buf, "%d\n", loading);
|
|
}
|
|
|
|
-static void firmware_free_data(const struct firmware *fw)
|
|
-{
|
|
- int i;
|
|
- vunmap(fw->data);
|
|
- if (fw->pages) {
|
|
- for (i = 0; i < PFN_UP(fw->size); i++)
|
|
- __free_page(fw->pages[i]);
|
|
- kfree(fw->pages);
|
|
- }
|
|
-}
|
|
-
|
|
/* Some architectures don't have PAGE_KERNEL_RO */
|
|
#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->is_paged_buf)
|
|
+ return 0;
|
|
+
|
|
+ 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
|
|
@@ -229,45 +572,41 @@ static ssize_t firmware_loading_store(struct device *dev,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct firmware_priv *fw_priv = to_firmware_priv(dev);
|
|
+ struct firmware_buf *fw_buf = fw_priv->buf;
|
|
int loading = simple_strtol(buf, NULL, 10);
|
|
int i;
|
|
|
|
mutex_lock(&fw_lock);
|
|
|
|
- if (!fw_priv->fw)
|
|
+ if (!fw_buf)
|
|
goto out;
|
|
|
|
switch (loading) {
|
|
case 1:
|
|
- firmware_free_data(fw_priv->fw);
|
|
- memset(fw_priv->fw, 0, sizeof(struct firmware));
|
|
- /* If the pages are not owned by 'struct firmware' */
|
|
- for (i = 0; i < fw_priv->nr_pages; i++)
|
|
- __free_page(fw_priv->pages[i]);
|
|
- kfree(fw_priv->pages);
|
|
- fw_priv->pages = NULL;
|
|
- fw_priv->page_array_size = 0;
|
|
- fw_priv->nr_pages = 0;
|
|
- set_bit(FW_STATUS_LOADING, &fw_priv->status);
|
|
+ /* discarding any previous partial load */
|
|
+ if (!test_bit(FW_STATUS_DONE, &fw_buf->status)) {
|
|
+ for (i = 0; i < fw_buf->nr_pages; i++)
|
|
+ __free_page(fw_buf->pages[i]);
|
|
+ kfree(fw_buf->pages);
|
|
+ fw_buf->pages = NULL;
|
|
+ fw_buf->page_array_size = 0;
|
|
+ fw_buf->nr_pages = 0;
|
|
+ set_bit(FW_STATUS_LOADING, &fw_buf->status);
|
|
+ }
|
|
break;
|
|
case 0:
|
|
- if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) {
|
|
- vunmap(fw_priv->fw->data);
|
|
- fw_priv->fw->data = vmap(fw_priv->pages,
|
|
- fw_priv->nr_pages,
|
|
- 0, PAGE_KERNEL_RO);
|
|
- if (!fw_priv->fw->data) {
|
|
- dev_err(dev, "%s: vmap() failed\n", __func__);
|
|
- goto err;
|
|
- }
|
|
- /* Pages are now owned by 'struct firmware' */
|
|
- fw_priv->fw->pages = fw_priv->pages;
|
|
- fw_priv->pages = NULL;
|
|
-
|
|
- fw_priv->page_array_size = 0;
|
|
- fw_priv->nr_pages = 0;
|
|
- complete(&fw_priv->completion);
|
|
- clear_bit(FW_STATUS_LOADING, &fw_priv->status);
|
|
+ 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;
|
|
}
|
|
/* fallthrough */
|
|
@@ -275,7 +614,6 @@ static ssize_t firmware_loading_store(struct device *dev,
|
|
dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading);
|
|
/* fallthrough */
|
|
case -1:
|
|
- err:
|
|
fw_load_abort(fw_priv);
|
|
break;
|
|
}
|
|
@@ -290,23 +628,23 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *bin_attr,
|
|
char *buffer, loff_t offset, size_t count)
|
|
{
|
|
- struct device *dev = to_dev(kobj);
|
|
+ struct device *dev = kobj_to_dev(kobj);
|
|
struct firmware_priv *fw_priv = to_firmware_priv(dev);
|
|
- struct firmware *fw;
|
|
+ struct firmware_buf *buf;
|
|
ssize_t ret_count;
|
|
|
|
mutex_lock(&fw_lock);
|
|
- fw = fw_priv->fw;
|
|
- if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->status)) {
|
|
+ buf = fw_priv->buf;
|
|
+ if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) {
|
|
ret_count = -ENODEV;
|
|
goto out;
|
|
}
|
|
- if (offset > fw->size) {
|
|
+ if (offset > buf->size) {
|
|
ret_count = 0;
|
|
goto out;
|
|
}
|
|
- if (count > fw->size - offset)
|
|
- count = fw->size - offset;
|
|
+ if (count > buf->size - offset)
|
|
+ count = buf->size - offset;
|
|
|
|
ret_count = count;
|
|
|
|
@@ -316,11 +654,11 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj,
|
|
int page_ofs = offset & (PAGE_SIZE-1);
|
|
int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count);
|
|
|
|
- page_data = kmap(fw_priv->pages[page_nr]);
|
|
+ page_data = kmap(buf->pages[page_nr]);
|
|
|
|
memcpy(buffer, page_data + page_ofs, page_cnt);
|
|
|
|
- kunmap(fw_priv->pages[page_nr]);
|
|
+ kunmap(buf->pages[page_nr]);
|
|
buffer += page_cnt;
|
|
offset += page_cnt;
|
|
count -= page_cnt;
|
|
@@ -332,12 +670,13 @@ out:
|
|
|
|
static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size)
|
|
{
|
|
+ struct firmware_buf *buf = fw_priv->buf;
|
|
int pages_needed = ALIGN(min_size, PAGE_SIZE) >> PAGE_SHIFT;
|
|
|
|
/* If the array of pages is too small, grow it... */
|
|
- if (fw_priv->page_array_size < pages_needed) {
|
|
+ if (buf->page_array_size < pages_needed) {
|
|
int new_array_size = max(pages_needed,
|
|
- fw_priv->page_array_size * 2);
|
|
+ buf->page_array_size * 2);
|
|
struct page **new_pages;
|
|
|
|
new_pages = kmalloc(new_array_size * sizeof(void *),
|
|
@@ -346,24 +685,24 @@ static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size)
|
|
fw_load_abort(fw_priv);
|
|
return -ENOMEM;
|
|
}
|
|
- memcpy(new_pages, fw_priv->pages,
|
|
- fw_priv->page_array_size * sizeof(void *));
|
|
- memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) *
|
|
- (new_array_size - fw_priv->page_array_size));
|
|
- kfree(fw_priv->pages);
|
|
- fw_priv->pages = new_pages;
|
|
- fw_priv->page_array_size = new_array_size;
|
|
+ memcpy(new_pages, buf->pages,
|
|
+ buf->page_array_size * sizeof(void *));
|
|
+ memset(&new_pages[buf->page_array_size], 0, sizeof(void *) *
|
|
+ (new_array_size - buf->page_array_size));
|
|
+ kfree(buf->pages);
|
|
+ buf->pages = new_pages;
|
|
+ buf->page_array_size = new_array_size;
|
|
}
|
|
|
|
- while (fw_priv->nr_pages < pages_needed) {
|
|
- fw_priv->pages[fw_priv->nr_pages] =
|
|
+ while (buf->nr_pages < pages_needed) {
|
|
+ buf->pages[buf->nr_pages] =
|
|
alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
|
|
|
|
- if (!fw_priv->pages[fw_priv->nr_pages]) {
|
|
+ if (!buf->pages[buf->nr_pages]) {
|
|
fw_load_abort(fw_priv);
|
|
return -ENOMEM;
|
|
}
|
|
- fw_priv->nr_pages++;
|
|
+ buf->nr_pages++;
|
|
}
|
|
return 0;
|
|
}
|
|
@@ -384,20 +723,21 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *bin_attr,
|
|
char *buffer, loff_t offset, size_t count)
|
|
{
|
|
- struct device *dev = to_dev(kobj);
|
|
+ struct device *dev = kobj_to_dev(kobj);
|
|
struct firmware_priv *fw_priv = to_firmware_priv(dev);
|
|
- struct firmware *fw;
|
|
+ struct firmware_buf *buf;
|
|
ssize_t retval;
|
|
|
|
if (!capable(CAP_SYS_RAWIO))
|
|
return -EPERM;
|
|
|
|
mutex_lock(&fw_lock);
|
|
- fw = fw_priv->fw;
|
|
- if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->status)) {
|
|
+ buf = fw_priv->buf;
|
|
+ if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) {
|
|
retval = -ENODEV;
|
|
goto out;
|
|
}
|
|
+
|
|
retval = fw_realloc_buffer(fw_priv, offset + count);
|
|
if (retval)
|
|
goto out;
|
|
@@ -410,17 +750,17 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj,
|
|
int page_ofs = offset & (PAGE_SIZE - 1);
|
|
int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count);
|
|
|
|
- page_data = kmap(fw_priv->pages[page_nr]);
|
|
+ page_data = kmap(buf->pages[page_nr]);
|
|
|
|
memcpy(page_data + page_ofs, buffer, page_cnt);
|
|
|
|
- kunmap(fw_priv->pages[page_nr]);
|
|
+ kunmap(buf->pages[page_nr]);
|
|
buffer += page_cnt;
|
|
offset += page_cnt;
|
|
count -= page_cnt;
|
|
}
|
|
|
|
- fw->size = max_t(size_t, offset, fw->size);
|
|
+ buf->size = max_t(size_t, offset, buf->size);
|
|
out:
|
|
mutex_unlock(&fw_lock);
|
|
return retval;
|
|
@@ -433,11 +773,18 @@ static struct bin_attribute firmware_attr_data = {
|
|
.write = firmware_data_write,
|
|
};
|
|
|
|
-static void firmware_class_timeout(u_long data)
|
|
+static void firmware_class_timeout_work(struct work_struct *work)
|
|
{
|
|
- struct firmware_priv *fw_priv = (struct firmware_priv *) data;
|
|
+ struct firmware_priv *fw_priv = container_of(work,
|
|
+ struct firmware_priv, timeout_work.work);
|
|
|
|
+ mutex_lock(&fw_lock);
|
|
+ if (test_bit(FW_STATUS_DONE, &(fw_priv->buf->status))) {
|
|
+ mutex_unlock(&fw_lock);
|
|
+ return;
|
|
+ }
|
|
fw_load_abort(fw_priv);
|
|
+ mutex_unlock(&fw_lock);
|
|
}
|
|
|
|
static struct firmware_priv *
|
|
@@ -447,70 +794,38 @@ fw_create_instance(struct firmware *firmware, const char *fw_name,
|
|
struct firmware_priv *fw_priv;
|
|
struct device *f_dev;
|
|
|
|
- fw_priv = kzalloc(sizeof(*fw_priv) + strlen(fw_name) + 1 , GFP_KERNEL);
|
|
+ fw_priv = kzalloc(sizeof(*fw_priv), GFP_KERNEL);
|
|
if (!fw_priv) {
|
|
dev_err(device, "%s: kmalloc failed\n", __func__);
|
|
- return ERR_PTR(-ENOMEM);
|
|
+ fw_priv = ERR_PTR(-ENOMEM);
|
|
+ goto exit;
|
|
}
|
|
|
|
- fw_priv->fw = firmware;
|
|
fw_priv->nowait = nowait;
|
|
- strcpy(fw_priv->fw_id, fw_name);
|
|
- init_completion(&fw_priv->completion);
|
|
- setup_timer(&fw_priv->timeout,
|
|
- firmware_class_timeout, (u_long) fw_priv);
|
|
+ fw_priv->fw = firmware;
|
|
+ INIT_DELAYED_WORK(&fw_priv->timeout_work,
|
|
+ firmware_class_timeout_work);
|
|
|
|
f_dev = &fw_priv->dev;
|
|
|
|
device_initialize(f_dev);
|
|
- dev_set_name(f_dev, "%s", dev_name(device));
|
|
+ dev_set_name(f_dev, "%s", fw_name);
|
|
f_dev->parent = device;
|
|
f_dev->class = &firmware_class;
|
|
-
|
|
+exit:
|
|
return fw_priv;
|
|
}
|
|
|
|
-static struct firmware_priv *
|
|
-_request_firmware_prepare(const struct firmware **firmware_p, const char *name,
|
|
- struct device *device, bool uevent, bool nowait)
|
|
-{
|
|
- struct firmware *firmware;
|
|
- struct firmware_priv *fw_priv;
|
|
-
|
|
- if (!firmware_p)
|
|
- return ERR_PTR(-EINVAL);
|
|
-
|
|
- *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL);
|
|
- if (!firmware) {
|
|
- dev_err(device, "%s: kmalloc(struct firmware) failed\n",
|
|
- __func__);
|
|
- return ERR_PTR(-ENOMEM);
|
|
- }
|
|
-
|
|
- if (fw_get_builtin_firmware(firmware, name)) {
|
|
- dev_dbg(device, "firmware: using built-in firmware %s\n", name);
|
|
- return NULL;
|
|
- }
|
|
-
|
|
- fw_priv = fw_create_instance(firmware, name, device, uevent, nowait);
|
|
- if (IS_ERR(fw_priv)) {
|
|
- release_firmware(firmware);
|
|
- *firmware_p = NULL;
|
|
- }
|
|
- return fw_priv;
|
|
-}
|
|
-
|
|
-static void _request_firmware_cleanup(const struct firmware **firmware_p)
|
|
-{
|
|
- release_firmware(*firmware_p);
|
|
- *firmware_p = NULL;
|
|
-}
|
|
-
|
|
+/* load a firmware via user helper */
|
|
static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
|
|
long timeout)
|
|
{
|
|
int retval = 0;
|
|
struct device *f_dev = &fw_priv->dev;
|
|
+ struct firmware_buf *buf = fw_priv->buf;
|
|
+
|
|
+ /* fall back on userspace loading */
|
|
+ buf->is_paged_buf = true;
|
|
|
|
dev_set_uevent_suppress(f_dev, true);
|
|
|
|
@@ -537,24 +852,18 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
|
|
|
|
if (uevent) {
|
|
dev_set_uevent_suppress(f_dev, false);
|
|
- dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_id);
|
|
+ dev_dbg(f_dev, "firmware: requesting %s\n", buf->fw_id);
|
|
if (timeout != MAX_SCHEDULE_TIMEOUT)
|
|
- mod_timer(&fw_priv->timeout,
|
|
- round_jiffies_up(jiffies + timeout));
|
|
+ schedule_delayed_work(&fw_priv->timeout_work, timeout);
|
|
|
|
kobject_uevent(&fw_priv->dev.kobj, KOBJ_ADD);
|
|
}
|
|
|
|
- wait_for_completion(&fw_priv->completion);
|
|
+ wait_for_completion(&buf->completion);
|
|
|
|
- set_bit(FW_STATUS_DONE, &fw_priv->status);
|
|
- del_timer_sync(&fw_priv->timeout);
|
|
+ cancel_delayed_work_sync(&fw_priv->timeout_work);
|
|
|
|
- mutex_lock(&fw_lock);
|
|
- if (!fw_priv->fw->size || test_bit(FW_STATUS_ABORT, &fw_priv->status))
|
|
- retval = -ENOENT;
|
|
- fw_priv->fw = NULL;
|
|
- mutex_unlock(&fw_lock);
|
|
+ fw_priv->buf = NULL;
|
|
|
|
device_remove_file(f_dev, &dev_attr_loading);
|
|
err_del_bin_attr:
|
|
@@ -566,6 +875,186 @@ err_put_dev:
|
|
return retval;
|
|
}
|
|
|
|
+static int fw_load_from_user_helper(struct firmware *firmware,
|
|
+ const char *name, struct device *device,
|
|
+ bool uevent, bool nowait, long timeout)
|
|
+{
|
|
+ struct firmware_priv *fw_priv;
|
|
+
|
|
+ fw_priv = fw_create_instance(firmware, name, device, uevent, nowait);
|
|
+ if (IS_ERR(fw_priv))
|
|
+ return PTR_ERR(fw_priv);
|
|
+
|
|
+ fw_priv->buf = firmware->priv;
|
|
+ return _request_firmware_load(fw_priv, uevent, timeout);
|
|
+}
|
|
+#else /* CONFIG_FW_LOADER_USER_HELPER */
|
|
+static inline int
|
|
+fw_load_from_user_helper(struct firmware *firmware, const char *name,
|
|
+ struct device *device, bool uevent, bool nowait,
|
|
+ long timeout)
|
|
+{
|
|
+ return -ENOENT;
|
|
+}
|
|
+
|
|
+/* No abort during direct loading */
|
|
+#define is_fw_load_aborted(buf) false
|
|
+
|
|
+#endif /* CONFIG_FW_LOADER_USER_HELPER */
|
|
+
|
|
+
|
|
+/* wait until the shared firmware_buf becomes ready (or error) */
|
|
+static int sync_cached_firmware_buf(struct firmware_buf *buf)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ mutex_lock(&fw_lock);
|
|
+ while (!test_bit(FW_STATUS_DONE, &buf->status)) {
|
|
+ if (is_fw_load_aborted(buf)) {
|
|
+ ret = -ENOENT;
|
|
+ break;
|
|
+ }
|
|
+ mutex_unlock(&fw_lock);
|
|
+ wait_for_completion(&buf->completion);
|
|
+ mutex_lock(&fw_lock);
|
|
+ }
|
|
+ mutex_unlock(&fw_lock);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* prepare firmware and firmware_buf structs;
|
|
+ * return 0 if a firmware is already assigned, 1 if need to load one,
|
|
+ * or a negative error code
|
|
+ */
|
|
+static int
|
|
+_request_firmware_prepare(struct firmware **firmware_p, const char *name,
|
|
+ struct device *device)
|
|
+{
|
|
+ struct firmware *firmware;
|
|
+ struct firmware_buf *buf;
|
|
+ int ret;
|
|
+
|
|
+ *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL);
|
|
+ if (!firmware) {
|
|
+ dev_err(device, "%s: kmalloc(struct firmware) failed\n",
|
|
+ __func__);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ if (fw_get_builtin_firmware(firmware, name)) {
|
|
+ dev_dbg(device, "firmware: using built-in firmware %s\n", name);
|
|
+ return 0; /* assigned */
|
|
+ }
|
|
+
|
|
+ ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf);
|
|
+
|
|
+ /*
|
|
+ * bind with 'buf' now to avoid warning in failure path
|
|
+ * of requesting firmware.
|
|
+ */
|
|
+ firmware->priv = buf;
|
|
+
|
|
+ if (ret > 0) {
|
|
+ ret = sync_cached_firmware_buf(buf);
|
|
+ if (!ret) {
|
|
+ fw_set_page_data(buf, firmware);
|
|
+ return 0; /* assigned */
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ return 1; /* need to load */
|
|
+}
|
|
+
|
|
+static int assign_firmware_buf(struct firmware *fw, struct device *device)
|
|
+{
|
|
+ struct firmware_buf *buf = fw->priv;
|
|
+
|
|
+ mutex_lock(&fw_lock);
|
|
+ if (!buf->size || is_fw_load_aborted(buf)) {
|
|
+ mutex_unlock(&fw_lock);
|
|
+ return -ENOENT;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * add firmware name into devres list so that we can auto cache
|
|
+ * and uncache firmware for device.
|
|
+ *
|
|
+ * device may has been deleted already, but the problem
|
|
+ * should be fixed in devres or driver core.
|
|
+ */
|
|
+ if (device)
|
|
+ fw_add_devm_name(device, buf->fw_id);
|
|
+
|
|
+ /*
|
|
+ * After caching firmware image is started, let it piggyback
|
|
+ * on request firmware.
|
|
+ */
|
|
+ if (buf->fwc->state == FW_LOADER_START_CACHE) {
|
|
+ if (fw_cache_piggyback_on_request(buf->fw_id))
|
|
+ kref_get(&buf->ref);
|
|
+ }
|
|
+
|
|
+ /* pass the pages buffer to driver at the last minute */
|
|
+ fw_set_page_data(buf, fw);
|
|
+ mutex_unlock(&fw_lock);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* called from request_firmware() and request_firmware_work_func() */
|
|
+static int
|
|
+_request_firmware(const struct firmware **firmware_p, const char *name,
|
|
+ struct device *device, bool uevent, bool nowait)
|
|
+{
|
|
+ struct firmware *fw;
|
|
+ long timeout;
|
|
+ int ret;
|
|
+
|
|
+ if (!firmware_p)
|
|
+ return -EINVAL;
|
|
+
|
|
+ ret = _request_firmware_prepare(&fw, name, device);
|
|
+ if (ret <= 0) /* error or already assigned */
|
|
+ goto out;
|
|
+
|
|
+ ret = 0;
|
|
+ timeout = firmware_loading_timeout();
|
|
+ if (nowait) {
|
|
+ timeout = usermodehelper_read_lock_wait(timeout);
|
|
+ if (!timeout) {
|
|
+ dev_dbg(device, "firmware: %s loading timed out\n",
|
|
+ name);
|
|
+ ret = -EBUSY;
|
|
+ goto out;
|
|
+ }
|
|
+ } else {
|
|
+ ret = usermodehelper_read_trylock();
|
|
+ if (WARN_ON(ret)) {
|
|
+ dev_err(device, "firmware: %s will not be loaded\n",
|
|
+ name);
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!fw_get_filesystem_firmware(device, fw->priv))
|
|
+ ret = fw_load_from_user_helper(fw, name, device,
|
|
+ uevent, nowait, timeout);
|
|
+ if (!ret)
|
|
+ ret = assign_firmware_buf(fw, device);
|
|
+
|
|
+ usermodehelper_read_unlock();
|
|
+
|
|
+ out:
|
|
+ if (ret < 0) {
|
|
+ release_firmware(fw);
|
|
+ fw = NULL;
|
|
+ }
|
|
+
|
|
+ *firmware_p = fw;
|
|
+ return ret;
|
|
+}
|
|
+
|
|
/**
|
|
* request_firmware: - send firmware request and wait for it
|
|
* @firmware_p: pointer to firmware image
|
|
@@ -580,31 +1069,17 @@ err_put_dev:
|
|
* @name will be used as $FIRMWARE in the uevent environment and
|
|
* should be distinctive enough not to be confused with any other
|
|
* firmware image for this or any other device.
|
|
+ *
|
|
+ * Caller must hold the reference count of @device.
|
|
+ *
|
|
+ * The function can be called safely inside device's suspend and
|
|
+ * resume callback.
|
|
**/
|
|
int
|
|
request_firmware(const struct firmware **firmware_p, const char *name,
|
|
struct device *device)
|
|
{
|
|
- struct firmware_priv *fw_priv;
|
|
- int ret;
|
|
-
|
|
- fw_priv = _request_firmware_prepare(firmware_p, name, device, true,
|
|
- false);
|
|
- if (IS_ERR_OR_NULL(fw_priv))
|
|
- return PTR_RET(fw_priv);
|
|
-
|
|
- ret = usermodehelper_read_trylock();
|
|
- if (WARN_ON(ret)) {
|
|
- dev_err(device, "firmware: %s will not be loaded\n", name);
|
|
- } else {
|
|
- ret = _request_firmware_load(fw_priv, true,
|
|
- firmware_loading_timeout());
|
|
- usermodehelper_read_unlock();
|
|
- }
|
|
- if (ret)
|
|
- _request_firmware_cleanup(firmware_p);
|
|
-
|
|
- return ret;
|
|
+ return _request_firmware(firmware_p, name, device, true, false);
|
|
}
|
|
|
|
/**
|
|
@@ -635,32 +1110,13 @@ static void request_firmware_work_func(struct work_struct *work)
|
|
{
|
|
struct firmware_work *fw_work;
|
|
const struct firmware *fw;
|
|
- struct firmware_priv *fw_priv;
|
|
- long timeout;
|
|
- int ret;
|
|
|
|
fw_work = container_of(work, struct firmware_work, work);
|
|
- fw_priv = _request_firmware_prepare(&fw, fw_work->name, fw_work->device,
|
|
- fw_work->uevent, true);
|
|
- if (IS_ERR_OR_NULL(fw_priv)) {
|
|
- ret = PTR_RET(fw_priv);
|
|
- goto out;
|
|
- }
|
|
-
|
|
- timeout = usermodehelper_read_lock_wait(firmware_loading_timeout());
|
|
- if (timeout) {
|
|
- ret = _request_firmware_load(fw_priv, fw_work->uevent, timeout);
|
|
- usermodehelper_read_unlock();
|
|
- } else {
|
|
- dev_dbg(fw_work->device, "firmware: %s loading timed out\n",
|
|
- fw_work->name);
|
|
- ret = -EAGAIN;
|
|
- }
|
|
- if (ret)
|
|
- _request_firmware_cleanup(&fw);
|
|
|
|
- out:
|
|
+ _request_firmware(&fw, fw_work->name, fw_work->device,
|
|
+ fw_work->uevent, true);
|
|
fw_work->cont(fw, fw_work->context);
|
|
+ put_device(fw_work->device); /* taken in request_firmware_nowait() */
|
|
|
|
module_put(fw_work->module);
|
|
kfree(fw_work);
|
|
@@ -679,9 +1135,15 @@ static void request_firmware_work_func(struct work_struct *work)
|
|
* @cont: function will be called asynchronously when the firmware
|
|
* request is over.
|
|
*
|
|
- * Asynchronous variant of request_firmware() for user contexts where
|
|
- * it is not possible to sleep for long time. It can't be called
|
|
- * in atomic contexts.
|
|
+ * Caller must hold the reference count of @device.
|
|
+ *
|
|
+ * Asynchronous variant of request_firmware() for user contexts:
|
|
+ * - sleep for as small periods as possible since it may
|
|
+ * increase kernel boot time of built-in device drivers
|
|
+ * requesting firmware in their ->probe() methods, if
|
|
+ * @gfp is GFP_KERNEL.
|
|
+ *
|
|
+ * - can't sleep at all if @gfp is GFP_ATOMIC.
|
|
**/
|
|
int
|
|
request_firmware_nowait(
|
|
@@ -707,19 +1169,391 @@ request_firmware_nowait(
|
|
return -EFAULT;
|
|
}
|
|
|
|
+ get_device(fw_work->device);
|
|
INIT_WORK(&fw_work->work, request_firmware_work_func);
|
|
schedule_work(&fw_work->work);
|
|
return 0;
|
|
}
|
|
|
|
+/**
|
|
+ * cache_firmware - cache one firmware image in kernel memory space
|
|
+ * @fw_name: the firmware image name
|
|
+ *
|
|
+ * Cache firmware in kernel memory so that drivers can use it when
|
|
+ * system isn't ready for them to request firmware image from userspace.
|
|
+ * Once it returns successfully, driver can use request_firmware or its
|
|
+ * nowait version to get the cached firmware without any interacting
|
|
+ * with userspace
|
|
+ *
|
|
+ * Return 0 if the firmware image has been cached successfully
|
|
+ * Return !0 otherwise
|
|
+ *
|
|
+ */
|
|
+int cache_firmware(const char *fw_name)
|
|
+{
|
|
+ int ret;
|
|
+ const struct firmware *fw;
|
|
+
|
|
+ pr_debug("%s: %s\n", __func__, fw_name);
|
|
+
|
|
+ ret = request_firmware(&fw, fw_name, NULL);
|
|
+ if (!ret)
|
|
+ kfree(fw);
|
|
+
|
|
+ pr_debug("%s: %s ret=%d\n", __func__, fw_name, ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * uncache_firmware - remove one cached firmware image
|
|
+ * @fw_name: the firmware image name
|
|
+ *
|
|
+ * Uncache one firmware image which has been cached successfully
|
|
+ * before.
|
|
+ *
|
|
+ * Return 0 if the firmware cache has been removed successfully
|
|
+ * Return !0 otherwise
|
|
+ *
|
|
+ */
|
|
+int uncache_firmware(const char *fw_name)
|
|
+{
|
|
+ struct firmware_buf *buf;
|
|
+ struct firmware fw;
|
|
+
|
|
+ pr_debug("%s: %s\n", __func__, fw_name);
|
|
+
|
|
+ if (fw_get_builtin_firmware(&fw, fw_name))
|
|
+ return 0;
|
|
+
|
|
+ buf = fw_lookup_buf(fw_name);
|
|
+ if (buf) {
|
|
+ fw_free_buf(buf);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+static struct fw_cache_entry *alloc_fw_cache_entry(const char *name)
|
|
+{
|
|
+ struct fw_cache_entry *fce;
|
|
+
|
|
+ fce = kzalloc(sizeof(*fce) + strlen(name) + 1, GFP_ATOMIC);
|
|
+ if (!fce)
|
|
+ goto exit;
|
|
+
|
|
+ strcpy(fce->name, name);
|
|
+exit:
|
|
+ return fce;
|
|
+}
|
|
+
|
|
+static int __fw_entry_found(const char *name)
|
|
+{
|
|
+ struct firmware_cache *fwc = &fw_cache;
|
|
+ struct fw_cache_entry *fce;
|
|
+
|
|
+ list_for_each_entry(fce, &fwc->fw_names, list) {
|
|
+ if (!strcmp(fce->name, name))
|
|
+ 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) {
|
|
+ ret = 1;
|
|
+ list_add(&fce->list, &fwc->fw_names);
|
|
+ pr_debug("%s: fw: %s\n", __func__, name);
|
|
+ }
|
|
+found:
|
|
+ spin_unlock(&fwc->name_lock);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void free_fw_cache_entry(struct fw_cache_entry *fce)
|
|
+{
|
|
+ kfree(fce);
|
|
+}
|
|
+
|
|
+static void __async_dev_cache_fw_image(void *fw_entry,
|
|
+ async_cookie_t cookie)
|
|
+{
|
|
+ struct fw_cache_entry *fce = fw_entry;
|
|
+ struct firmware_cache *fwc = &fw_cache;
|
|
+ int ret;
|
|
+
|
|
+ ret = cache_firmware(fce->name);
|
|
+ if (ret) {
|
|
+ spin_lock(&fwc->name_lock);
|
|
+ list_del(&fce->list);
|
|
+ spin_unlock(&fwc->name_lock);
|
|
+
|
|
+ free_fw_cache_entry(fce);
|
|
+ }
|
|
+
|
|
+ spin_lock(&fwc->name_lock);
|
|
+ fwc->cnt--;
|
|
+ spin_unlock(&fwc->name_lock);
|
|
+
|
|
+ wake_up(&fwc->wait_queue);
|
|
+}
|
|
+
|
|
+/* called with dev->devres_lock held */
|
|
+static void dev_create_fw_entry(struct device *dev, void *res,
|
|
+ void *data)
|
|
+{
|
|
+ struct fw_name_devm *fwn = res;
|
|
+ const char *fw_name = fwn->name;
|
|
+ struct list_head *head = data;
|
|
+ struct fw_cache_entry *fce;
|
|
+
|
|
+ fce = alloc_fw_cache_entry(fw_name);
|
|
+ if (fce)
|
|
+ list_add(&fce->list, head);
|
|
+}
|
|
+
|
|
+static int devm_name_match(struct device *dev, void *res,
|
|
+ void *match_data)
|
|
+{
|
|
+ struct fw_name_devm *fwn = res;
|
|
+ return (fwn->magic == (unsigned long)match_data);
|
|
+}
|
|
+
|
|
+static void dev_cache_fw_image(struct device *dev, void *data)
|
|
+{
|
|
+ LIST_HEAD(todo);
|
|
+ struct fw_cache_entry *fce;
|
|
+ struct fw_cache_entry *fce_next;
|
|
+ struct firmware_cache *fwc = &fw_cache;
|
|
+
|
|
+ devres_for_each_res(dev, fw_name_devm_release,
|
|
+ devm_name_match, &fw_cache,
|
|
+ dev_create_fw_entry, &todo);
|
|
+
|
|
+ list_for_each_entry_safe(fce, fce_next, &todo, list) {
|
|
+ list_del(&fce->list);
|
|
+
|
|
+ spin_lock(&fwc->name_lock);
|
|
+ /* 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);
|
|
+
|
|
+ if (fce)
|
|
+ async_schedule(__async_dev_cache_fw_image,
|
|
+ (void *)fce);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void __device_uncache_fw_images(void)
|
|
+{
|
|
+ struct firmware_cache *fwc = &fw_cache;
|
|
+ struct fw_cache_entry *fce;
|
|
+
|
|
+ spin_lock(&fwc->name_lock);
|
|
+ while (!list_empty(&fwc->fw_names)) {
|
|
+ fce = list_entry(fwc->fw_names.next,
|
|
+ struct fw_cache_entry, list);
|
|
+ list_del(&fce->list);
|
|
+ spin_unlock(&fwc->name_lock);
|
|
+
|
|
+ uncache_firmware(fce->name);
|
|
+ free_fw_cache_entry(fce);
|
|
+
|
|
+ spin_lock(&fwc->name_lock);
|
|
+ }
|
|
+ spin_unlock(&fwc->name_lock);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * device_cache_fw_images - cache devices' firmware
|
|
+ *
|
|
+ * If one device called request_firmware or its nowait version
|
|
+ * successfully before, the firmware names are recored into the
|
|
+ * device's devres link list, so device_cache_fw_images can call
|
|
+ * cache_firmware() to cache these firmwares for the device,
|
|
+ * then the device driver can load its firmwares easily at
|
|
+ * time when system is not ready to complete loading firmware.
|
|
+ */
|
|
+static void device_cache_fw_images(void)
|
|
+{
|
|
+ struct firmware_cache *fwc = &fw_cache;
|
|
+ int old_timeout;
|
|
+ DEFINE_WAIT(wait);
|
|
+
|
|
+ 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
|
|
+ * successfully at lease once, also system is ready for
|
|
+ * completing firmware loading now. The maximum size of
|
|
+ * firmware in current distributions is about 2M bytes,
|
|
+ * so 10 secs should be enough.
|
|
+ */
|
|
+ old_timeout = loading_timeout;
|
|
+ loading_timeout = 10;
|
|
+
|
|
+ mutex_lock(&fw_lock);
|
|
+ fwc->state = FW_LOADER_START_CACHE;
|
|
+ dpm_for_each_dev(NULL, dev_cache_fw_image);
|
|
+ mutex_unlock(&fw_lock);
|
|
+
|
|
+ /* wait for completion of caching firmware for all devices */
|
|
+ spin_lock(&fwc->name_lock);
|
|
+ for (;;) {
|
|
+ prepare_to_wait(&fwc->wait_queue, &wait,
|
|
+ TASK_UNINTERRUPTIBLE);
|
|
+ if (!fwc->cnt)
|
|
+ break;
|
|
+
|
|
+ spin_unlock(&fwc->name_lock);
|
|
+
|
|
+ schedule();
|
|
+
|
|
+ spin_lock(&fwc->name_lock);
|
|
+ }
|
|
+ spin_unlock(&fwc->name_lock);
|
|
+ finish_wait(&fwc->wait_queue, &wait);
|
|
+
|
|
+ loading_timeout = old_timeout;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * device_uncache_fw_images - uncache devices' firmware
|
|
+ *
|
|
+ * uncache all firmwares which have been cached successfully
|
|
+ * by device_uncache_fw_images earlier
|
|
+ */
|
|
+static void device_uncache_fw_images(void)
|
|
+{
|
|
+ pr_debug("%s\n", __func__);
|
|
+ __device_uncache_fw_images();
|
|
+}
|
|
+
|
|
+static void device_uncache_fw_images_work(struct work_struct *work)
|
|
+{
|
|
+ device_uncache_fw_images();
|
|
+}
|
|
+
|
|
+/**
|
|
+ * device_uncache_fw_images_delay - uncache devices firmwares
|
|
+ * @delay: number of milliseconds to delay uncache device firmwares
|
|
+ *
|
|
+ * uncache all devices's firmwares which has been cached successfully
|
|
+ * by device_cache_fw_images after @delay milliseconds.
|
|
+ */
|
|
+static void device_uncache_fw_images_delay(unsigned long delay)
|
|
+{
|
|
+ schedule_delayed_work(&fw_cache.work,
|
|
+ msecs_to_jiffies(delay));
|
|
+}
|
|
+
|
|
+static int fw_pm_notify(struct notifier_block *notify_block,
|
|
+ unsigned long mode, void *unused)
|
|
+{
|
|
+ switch (mode) {
|
|
+ case PM_HIBERNATION_PREPARE:
|
|
+ case PM_SUSPEND_PREPARE:
|
|
+ device_cache_fw_images();
|
|
+ break;
|
|
+
|
|
+ case PM_POST_SUSPEND:
|
|
+ case PM_POST_HIBERNATION:
|
|
+ case PM_POST_RESTORE:
|
|
+ /*
|
|
+ * In case that system sleep failed and syscore_suspend is
|
|
+ * not called.
|
|
+ */
|
|
+ mutex_lock(&fw_lock);
|
|
+ fw_cache.state = FW_LOADER_NO_CACHE;
|
|
+ mutex_unlock(&fw_lock);
|
|
+
|
|
+ device_uncache_fw_images_delay(10 * MSEC_PER_SEC);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* stop caching firmware once syscore_suspend is reached */
|
|
+static int fw_suspend(void)
|
|
+{
|
|
+ fw_cache.state = FW_LOADER_NO_CACHE;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct syscore_ops fw_syscore_ops = {
|
|
+ .suspend = fw_suspend,
|
|
+};
|
|
+#else
|
|
+static int fw_cache_piggyback_on_request(const char *name)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static void __init fw_cache_init(void)
|
|
+{
|
|
+ spin_lock_init(&fw_cache.lock);
|
|
+ INIT_LIST_HEAD(&fw_cache.head);
|
|
+ fw_cache.state = FW_LOADER_NO_CACHE;
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+ spin_lock_init(&fw_cache.name_lock);
|
|
+ INIT_LIST_HEAD(&fw_cache.fw_names);
|
|
+ fw_cache.cnt = 0;
|
|
+
|
|
+ init_waitqueue_head(&fw_cache.wait_queue);
|
|
+ INIT_DELAYED_WORK(&fw_cache.work,
|
|
+ device_uncache_fw_images_work);
|
|
+
|
|
+ fw_cache.pm_notify.notifier_call = fw_pm_notify;
|
|
+ register_pm_notifier(&fw_cache.pm_notify);
|
|
+
|
|
+ register_syscore_ops(&fw_syscore_ops);
|
|
+#endif
|
|
+}
|
|
+
|
|
static int __init firmware_class_init(void)
|
|
{
|
|
+ fw_cache_init();
|
|
+#ifdef CONFIG_FW_LOADER_USER_HELPER
|
|
return class_register(&firmware_class);
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
}
|
|
|
|
static void __exit firmware_class_exit(void)
|
|
{
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+ unregister_syscore_ops(&fw_syscore_ops);
|
|
+ unregister_pm_notifier(&fw_cache.pm_notify);
|
|
+#endif
|
|
+#ifdef CONFIG_FW_LOADER_USER_HELPER
|
|
class_unregister(&firmware_class);
|
|
+#endif
|
|
}
|
|
|
|
fs_initcall(firmware_class_init);
|
|
@@ -728,3 +1562,5 @@ module_exit(firmware_class_exit);
|
|
EXPORT_SYMBOL(release_firmware);
|
|
EXPORT_SYMBOL(request_firmware);
|
|
EXPORT_SYMBOL(request_firmware_nowait);
|
|
+EXPORT_SYMBOL_GPL(cache_firmware);
|
|
+EXPORT_SYMBOL_GPL(uncache_firmware);
|
|
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
|
|
index 77e3b97..8bde48f 100644
|
|
--- a/drivers/base/power/main.c
|
|
+++ b/drivers/base/power/main.c
|
|
@@ -1387,3 +1387,25 @@ int device_pm_wait_for_dev(struct device *subordinate, struct device *dev)
|
|
return async_error;
|
|
}
|
|
EXPORT_SYMBOL_GPL(device_pm_wait_for_dev);
|
|
+
|
|
+/**
|
|
+ * dpm_for_each_dev - device iterator.
|
|
+ * @data: data for the callback.
|
|
+ * @fn: function to be called for each device.
|
|
+ *
|
|
+ * Iterate over devices in dpm_list, and call @fn for each device,
|
|
+ * passing it @data.
|
|
+ */
|
|
+void dpm_for_each_dev(void *data, void (*fn)(struct device *, void *))
|
|
+{
|
|
+ struct device *dev;
|
|
+
|
|
+ if (!fn)
|
|
+ return;
|
|
+
|
|
+ device_pm_lock();
|
|
+ list_for_each_entry(dev, &dpm_list, power.entry)
|
|
+ fn(dev, data);
|
|
+ device_pm_unlock();
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(dpm_for_each_dev);
|
|
diff --git a/include/linux/device.h b/include/linux/device.h
|
|
index 5ad17cc..ad8904d 100644
|
|
--- a/include/linux/device.h
|
|
+++ b/include/linux/device.h
|
|
@@ -531,6 +531,10 @@ extern void *__devres_alloc(dr_release_t release, size_t size, gfp_t gfp,
|
|
#else
|
|
extern void *devres_alloc(dr_release_t release, size_t size, gfp_t gfp);
|
|
#endif
|
|
+extern void devres_for_each_res(struct device *dev, dr_release_t release,
|
|
+ dr_match_t match, void *match_data,
|
|
+ void (*fn)(struct device *, void *, void *),
|
|
+ void *data);
|
|
extern void devres_free(void *res);
|
|
extern void devres_add(struct device *dev, void *res);
|
|
extern void *devres_find(struct device *dev, dr_release_t release,
|
|
@@ -541,6 +545,8 @@ extern void *devres_remove(struct device *dev, dr_release_t release,
|
|
dr_match_t match, void *match_data);
|
|
extern int devres_destroy(struct device *dev, dr_release_t release,
|
|
dr_match_t match, void *match_data);
|
|
+extern int devres_release(struct device *dev, dr_release_t release,
|
|
+ dr_match_t match, void *match_data);
|
|
|
|
/* devres group */
|
|
extern void * __must_check devres_open_group(struct device *dev, void *id,
|
|
diff --git a/include/linux/firmware.h b/include/linux/firmware.h
|
|
index 1e7c011..e4279fe 100644
|
|
--- a/include/linux/firmware.h
|
|
+++ b/include/linux/firmware.h
|
|
@@ -12,6 +12,9 @@ struct firmware {
|
|
size_t size;
|
|
const u8 *data;
|
|
struct page **pages;
|
|
+
|
|
+ /* firmware loader private fields */
|
|
+ void *priv;
|
|
};
|
|
|
|
struct module;
|
|
@@ -44,6 +47,8 @@ int request_firmware_nowait(
|
|
void (*cont)(const struct firmware *fw, void *context));
|
|
|
|
void release_firmware(const struct firmware *fw);
|
|
+int cache_firmware(const char *name);
|
|
+int uncache_firmware(const char *name);
|
|
#else
|
|
static inline int request_firmware(const struct firmware **fw,
|
|
const char *name,
|
|
@@ -62,6 +67,16 @@ static inline int request_firmware_nowait(
|
|
static inline void release_firmware(const struct firmware *fw)
|
|
{
|
|
}
|
|
+
|
|
+static inline int cache_firmware(const char *name)
|
|
+{
|
|
+ return -ENOENT;
|
|
+}
|
|
+
|
|
+static inline int uncache_firmware(const char *name)
|
|
+{
|
|
+ return -EINVAL;
|
|
+}
|
|
#endif
|
|
|
|
#endif
|
|
diff --git a/include/linux/pm.h b/include/linux/pm.h
|
|
index f067e60..88f034a 100644
|
|
--- a/include/linux/pm.h
|
|
+++ b/include/linux/pm.h
|
|
@@ -638,6 +638,7 @@ extern void __suspend_report_result(const char *function, void *fn, int ret);
|
|
} while (0)
|
|
|
|
extern int device_pm_wait_for_dev(struct device *sub, struct device *dev);
|
|
+extern void dpm_for_each_dev(void *data, void (*fn)(struct device *, void *));
|
|
|
|
extern int pm_generic_prepare(struct device *dev);
|
|
extern int pm_generic_suspend_late(struct device *dev);
|
|
@@ -677,6 +678,10 @@ static inline int device_pm_wait_for_dev(struct device *a, struct device *b)
|
|
return 0;
|
|
}
|
|
|
|
+static inline void dpm_for_each_dev(void *data, void (*fn)(struct device *, void *))
|
|
+{
|
|
+}
|
|
+
|
|
#define pm_generic_prepare NULL
|
|
#define pm_generic_suspend NULL
|
|
#define pm_generic_resume NULL
|
|
--
|
|
2.1.3
|
|
|