Driver core fixes for 3.7-rc3
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: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.19 (GNU/Linux) iEYEABECAAYFAlCKwkIACgkQMUfUDdst+ynzUgCfQDwxUw1PVqQyWy7SakpsjFJJ 8kwAoITyjppn39v1WuZbg0+FZ6JpocyY =2mDG -----END PGP SIGNATURE----- Merge tag 'driver-core-3.7-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core 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: Greg Kroah-Hartman <gregkh@linuxfoundation.org>" * 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
This commit is contained in:
Коммит
299650cad6
|
@ -18,32 +18,40 @@
|
|||
High level behavior (mixed):
|
||||
============================
|
||||
|
||||
kernel(driver): calls request_firmware(&fw_entry, $FIRMWARE, device)
|
||||
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)
|
||||
|
||||
userspace:
|
||||
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):
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
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 是一个分配这类额外含义的典型例子。
|
|
@ -0,0 +1,156 @@
|
|||
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 (保留,将来可能使用)
|
|
@ -0,0 +1,93 @@
|
|||
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
|
|
@ -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);
|
||||
|
||||
vunmap(buf->data);
|
||||
for (i = 0; i < buf->nr_pages; i++)
|
||||
__free_page(buf->pages[i]);
|
||||
kfree(buf->pages);
|
||||
|
||||
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 @@ exit:
|
|||
return fw_priv;
|
||||
}
|
||||
|
||||
/* one pages buffer is mapped/unmapped only once */
|
||||
static int fw_map_pages_buf(struct firmware_buf *buf)
|
||||
{
|
||||
buf->data = vmap(buf->pages, buf->nr_pages, 0, PAGE_KERNEL_RO);
|
||||
if (!buf->data)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* store the pages buffer info firmware from buf */
|
||||
static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw)
|
||||
{
|
||||
|
@ -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 @@ 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;
|
||||
|
@ -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))
|
||||
goto found;
|
||||
}
|
||||
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++;
|
||||
list_add(&fce->list, &fwc->fw_names);
|
||||
/* 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);
|
||||
|
||||
|
|
|
@ -485,20 +485,18 @@ int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd)
|
|||
/**
|
||||
* sysfs_pathname - return full path to sysfs dirent
|
||||
* @sd: sysfs_dirent whose path we want
|
||||
* @path: caller allocated buffer
|
||||
* @path: caller allocated buffer of size PATH_MAX
|
||||
*
|
||||
* Gives the name "/" to the sysfs_root entry; any path returned
|
||||
* is relative to wherever sysfs is mounted.
|
||||
*
|
||||
* XXX: does no error checking on @path size
|
||||
*/
|
||||
static char *sysfs_pathname(struct sysfs_dirent *sd, char *path)
|
||||
{
|
||||
if (sd->s_parent) {
|
||||
sysfs_pathname(sd->s_parent, path);
|
||||
strcat(path, "/");
|
||||
strlcat(path, "/", PATH_MAX);
|
||||
}
|
||||
strcat(path, sd->s_name);
|
||||
strlcat(path, sd->s_name, PATH_MAX);
|
||||
return path;
|
||||
}
|
||||
|
||||
|
@ -531,9 +529,11 @@ int sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd)
|
|||
char *path = kzalloc(PATH_MAX, GFP_KERNEL);
|
||||
WARN(1, KERN_WARNING
|
||||
"sysfs: cannot create duplicate filename '%s'\n",
|
||||
(path == NULL) ? sd->s_name :
|
||||
strcat(strcat(sysfs_pathname(acxt->parent_sd, path), "/"),
|
||||
sd->s_name));
|
||||
(path == NULL) ? sd->s_name
|
||||
: (sysfs_pathname(acxt->parent_sd, path),
|
||||
strlcat(path, "/", PATH_MAX),
|
||||
strlcat(path, sd->s_name, PATH_MAX),
|
||||
path));
|
||||
kfree(path);
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ int __dynamic_netdev_dbg(struct _ddebug *descriptor,
|
|||
const char *fmt, ...);
|
||||
|
||||
#define DEFINE_DYNAMIC_DEBUG_METADATA(name, fmt) \
|
||||
static struct _ddebug __used __aligned(8) \
|
||||
static struct _ddebug __aligned(8) \
|
||||
__attribute__((section("__verbose"))) name = { \
|
||||
.modname = KBUILD_MODNAME, \
|
||||
.function = __func__, \
|
||||
|
|
Загрузка…
Ссылка в новой задаче