Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 299650ca authored by Linus Torvalds's avatar Linus Torvalds
Browse files


Pull driver core fixes from Greg Kroah-Hartman:
 "Here are a number of firmware core fixes for 3.7, and some other minor
  fixes.  And some documentation updates thrown in for good measure.

  All have been in the linux-next tree for a while.

  Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org&gt;">

* tag 'driver-core-3.7-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core:
  Documentation:Chinese translation of Documentation/arm64/memory.txt
  Documentation:Chinese translation of Documentation/arm64/booting.txt
  Documentation:Chinese translation of Documentation/IRQ.txt
  firmware loader: document kernel direct loading
  sysfs: sysfs_pathname/sysfs_add_one: Use strlcat() instead of strcat()
  dynamic_debug: Remove unnecessary __used
  firmware loader: sync firmware cache by async_synchronize_full_domain
  firmware loader: let direct loading back on 'firmware_buf'
  firmware loader: fix one reqeust_firmware race
  firmware loader: cancel uncache work before caching firmware
parents f76ddd98 bf34be05
Loading
Loading
Loading
Loading
+17 −9
Original line number Diff line number Diff line
@@ -18,32 +18,40 @@
 High level behavior (mixed):
 ============================

 kernel(driver): calls request_firmware(&fw_entry, $FIRMWARE, device)

 userspace:
 1), kernel(driver):
	- calls request_firmware(&fw_entry, $FIRMWARE, device)
	- kernel searchs the fimware image with name $FIRMWARE directly
	in the below search path of root filesystem:
		"/lib/firmware/updates/" UTS_RELEASE,
		"/lib/firmware/updates",
		"/lib/firmware/" UTS_RELEASE,
		"/lib/firmware"
	- If found, goto 7), else goto 2)

 2), userspace:
 	- /sys/class/firmware/xxx/{loading,data} appear.
	- hotplug gets called with a firmware identifier in $FIRMWARE
	  and the usual hotplug environment.
		- hotplug: echo 1 > /sys/class/firmware/xxx/loading

 kernel: Discard any previous partial load.
 3), kernel: Discard any previous partial load.

 userspace:
 4), userspace:
		- hotplug: cat appropriate_firmware_image > \
					/sys/class/firmware/xxx/data

 kernel: grows a buffer in PAGE_SIZE increments to hold the image as it
 5), kernel: grows a buffer in PAGE_SIZE increments to hold the image as it
	 comes in.

 userspace:
 6), userspace:
		- hotplug: echo 0 > /sys/class/firmware/xxx/loading

 kernel: request_firmware() returns and the driver has the firmware
 7), kernel: request_firmware() returns and the driver has the firmware
	 image in fw_entry->{data,size}. If something went wrong
	 request_firmware() returns non-zero and fw_entry is set to
	 NULL.

 kernel(driver): Driver code calls release_firmware(fw_entry) releasing
 8), kernel(driver): Driver code calls release_firmware(fw_entry) releasing
		 the firmware image and any related resource.

 High level behavior (driver code):
+39 −0
Original line number Diff line number Diff line
Chinese translated version of Documentation/IRQ.txt

If you have any comment or update to the content, please contact the
original document maintainer directly.  However, if you have a problem
communicating in English you can also ask the Chinese maintainer for
help.  Contact the Chinese maintainer if this translation is outdated
or if there is a problem with the translation.

Maintainer: Eric W. Biederman <ebiederman@xmission.com>
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
---------------------------------------------------------------------
Documentation/IRQ.txt 的中文翻译

如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
译存在问题,请联系中文版维护者。
英文版维护者: Eric W. Biederman <ebiederman@xmission.com>
中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>


以下为正文
---------------------------------------------------------------------
何为 IRQ?

一个 IRQ 是来自某个设备的一个中断请求。目前,它们可以来自一个硬件引脚,
或来自一个数据包。多个设备可能连接到同个硬件引脚,从而共享一个 IRQ。

一个 IRQ 编号是用于告知硬件中断源的内核标识。通常情况下,这是一个
全局 irq_desc 数组的索引,但是除了在 linux/interrupt.h 中的实现,
具体的细节是体系结构特定的。

一个 IRQ 编号是设备上某个可能的中断源的枚举。通常情况下,枚举的编号是
该引脚在系统内中断控制器的所有输入引脚中的编号。对于 ISA 总线中的情况,
枚举的是在两个 i8259 中断控制器中 16 个输入引脚。

架构可以对 IRQ 编号指定额外的含义,在硬件涉及任何手工配置的情况下,
是被提倡的。ISA 的 IRQ 是一个分配这类额外含义的典型例子。
+156 −0
Original line number Diff line number Diff line
Chinese translated version of Documentation/arm64/booting.txt

If you have any comment or update to the content, please contact the
original document maintainer directly.  However, if you have a problem
communicating in English you can also ask the Chinese maintainer for
help.  Contact the Chinese maintainer if this translation is outdated
or if there is a problem with the translation.

Maintainer: Will Deacon <will.deacon@arm.com>
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
---------------------------------------------------------------------
Documentation/arm64/booting.txt 的中文翻译

如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
译存在问题,请联系中文版维护者。

英文版维护者: Will Deacon <will.deacon@arm.com>
中文版维护者: 傅炜  Fu Wei <tekkamanninja@gmail.com>
中文版翻译者: 傅炜  Fu Wei <tekkamanninja@gmail.com>
中文版校译者: 傅炜  Fu Wei <tekkamanninja@gmail.com>

以下为正文
---------------------------------------------------------------------
			启动 AArch64 Linux
			==================

作者: Will Deacon <will.deacon@arm.com>
日期: 2012 年 09 月 07 日

本文档基于 Russell King 的 ARM 启动文档,且适用于所有公开发布的
AArch64 Linux 内核代码。

AArch64 异常模型由多个异常级别(EL0 - EL3)组成,对于 EL0 和 EL1
异常级有对应的安全和非安全模式。EL2 是系统管理级,且仅存在于
非安全模式下。EL3 是最高特权级,且仅存在于安全模式下。

基于本文档的目的,我们将简单地使用‘引导装载程序’(‘boot loader’)
这个术语来定义在将控制权交给 Linux 内核前 CPU 上执行的所有软件。
这可能包含安全监控和系统管理代码,或者它可能只是一些用于准备最小启动
环境的指令。

基本上,引导装载程序(至少)应实现以下操作:

1、设置和初始化 RAM
2、设置设备树数据
3、解压内核映像
4、调用内核映像


1、设置和初始化 RAM
-----------------

必要性: 强制

引导装载程序应该找到并初始化系统中所有内核用于保持系统变量数据的 RAM。
这个操作的执行是设备依赖的。(它可能使用内部算法来自动定位和计算所有
RAM,或可能使用对这个设备已知的 RAM 信息,还可能使用任何引导装载程序
设计者想到的匹配方法。)


2、设置设备树数据
---------------

必要性: 强制

设备树数据块(dtb)大小必须不大于 2 MB,且位于从内核映像起始算起第一个
512MB 内的 2MB 边界上。这使得内核可以通过初始页表中的单个节描述符来
映射此数据块。


3、解压内核映像
-------------

必要性: 可选

AArch64 内核当前没有提供自解压代码,因此如果使用了压缩内核映像文件
(比如 Image.gz),则需要通过引导装载程序(使用 gzip 等)来进行解压。
若引导装载程序没有实现这个需求,就要使用非压缩内核映像文件。


4、调用内核映像
-------------

必要性: 强制

已解压的内核映像包含一个 32 字节的头,内容如下:

  u32 magic	= 0x14000008;	/* 跳转到 stext, 小端 */
  u32 res0	= 0;		/* 保留 */
  u64 text_offset;		/* 映像装载偏移 */
  u64 res1	= 0;		/* 保留 */
  u64 res2	= 0;		/* 保留 */

映像必须位于系统 RAM 起始处的特定偏移(当前是 0x80000)。系统 RAM
的起始地址必须是以 2MB 对齐的。

在跳转入内核前,必须符合以下状态:

- 停止所有 DMA 设备,这样内存数据就不会因为虚假网络包或磁盘数据而
  被破坏。这可能可以节省你许多的调试时间。

- 主 CPU 通用寄存器设置
  x0 = 系统 RAM 中设备树数据块(dtb)的物理地址。
  x1 = 0 (保留,将来可能使用)
  x2 = 0 (保留,将来可能使用)
  x3 = 0 (保留,将来可能使用)

- CPU 模式
  所有形式的中断必须在 PSTATE.DAIF 中被屏蔽(Debug、SError、IRQ
  和 FIQ)。
  CPU 必须处于 EL2(推荐,可访问虚拟化扩展)或非安全 EL1 模式下。

- 高速缓存、MMU
  MMU 必须关闭。
  指令缓存开启或关闭都可以。
  数据缓存必须关闭且无效。
  外部高速缓存(如果存在)必须配置并禁用。

- 架构计时器
  CNTFRQ 必须设定为计时器的频率。
  如果在 EL1 模式下进入内核,则 CNTHCTL_EL2 中的 EL1PCTEN (bit 0)
  必须置位。

- 一致性
  通过内核启动的所有 CPU 在内核入口地址上必须处于相同的一致性域中。
  这可能要根据具体实现来定义初始化过程,以使能每个CPU上对维护操作的
  接收。

- 系统寄存器
  在进入内核映像的异常级中,所有构架中可写的系统寄存器必须通过软件
  在一个更高的异常级别下初始化,以防止在 未知 状态下运行。

引导装载程序必须在每个 CPU 处于以下状态时跳入内核入口:

- 主 CPU 必须直接跳入内核映像的第一条指令。通过此 CPU 传递的设备树
  数据块必须在每个 CPU 节点中包含以下内容:

    1、‘enable-method’属性。目前,此字段支持的值仅为字符串“spin-table”。

    2、‘cpu-release-addr’标识一个 64-bit、初始化为零的内存位置。

  引导装载程序必须生成这些设备树属性,并在跳入内核入口之前将其插入
  数据块。

- 任何辅助 CPU 必须在内存保留区(通过设备树中的 /memreserve/ 域传递
  给内核)中自旋于内核之外,轮询它们的 cpu-release-addr 位置(必须
  包含在保留区中)。可通过插入 wfe 指令来降低忙循环开销,而主 CPU 将
  发出 sev 指令。当对 cpu-release-addr 所指位置的读取操作返回非零值
  时,CPU 必须直接跳入此值所指向的地址。

- 辅助 CPU 通用寄存器设置
  x0 = 0 (保留,将来可能使用)
  x1 = 0 (保留,将来可能使用)
  x2 = 0 (保留,将来可能使用)
  x3 = 0 (保留,将来可能使用)
+93 −0
Original line number Diff line number Diff line
Chinese translated version of Documentation/arm64/memory.txt

If you have any comment or update to the content, please contact the
original document maintainer directly.  However, if you have a problem
communicating in English you can also ask the Chinese maintainer for
help.  Contact the Chinese maintainer if this translation is outdated
or if there is a problem with the translation.

Maintainer: Catalin Marinas <catalin.marinas@arm.com>
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
---------------------------------------------------------------------
Documentation/arm64/memory.txt 的中文翻译

如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
译存在问题,请联系中文版维护者。

英文版维护者: Catalin Marinas <catalin.marinas@arm.com>
中文版维护者: 傅炜  Fu Wei <tekkamanninja@gmail.com>
中文版翻译者: 傅炜  Fu Wei <tekkamanninja@gmail.com>
中文版校译者: 傅炜  Fu Wei <tekkamanninja@gmail.com>

以下为正文
---------------------------------------------------------------------
		     Linux 在 AArch64 中的内存布局
		     ===========================

作者: Catalin Marinas <catalin.marinas@arm.com>
日期: 2012 年 02 月 20 日

本文档描述 AArch64 Linux 内核所使用的虚拟内存布局。此构架可以实现
页大小为 4KB 的 4 级转换表和页大小为 64KB 的 3 级转换表。

AArch64 Linux 使用页大小为 4KB 的 3 级转换表配置,对于用户和内核
都有 39-bit (512GB) 的虚拟地址空间。对于页大小为 64KB的配置,仅
使用 2 级转换表,但内存布局相同。

用户地址空间的 63:39 位为 0,而内核地址空间的相应位为 1。TTBRx 的
选择由虚拟地址的 63 位给出。swapper_pg_dir 仅包含内核(全局)映射,
而用户 pgd 仅包含用户(非全局)映射。swapper_pgd_dir 地址被写入
TTBR1 中,且从不写入 TTBR0。


AArch64 Linux 内存布局:

起始地址			结束地址			大小		用途
-----------------------------------------------------------------------
0000000000000000	0000007fffffffff	 512GB		用户空间

ffffff8000000000	ffffffbbfffcffff	~240GB		vmalloc

ffffffbbfffd0000	ffffffbcfffdffff	  64KB		[防护页]

ffffffbbfffe0000	ffffffbcfffeffff	  64KB		PCI I/O 空间

ffffffbbffff0000	ffffffbcffffffff	  64KB		[防护页]

ffffffbc00000000	ffffffbdffffffff	   8GB		vmemmap

ffffffbe00000000	ffffffbffbffffff	  ~8GB		[防护页,未来用于 vmmemap]

ffffffbffc000000	ffffffbfffffffff	  64MB		模块

ffffffc000000000	ffffffffffffffff	 256GB		内存空间


4KB 页大小的转换表查找:

+--------+--------+--------+--------+--------+--------+--------+--------+
|63    56|55    48|47    40|39    32|31    24|23    16|15     8|7      0|
+--------+--------+--------+--------+--------+--------+--------+--------+
 |                 |         |         |         |         |
 |                 |         |         |         |         v
 |                 |         |         |         |   [11:0]  页内偏移
 |                 |         |         |         +-> [20:12] L3 索引
 |                 |         |         +-----------> [29:21] L2 索引
 |                 |         +---------------------> [38:30] L1 索引
 |                 +-------------------------------> [47:39] L0 索引 (未使用)
 +-------------------------------------------------> [63] TTBR0/1


64KB 页大小的转换表查找:

+--------+--------+--------+--------+--------+--------+--------+--------+
|63    56|55    48|47    40|39    32|31    24|23    16|15     8|7      0|
+--------+--------+--------+--------+--------+--------+--------+--------+
 |                 |    |               |              |
 |                 |    |               |              v
 |                 |    |               |            [15:0]  页内偏移
 |                 |    |               +----------> [28:16] L3 索引
 |                 |    +--------------------------> [41:29] L2 索引 (仅使用 38:29 )
 |                 +-------------------------------> [47:42] L1 索引 (未使用)
 +-------------------------------------------------> [63] TTBR0/1
+151 −115
Original line number Diff line number Diff line
@@ -36,68 +36,6 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
MODULE_DESCRIPTION("Multi purpose firmware loading support");
MODULE_LICENSE("GPL");

static const char *fw_path[] = {
	"/lib/firmware/updates/" UTS_RELEASE,
	"/lib/firmware/updates",
	"/lib/firmware/" UTS_RELEASE,
	"/lib/firmware"
};

/* Don't inline this: 'struct kstat' is biggish */
static noinline 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 *fw)
{
	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->data = buf;
	fw->size = size;
	return true;
}

static bool fw_get_filesystem_firmware(struct firmware *fw, const char *name)
{
	int i;
	bool success = false;
	char *path = __getname();

	for (i = 0; i < ARRAY_SIZE(fw_path); i++) {
		struct file *file;
		snprintf(path, PATH_MAX, "%s/%s", fw_path[i], name);

		file = filp_open(path, O_RDONLY, 0);
		if (IS_ERR(file))
			continue;
		success = fw_read_file_contents(file, fw);
		fput(file);
		if (success)
			break;
	}
	__putname(path);
	return success;
}

/* Builtin firmware support */

#ifdef CONFIG_FW_LOADER
@@ -150,6 +88,11 @@ enum {
	FW_STATUS_ABORT,
};

enum fw_buf_fmt {
	VMALLOC_BUF,	/* used in direct loading */
	PAGE_BUF,	/* used in loading via userspace */
};

static int loading_timeout = 60;	/* In seconds */

static inline long firmware_loading_timeout(void)
@@ -173,8 +116,6 @@ struct firmware_cache {
	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;
@@ -187,6 +128,7 @@ struct firmware_buf {
	struct completion completion;
	struct firmware_cache *fwc;
	unsigned long status;
	enum fw_buf_fmt fmt;
	void *data;
	size_t size;
	struct page **pages;
@@ -240,6 +182,7 @@ static struct firmware_buf *__allocate_fw_buf(const char *fw_name,
	strcpy(buf->fw_id, fw_name);
	buf->fwc = fwc;
	init_completion(&buf->completion);
	buf->fmt = VMALLOC_BUF;

	pr_debug("%s: fw-%s buf=%p\n", __func__, fw_name, buf);

@@ -307,10 +250,14 @@ static void __fw_free_buf(struct kref *ref)
	list_del(&buf->list);
	spin_unlock(&fwc->lock);


	if (buf->fmt == PAGE_BUF) {
		vunmap(buf->data);
		for (i = 0; i < buf->nr_pages; i++)
			__free_page(buf->pages[i]);
		kfree(buf->pages);
	} else
		vfree(buf->data);
	kfree(buf);
}

@@ -319,6 +266,69 @@ static void fw_free_buf(struct firmware_buf *buf)
	kref_put(&buf->ref, __fw_free_buf);
}

/* direct firmware loading support */
static const char *fw_path[] = {
	"/lib/firmware/updates/" UTS_RELEASE,
	"/lib/firmware/updates",
	"/lib/firmware/" UTS_RELEASE,
	"/lib/firmware"
};

/* Don't inline this: 'struct kstat' is biggish */
static noinline 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 firmware_buf *buf)
{
	int i;
	bool success = false;
	char *path = __getname();

	for (i = 0; i < ARRAY_SIZE(fw_path); i++) {
		struct file *file;
		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);
	return success;
}

static struct firmware_priv *to_firmware_priv(struct device *dev)
{
	return container_of(dev, struct firmware_priv, dev);
@@ -423,6 +433,21 @@ 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->fmt != PAGE_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
@@ -467,6 +492,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 +703,6 @@ fw_create_instance(struct firmware *firmware, const char *fw_name,
	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)
{
@@ -778,11 +802,6 @@ _request_firmware_prepare(const struct firmware **firmware_p, const char *name,
		return NULL;
	}

	if (fw_get_filesystem_firmware(firmware, name)) {
		dev_dbg(device, "firmware: direct-loading firmware %s\n", name);
		return NULL;
	}

	ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf);
	if (!ret)
		fw_priv = fw_create_instance(firmware, name, device,
@@ -832,6 +851,21 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
	struct device *f_dev = &fw_priv->dev;
	struct firmware_buf *buf = fw_priv->buf;
	struct firmware_cache *fwc = &fw_cache;
	int direct_load = 0;

	/* try direct loading from fs first */
	if (fw_get_filesystem_firmware(buf)) {
		dev_dbg(f_dev->parent, "firmware: direct-loading"
			" firmware %s\n", buf->fw_id);

		set_bit(FW_STATUS_DONE, &buf->status);
		complete_all(&buf->completion);
		direct_load = 1;
		goto handle_fw;
	}

	/* fall back on userspace loading */
	buf->fmt = PAGE_BUF;

	dev_set_uevent_suppress(f_dev, true);

@@ -870,6 +904,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,

	del_timer_sync(&fw_priv->timeout);

handle_fw:
	mutex_lock(&fw_lock);
	if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status))
		retval = -ENOENT;
@@ -884,9 +919,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.
@@ -902,6 +934,9 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
	fw_priv->buf = NULL;
	mutex_unlock(&fw_lock);

	if (direct_load)
		goto err_put_dev;

	device_remove_file(f_dev, &dev_attr_loading);
err_del_bin_attr:
	device_remove_bin_file(f_dev, &firmware_attr_data);
@@ -1129,6 +1164,8 @@ int uncache_firmware(const char *fw_name)
}

#ifdef CONFIG_PM_SLEEP
static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain);

static struct fw_cache_entry *alloc_fw_cache_entry(const char *name)
{
	struct fw_cache_entry *fce;
@@ -1142,6 +1179,18 @@ static struct fw_cache_entry *alloc_fw_cache_entry(const char *name)
	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;
@@ -1149,10 +1198,8 @@ static int fw_cache_piggyback_on_request(const char *name)
	int ret = 0;

	spin_lock(&fwc->name_lock);
	list_for_each_entry(fce, &fwc->fw_names, list) {
		if (!strcmp(fce->name, name))
	if (__fw_entry_found(name))
		goto found;
	}

	fce = alloc_fw_cache_entry(name);
	if (fce) {
@@ -1185,12 +1232,6 @@ static void __async_dev_cache_fw_image(void *fw_entry,

		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 */
@@ -1229,11 +1270,19 @@ static void dev_cache_fw_image(struct device *dev, void *data)
		list_del(&fce->list);

		spin_lock(&fwc->name_lock);
		fwc->cnt++;
		/* only one cache entry for one firmware */
		if (!__fw_entry_found(fce->name)) {
			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_domain(__async_dev_cache_fw_image,
					      (void *)fce,
					      &fw_cache_domain);
	}
}

@@ -1275,6 +1324,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
@@ -1292,21 +1344,7 @@ static void device_cache_fw_images(void)
	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);
	async_synchronize_full_domain(&fw_cache_domain);

	loading_timeout = old_timeout;
}
@@ -1394,9 +1432,7 @@ static void __init fw_cache_init(void)
#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);

Loading