diff --git a/Documentation/ABI/testing/sysfs-class-extcon b/Documentation/ABI/testing/sysfs-class-extcon index 20ab361bd8c64ea2c3c313e778edc64d26352cc5..57a726232912e46ae61adfc545ace17d25023558 100644 --- a/Documentation/ABI/testing/sysfs-class-extcon +++ b/Documentation/ABI/testing/sysfs-class-extcon @@ -13,7 +13,7 @@ Description: accessory cables have such capability. For example, the 30-pin port of Nuri board (/arch/arm/mach-exynos) may have both HDMI and Charger attached, or analog audio, - video, and USB cables attached simulteneously. + video, and USB cables attached simultaneously. If there are cables mutually exclusive with each other, such binary relations may be expressed with extcon_dev's @@ -35,7 +35,7 @@ Description: The /sys/class/extcon/.../state shows and stores the cable attach/detach information of the corresponding extcon object. If the extcon object has an optional callback "show_state" - defined, the showing function is overriden with the optional + defined, the showing function is overridden with the optional callback. If the default callback for showing function is used, the @@ -46,19 +46,19 @@ Description: TA=1 EAR_JACK=0 # - In this example, the extcon device have USB_OTG and TA + In this example, the extcon device has USB_OTG and TA cables attached and HDMI and EAR_JACK cables detached. In order to update the state of an extcon device, enter a hex - state number starting with 0x. - echo 0xHEX > state + state number starting with 0x: + # echo 0xHEX > state - This updates the whole state of the extcon dev. + This updates the whole state of the extcon device. Inputs of all the methods are required to meet the - mutually_exclusive contidions if they exist. + mutually_exclusive conditions if they exist. It is recommended to use this "global" state interface if - you need to enter the value atomically. The later state + you need to set the value atomically. The later state interface associated with each cable cannot update multiple cable states of an extcon device simultaneously. @@ -73,7 +73,7 @@ What: /sys/class/extcon/.../cable.x/state Date: February 2012 Contact: MyungJoo Ham <myungjoo.ham@samsung.com> Description: - The /sys/class/extcon/.../cable.x/name shows and stores the + The /sys/class/extcon/.../cable.x/state shows and stores the state of cable "x" (integer between 0 and 31) of an extcon device. The state value is either 0 (detached) or 1 (attached). @@ -83,8 +83,8 @@ Date: December 2011 Contact: MyungJoo Ham <myungjoo.ham@samsung.com> Description: Shows the relations of mutually exclusiveness. For example, - if the mutually_exclusive array of extcon_dev is - {0x3, 0x5, 0xC, 0x0}, the, the output is: + if the mutually_exclusive array of extcon device is + {0x3, 0x5, 0xC, 0x0}, then the output is: # ls mutually_exclusive/ 0x3 0x5 diff --git a/Documentation/filesystems/debugfs.txt b/Documentation/filesystems/debugfs.txt index 7a34f827989c1fe5c1f3c29bebf57d101150322c..3a863f6927281bf4e672cf1b8bbeb341c112cb37 100644 --- a/Documentation/filesystems/debugfs.txt +++ b/Documentation/filesystems/debugfs.txt @@ -15,8 +15,8 @@ Debugfs is typically mounted with a command like: mount -t debugfs none /sys/kernel/debug (Or an equivalent /etc/fstab line). -The debugfs root directory is accessible by anyone by default. To -restrict access to the tree the "uid", "gid" and "mode" mount +The debugfs root directory is accessible only to the root user by +default. To change access to the tree the "uid", "gid" and "mode" mount options can be used. Note that the debugfs API is exported GPL-only to modules. diff --git a/Documentation/kobject.txt b/Documentation/kobject.txt index 49578cf1aea5c34f9172db17e43bfa24a099b650..c5182bb2c16c3c2142db906e11cfc604a290f564 100644 --- a/Documentation/kobject.txt +++ b/Documentation/kobject.txt @@ -284,9 +284,11 @@ instead, it is associated with the ktype. So let us introduce struct kobj_type: struct kobj_type { - void (*release)(struct kobject *); + void (*release)(struct kobject *kobj); const struct sysfs_ops *sysfs_ops; - struct attribute **default_attrs; + struct attribute **default_attrs; + const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); + const void *(*namespace)(struct kobject *kobj); }; This structure is used to describe a particular type of kobject (or, more diff --git a/Documentation/zh_CN/arm/Booting b/Documentation/zh_CN/arm/Booting new file mode 100644 index 0000000000000000000000000000000000000000..6158a64df80c5147f2b1c516bf7fa7caa8a9361d --- /dev/null +++ b/Documentation/zh_CN/arm/Booting @@ -0,0 +1,175 @@ +Chinese translated version of Documentation/arm/Booting + +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: Russell King <linux@arm.linux.org.uk> +Chinese maintainer: Fu Wei <tekkamanninja@gmail.com> +--------------------------------------------------------------------- +Documentation/arm/Booting çš„ä¸æ–‡ç¿»è¯‘ + +如果想评论或更新本文的内容,请直接è”ç³»åŽŸæ–‡æ¡£çš„ç»´æŠ¤è€…ã€‚å¦‚æžœä½ ä½¿ç”¨è‹±æ–‡ +äº¤æµæœ‰å›°éš¾çš„è¯ï¼Œä¹Ÿå¯ä»¥å‘䏿–‡ç‰ˆç»´æŠ¤è€…求助。如果本翻译更新ä¸åŠæ—¶æˆ–者翻 +译å˜åœ¨é—®é¢˜ï¼Œè¯·è”ç³»ä¸æ–‡ç‰ˆç»´æŠ¤è€…。 + +英文版维护者: Russell King <linux@arm.linux.org.uk> +䏿–‡ç‰ˆç»´æŠ¤è€…: 傅炜 Fu Wei <tekkamanninja@gmail.com> +䏿–‡ç‰ˆç¿»è¯‘者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +䏿–‡ç‰ˆæ ¡è¯‘者: 傅炜 Fu Wei <tekkamanninja@gmail.com> + +ä»¥ä¸‹ä¸ºæ£æ–‡ +--------------------------------------------------------------------- + + å¯åЍ ARM Linux + ============== + +作者:Russell King +日期:2002å¹´5月18æ—¥ + +以下文档适用于 2.4.18-rmk6 åŠä»¥ä¸Šç‰ˆæœ¬ã€‚ + +为了å¯åЍ ARM Linuxï¼Œä½ éœ€è¦ä¸€ä¸ªå¼•导装载程åºï¼ˆboot loader), +å®ƒæ˜¯ä¸€ä¸ªåœ¨ä¸»å†…æ ¸å¯åЍå‰è¿è¡Œçš„一个å°ç¨‹åºã€‚引导装载程åºéœ€è¦åˆå§‹åŒ–å„ç§ +设备,并最终调用 Linux å†…æ ¸ï¼Œå°†ä¿¡æ¯ä¼ é€’ç»™å†…æ ¸ã€‚ + +从本质上讲,引导装载程åºåº”æä¾›ï¼ˆè‡³å°‘)以下功能: + +1ã€è®¾ç½®å’Œåˆå§‹åŒ– RAM。 +2ã€åˆå§‹åŒ–一个串å£ã€‚ +3ã€æ£€æµ‹æœºå™¨çš„类型(machine type)。 +4ã€è®¾ç½®å†…æ ¸æ ‡ç¾åˆ—表(tagged list)。 +5ã€è°ƒç”¨å†…æ ¸æ˜ åƒã€‚ + + +1ã€è®¾ç½®å’Œåˆå§‹åŒ– RAM +------------------- + +çŽ°æœ‰çš„å¼•å¯¼åŠ è½½ç¨‹åº: 强制 +æ–°å¼€å‘çš„å¼•å¯¼åŠ è½½ç¨‹åº: 强制 + +引导装载程åºåº”该找到并åˆå§‹åŒ–ç³»ç»Ÿä¸æ‰€æœ‰å†…æ ¸ç”¨äºŽä¿æŒç³»ç»Ÿå˜é‡æ•°æ®çš„ RAM。 +这个æ“作的执行是设备ä¾èµ–的。(它å¯èƒ½ä½¿ç”¨å†…部算法æ¥è‡ªåŠ¨å®šä½å’Œè®¡ç®—所有 +RAM,或å¯èƒ½ä½¿ç”¨å¯¹è¿™ä¸ªè®¾å¤‡å·²çŸ¥çš„ RAM ä¿¡æ¯ï¼Œè¿˜å¯èƒ½ä½¿ç”¨ä»»ä½•å¼•å¯¼è£…è½½ç¨‹åº +è®¾è®¡è€…æƒ³åˆ°çš„åŒ¹é…æ–¹æ³•。) + + +2ã€åˆå§‹åŒ–ä¸€ä¸ªä¸²å£ +----------------------------- + +çŽ°æœ‰çš„å¼•å¯¼åŠ è½½ç¨‹åº: å¯é€‰ã€å»ºè®® +æ–°å¼€å‘çš„å¼•å¯¼åŠ è½½ç¨‹åº: å¯é€‰ã€å»ºè®® + +å¼•å¯¼åŠ è½½ç¨‹åºåº”该åˆå§‹åŒ–å¹¶ä½¿èƒ½ä¸€ä¸ªç›®æ ‡æ¿ä¸Šçš„串å£ã€‚è¿™å…è®¸å†…æ ¸ä¸²å£é©±åЍ +自动检测哪个串å£ç”¨äºŽå†…æ ¸æŽ§åˆ¶å°ã€‚(ä¸€èˆ¬ç”¨äºŽè°ƒè¯•æˆ–ä¸Žç›®æ ‡æ¿é€šä¿¡ã€‚) + +ä½œä¸ºæ›¿ä»£æ–¹æ¡ˆï¼Œå¼•å¯¼åŠ è½½ç¨‹åºä¹Ÿå¯ä»¥é€šè¿‡æ ‡ç¾åˆ—è¡¨ä¼ é€’ç›¸å…³çš„'console=' +é€‰é¡¹ç»™å†…æ ¸ä»¥æŒ‡å®šæŸä¸ªä¸²å£ï¼Œè€Œä¸²å£æ•°æ®æ ¼å¼çš„é€‰é¡¹åœ¨ä»¥ä¸‹æ–‡æ¡£ä¸æè¿°ï¼š + + Documentation/kernel-parameters.txt。 + + +3ã€æ£€æµ‹æœºå™¨ç±»åž‹ +-------------------------- + +çŽ°æœ‰çš„å¼•å¯¼åŠ è½½ç¨‹åº: å¯é€‰ +æ–°å¼€å‘çš„å¼•å¯¼åŠ è½½ç¨‹åº: 强制 + +å¼•å¯¼åŠ è½½ç¨‹åºåº”该通过æŸäº›æ–¹å¼æ£€æµ‹è‡ªèº«æ‰€å¤„的机器类型。这是一个硬件 +ä»£ç æˆ–通过查看所连接的硬件用æŸäº›ç®—法得到,这些超出了本文档的范围。 +å¼•å¯¼åŠ è½½ç¨‹åºæœ€ç»ˆå¿…须能æä¾›ä¸€ä¸ª MACH_TYPE_xxx å€¼ç»™å†…æ ¸ã€‚ +(è¯¦è§ linux/arch/arm/tools/mach-types )。 + +4ã€è®¾ç½®å¯åŠ¨æ•°æ® +------------------ + +çŽ°æœ‰çš„å¼•å¯¼åŠ è½½ç¨‹åº: å¯é€‰ã€å¼ºçƒˆå»ºè®® +æ–°å¼€å‘çš„å¼•å¯¼åŠ è½½ç¨‹åº: 强制 + +å¼•å¯¼åŠ è½½ç¨‹åºå¿…é¡»æä¾›æ ‡ç¾åˆ—表或者 dtb æ˜ åƒä»¥ä¼ 递é…置数æ®ç»™å†…æ ¸ã€‚å¯åЍ +æ•°æ®çš„物ç†åœ°å€é€šè¿‡å¯„å˜å™¨ r2 ä¼ é€’ç»™å†…æ ¸ã€‚ + +4aã€è®¾ç½®å†…æ ¸æ ‡ç¾åˆ—表 +-------------------------------- + +bootloader 必须创建和åˆå§‹åŒ–å†…æ ¸æ ‡ç¾åˆ—è¡¨ã€‚ä¸€ä¸ªæœ‰æ•ˆçš„æ ‡ç¾åˆ—表以 +ATAG_CORE æ ‡ç¾å¼€å§‹ï¼Œå¹¶ä»¥ ATAG_NONE æ ‡ç¾ç»“æŸã€‚ATAG_CORE æ ‡ç¾å¯ä»¥æ˜¯ +空的,也å¯ä»¥æ˜¯éžç©ºã€‚一个空 ATAG_CORE æ ‡ç¾å…¶ size 域设置为 +‘2’(0x00000002)。ATAG_NONE æ ‡ç¾çš„ size 域必须设置为零。 + +在列表ä¸å¯ä»¥ä¿å˜ä»»æ„æ•°é‡çš„æ ‡ç¾ã€‚对于一个é‡å¤çš„æ ‡ç¾æ˜¯è¿½åŠ åˆ°ä¹‹å‰æ ‡ç¾ +所æºå¸¦çš„ä¿¡æ¯ä¹‹åŽï¼Œè¿˜æ˜¯ä¼šè¦†ç›–原æ¥çš„ä¿¡æ¯ï¼Œæ˜¯æœªå®šä¹‰çš„。æŸäº›æ ‡ç¾çš„行为 +是å‰è€…,其他是åŽè€…。 + +bootloader å¿…é¡»ä¼ é€’ä¸€ä¸ªç³»ç»Ÿå†…å˜çš„ä½ç½®å’Œæœ€å°å€¼ï¼Œä»¥åŠæ ¹æ–‡ä»¶ç³»ç»Ÿä½ç½®ã€‚ +å› æ¤ï¼Œæœ€å°çš„æ ‡ç¾åˆ—表如下所示: + + +-----------+ +åŸºåœ°å€ -> | ATAG_CORE | | + +-----------+ | + | ATAG_MEM | | 地å€å¢žé•¿æ–¹å‘ + +-----------+ | + | ATAG_NONE | | + +-----------+ v + +æ ‡ç¾åˆ—表应该ä¿å˜åœ¨ç³»ç»Ÿçš„ RAM ä¸ã€‚ + +æ ‡ç¾åˆ—è¡¨å¿…é¡»ç½®äºŽå†…æ ¸è‡ªè§£åŽ‹å’Œ initrd'bootp' 程åºéƒ½ä¸ä¼šè¦†ç›–的内å˜åŒºã€‚ +建议放在 RAM 的头 16KiB ä¸ã€‚ + +4bã€è®¾ç½®è®¾å¤‡æ ‘ +------------------------- + +bootloader 必须以 64bit 地å€å¯¹é½çš„å½¢å¼åŠ è½½ä¸€ä¸ªè®¾å¤‡æ ‘æ˜ åƒ(dtb)到系统 +RAM ä¸ï¼Œå¹¶ç”¨å¯åŠ¨æ•°æ®åˆå§‹åŒ–它。dtb æ ¼å¼åœ¨æ–‡æ¡£ +Documentation/devicetree/booting-without-of.txt ä¸ã€‚å†…æ ¸å°†ä¼šåœ¨ +dtb 物ç†åœ°å€å¤„查找 dtb 锿•°å€¼ï¼ˆ0xd00dfeed),以确定 dtb 是å¦å·²ç»ä»£æ›¿ +æ ‡ç¾åˆ—è¡¨è¢«ä¼ é€’è¿›æ¥ã€‚ + +bootloader å¿…é¡»ä¼ é€’ä¸€ä¸ªç³»ç»Ÿå†…å˜çš„ä½ç½®å’Œæœ€å°å€¼ï¼Œä»¥åŠæ ¹æ–‡ä»¶ç³»ç»Ÿä½ç½®ã€‚ +dtb å¿…é¡»ç½®äºŽå†…æ ¸è‡ªè§£åŽ‹ä¸ä¼šè¦†ç›–的内å˜åŒºã€‚建议将其放置于 RAM 的头 16KiB +ä¸ã€‚但是ä¸å¯å°†å…¶æ”¾ç½®äºŽâ€œ0â€ç‰©ç†åœ°å€å¤„ï¼Œå› ä¸ºå†…æ ¸è®¤ä¸ºï¼šr2 ä¸ä¸º 0,æ„å‘³ç€ +æ²¡æœ‰æ ‡ç¾åˆ—表和 dtb ä¼ é€’è¿‡æ¥ã€‚ + +5ã€è°ƒç”¨å†…æ ¸æ˜ åƒ +--------------------------- + +çŽ°æœ‰çš„å¼•å¯¼åŠ è½½ç¨‹åº: 强制 +æ–°å¼€å‘çš„å¼•å¯¼åŠ è½½ç¨‹åº: 强制 + +è°ƒç”¨å†…æ ¸æ˜ åƒ zImage 有两个选择。如果 zImge ä¿å˜åœ¨ flash ä¸ï¼Œä¸”是为了 +在 flash ä¸ç›´æŽ¥è¿è¡Œè€Œè¢«æ£ç¡®é“¾æŽ¥çš„ã€‚è¿™æ ·å¼•å¯¼åŠ è½½ç¨‹åºå°±å¯ä»¥åœ¨ flash ä¸ +直接调用 zImage。 + +zImage 也å¯ä»¥è¢«æ”¾åœ¨ç³»ç»Ÿ RAM(任æ„ä½ç½®ï¼‰ä¸è¢«è°ƒç”¨ã€‚注æ„ï¼šå†…æ ¸ä½¿ç”¨æ˜ åƒ +基地å€çš„å‰ 16KB RAM 空间æ¥ä¿å˜é¡µè¡¨ã€‚å»ºè®®å°†æ˜ åƒç½®äºŽ RAM çš„ 32KB 处。 + +对于以上任æ„ä¸€ç§æƒ…况,都必须符åˆä»¥ä¸‹å¯åŠ¨çŠ¶æ€ï¼š + +- åœæ¢æ‰€æœ‰ DMA è®¾å¤‡ï¼Œè¿™æ ·å†…å˜æ•°æ®å°±ä¸ä¼šå› 为虚å‡ç½‘络包或ç£ç›˜æ•°æ®è€Œè¢«ç ´å。 + è¿™å¯èƒ½å¯ä»¥èŠ‚çœä½ 许多的调试时间。 + +- CPU 寄å˜å™¨é…ç½® + r0 = 0, + r1 = ï¼ˆåœ¨ä¸Šé¢ 3 ä¸èŽ·å–的)机器类型ç 。 + r2 = æ ‡ç¾åˆ—表在系统 RAM ä¸çš„物ç†åœ°å€ï¼Œæˆ– + è®¾å¤‡æ ‘å—(dtb)在系统 RAM ä¸çš„物ç†åœ°å€ + +- CPU æ¨¡å¼ + 所有形å¼çš„䏿–å¿…é¡»è¢«ç¦æ¢ (IRQs å’Œ FIQs) + CPU 必须处于 SVC 模å¼ã€‚(对于 Angel 调试有特例å˜åœ¨) + +- 缓å˜ï¼ŒMMUs + MMU 必须关é—。 + 指令缓å˜å¼€å¯æˆ–å…³é—都å¯ä»¥ã€‚ + æ•°æ®ç¼“å˜å¿…须关é—。 + +- å¼•å¯¼åŠ è½½ç¨‹åºåº”è¯¥é€šè¿‡ç›´æŽ¥è·³è½¬åˆ°å†…æ ¸æ˜ åƒçš„ç¬¬ä¸€æ¡æŒ‡ä»¤æ¥è°ƒç”¨å†…æ ¸æ˜ åƒã€‚ + + å¯¹äºŽæ”¯æŒ ARM 指令集的 CPUï¼Œè·³å…¥å†…æ ¸å…¥å£æ—¶å¿…须处在 ARM 状æ€ï¼Œå³ä½¿ + 对于 Thumb-2 å†…æ ¸ä¹Ÿæ˜¯å¦‚æ¤ã€‚ + + å¯¹äºŽä»…æ”¯æŒ Thumb 指令集的 CPU,比如 Cortex-M 系列的 CPU,跳入 + å†…æ ¸å…¥å£æ—¶å¿…须处于 Thumb 状æ€ã€‚ diff --git a/Documentation/zh_CN/basic_profiling.txt b/Documentation/zh_CN/basic_profiling.txt new file mode 100644 index 0000000000000000000000000000000000000000..1e6bf0bdf8f54a017e1c98514a8ff0641d98c707 --- /dev/null +++ b/Documentation/zh_CN/basic_profiling.txt @@ -0,0 +1,71 @@ +Chinese translated version of Documentation/basic_profiling + +If you have any comment or update to the content, please post to LKML directly. +However, if you have problem communicating in English you can also ask the +Chinese maintainer for help. Contact the Chinese maintainer, if this +translation is outdated or there is problem with translation. + +Chinese maintainer: Liang Xie <xieliang@xiaomi.com> +--------------------------------------------------------------------- +Documentation/basic_profilingçš„ä¸æ–‡ç¿»è¯‘ + +如果想评论或更新本文的内容,请直接å‘信到LKMLã€‚å¦‚æžœä½ ä½¿ç”¨è‹±æ–‡äº¤æµæœ‰å›°éš¾çš„è¯ï¼Œä¹Ÿå¯ +以å‘䏿–‡ç‰ˆç»´æŠ¤è€…求助。如果本翻译更新ä¸åŠæ—¶æˆ–者翻译å˜åœ¨é—®é¢˜ï¼Œè¯·è”ç³»ä¸æ–‡ç‰ˆç»´æŠ¤è€…。 + +䏿–‡ç‰ˆç»´æŠ¤è€…: 谢良 Liang Xie <xieliang007@gmail.com> +䏿–‡ç‰ˆç¿»è¯‘者: 谢良 Liang Xie <xieliang007@gmail.com> +䏿–‡ç‰ˆæ ¡è¯‘者: +ä»¥ä¸‹ä¸ºæ£æ–‡ +--------------------------------------------------------------------- + +下é¢è¿™äº›è¯´æ˜ŽæŒ‡ä»¤éƒ½æ˜¯éžå¸¸åŸºç¡€çš„ï¼Œå¦‚æžœä½ æƒ³è¿›ä¸€æ¥äº†è§£è¯·é˜…读相关专业文档:) +请ä¸è¦å†åœ¨æœ¬æ–‡æ¡£å¢žåŠ æ–°çš„å†…å®¹ï¼Œä½†å¯ä»¥ä¿®å¤æ–‡æ¡£ä¸çš„错误:)(mbligh@aracnet.com) +感谢John Levon,Dave Hansenç‰åœ¨æ’°å†™æ—¶çš„帮助 + +<test> ç”¨äºŽè¡¨ç¤ºè¦æµ‹é‡çš„ç›®æ ‡ +è¯·å…ˆç¡®ä¿æ‚¨å·²ç»æœ‰æ£ç¡®çš„System.map / vmlinuxé…ç½®ï¼ + +对于linux系统æ¥è¯´ï¼Œé…ç½®vmlinuz最容易的方法å¯èƒ½å°±æ˜¯ä½¿ç”¨â€œmake installâ€ï¼Œç„¶åŽä¿®æ”¹ +/sbin/installkernelå°†vmlinuxæ‹·è´åˆ°/boot目录,而System.map通常是默认安装好的 + +Readprofile +----------- +2.6ç³»åˆ—å†…æ ¸éœ€è¦ç‰ˆæœ¬ç›¸å¯¹è¾ƒæ–°çš„readprofile,比如util-linux 2.12aä¸åŒ…å«çš„,å¯ä»¥ä»Ž: + +http://www.kernel.org/pub/linux/utils/util-linux/ 下载 + +大部分linuxå‘行版已ç»åŒ…å«äº†. + +å¯ç”¨readprofile需è¦åœ¨kernelå¯åŠ¨å‘½ä»¤è¡Œå¢žåŠ â€profile=2“ + +clear readprofile -r + <test> +dump output readprofile -m /boot/System.map > captured_profile + +Oprofile +-------- + +从http://oprofile.sourceforge.net/èŽ·å–æºä»£ç (请å‚考Changes以获å–匹é…的版本) +在kernelå¯åŠ¨å‘½ä»¤è¡Œå¢žåŠ â€œidle=poll†+ +é…ç½®CONFIG_PROFILING=yå’ŒCONFIG_OPROFILE=yç„¶åŽé‡å¯è¿›å…¥æ–°kernel + +./configure --with-kernel-support +make install + +想得到好的测é‡ç»“果,请确ä¿å¯ç”¨äº†æœ¬åœ°APIC特性。如果opreport显示有0Hz CPU, +说明APIC特性没有开å¯ã€‚å¦å¤–注æ„idle=poll选项å¯èƒ½æœ‰æŸæ€§èƒ½ã€‚ + +One time setup: + opcontrol --setup --vmlinux=/boot/vmlinux + +clear opcontrol --reset +start opcontrol --start + <test> +stop opcontrol --stop +dump output opreport > output_file + +如果åªçœ‹kernel相关的报告结果,请è¿è¡Œå‘½ä»¤ opreport -l /boot/vmlinux > output_file + +通过reset选项å¯ä»¥æ¸…ç†è¿‡æœŸç»Ÿè®¡æ•°æ®ï¼Œç›¸å½“于é‡å¯çš„æ•ˆæžœã€‚ + diff --git a/Documentation/zh_CN/filesystems/sysfs.txt b/Documentation/zh_CN/filesystems/sysfs.txt new file mode 100644 index 0000000000000000000000000000000000000000..e230eaa331226901582f33d030af73499a9b9f5d --- /dev/null +++ b/Documentation/zh_CN/filesystems/sysfs.txt @@ -0,0 +1,372 @@ +Chinese translated version of Documentation/filesystems/sysfs.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: Patrick Mochel <mochel@osdl.org> + Mike Murphy <mamurph@cs.clemson.edu> +Chinese maintainer: Fu Wei <tekkamanninja@gmail.com> +--------------------------------------------------------------------- +Documentation/filesystems/sysfs.txt çš„ä¸æ–‡ç¿»è¯‘ + +如果想评论或更新本文的内容,请直接è”ç³»åŽŸæ–‡æ¡£çš„ç»´æŠ¤è€…ã€‚å¦‚æžœä½ ä½¿ç”¨è‹±æ–‡ +äº¤æµæœ‰å›°éš¾çš„è¯ï¼Œä¹Ÿå¯ä»¥å‘䏿–‡ç‰ˆç»´æŠ¤è€…求助。如果本翻译更新ä¸åŠæ—¶æˆ–者翻 +译å˜åœ¨é—®é¢˜ï¼Œè¯·è”ç³»ä¸æ–‡ç‰ˆç»´æŠ¤è€…。 +英文版维护者: Patrick Mochel <mochel@osdl.org> + Mike Murphy <mamurph@cs.clemson.edu> +䏿–‡ç‰ˆç»´æŠ¤è€…: 傅炜 Fu Wei <tekkamanninja@gmail.com> +䏿–‡ç‰ˆç¿»è¯‘者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +䏿–‡ç‰ˆæ ¡è¯‘者: 傅炜 Fu Wei <tekkamanninja@gmail.com> + + +ä»¥ä¸‹ä¸ºæ£æ–‡ +--------------------------------------------------------------------- +sysfs - ç”¨äºŽå¯¼å‡ºå†…æ ¸å¯¹è±¡(kobject)的文件系统 + +Patrick Mochel <mochel@osdl.org> +Mike Murphy <mamurph@cs.clemson.edu> + +修订: 16 August 2011 +原始版本: 10 January 2003 + + +sysfs 简介: +~~~~~~~~~~ + +sysfs 是一个最åˆåŸºäºŽ ramfs 且ä½äºŽå†…å˜çš„æ–‡ä»¶ç³»ç»Ÿã€‚它æä¾›å¯¼å‡ºå†…æ ¸ +æ•°æ®ç»“æž„åŠå…¶å±žæ€§ï¼Œä»¥åŠå®ƒä»¬ä¹‹é—´çš„å…³è”到用户空间的方法。 + +sysfs 始终与 kobject 的底层结构紧密相关。请阅读 +Documentation/kobject.txt 文档以获得更多关于 kobject 接å£çš„ +ä¿¡æ¯ã€‚ + + +使用 sysfs +~~~~~~~~~~~ + +åªè¦å†…æ ¸é…ç½®ä¸å®šä¹‰äº† CONFIG_SYSFS ,sysfs æ€»æ˜¯è¢«ç¼–è¯‘è¿›å†…æ ¸ã€‚ä½ å¯ +通过以下命令挂载它: + + mount -t sysfs sysfs /sys + + +创建目录 +~~~~~~~~ + +任何 kobject åœ¨ç³»ç»Ÿä¸æ³¨å†Œï¼Œå°±ä¼šæœ‰ä¸€ä¸ªç›®å½•在 sysfs ä¸è¢«åˆ›å»ºã€‚这个 +目录是作为该 kobject 的父对象所在目录的åç›®å½•åˆ›å»ºçš„ï¼Œä»¥å‡†ç¡®åœ°ä¼ é€’ +å†…æ ¸çš„å¯¹è±¡å±‚æ¬¡åˆ°ç”¨æˆ·ç©ºé—´ã€‚sysfs ä¸çš„顶层目录代表ç€å†…æ ¸å¯¹è±¡å±‚æ¬¡çš„ +å…±åŒç¥–先;例如:æŸäº›å¯¹è±¡å±žäºŽæŸä¸ªå系统。 + +Sysfs 在与其目录关è”çš„ sysfs_dirent 对象ä¸å†…部ä¿å˜ä¸€ä¸ªæŒ‡å‘实现 +目录的 kobject 的指针。以å‰ï¼Œè¿™ä¸ª kobject 指针被 sysfs 直接用于 +kobject 文件打开和关é—的引用计数。而现在的 sysfs 实现ä¸ï¼Œkobject +引用计数åªèƒ½é€šè¿‡ sysfs_schedule_callback() 函数直接修改。 + + +属性 +~~~~ + +kobject 的属性å¯åœ¨æ–‡ä»¶ç³»ç»Ÿä¸ä»¥æ™®é€šæ–‡ä»¶çš„å½¢å¼å¯¼å‡ºã€‚Sysfs 为属性定义 +了é¢å‘文件 I/O æ“作的方法,以æä¾›å¯¹å†…æ ¸å±žæ€§çš„è¯»å†™ã€‚ + + +属性应为 ASCII ç æ–‡æœ¬æ–‡ä»¶ã€‚以一个文件åªå˜å‚¨ä¸€ä¸ªå±žæ€§å€¼ä¸ºå®œã€‚但一个 +文件åªåŒ…å«ä¸€ä¸ªå±žæ€§å€¼å¯èƒ½å½±å“效率,所以一个包å«ç›¸åŒæ•°æ®ç±»åž‹çš„属性值 +数组也被广泛地接å—。 + +æ··åˆç±»åž‹ã€è¡¨è¾¾å¤šè¡Œæ•°æ®ä»¥åŠä¸€äº›æ€ªå¼‚çš„æ•°æ®æ ¼å¼ä¼šé到强烈åå¯¹ã€‚è¿™æ ·åšæ˜¯ +很丢脸的,而且其代ç 会在未通知作者的情况下被é‡å†™ã€‚ + + +一个简å•的属性结构定义如下: + +struct attribute { + char * name; + struct module *owner; + umode_t mode; +}; + + +int sysfs_create_file(struct kobject * kobj, const struct attribute * attr); +void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr); + + +一个å•独的属性结构并ä¸åŒ…å«è¯»å†™å…¶å±žæ€§å€¼çš„æ–¹æ³•。åç³»ç»Ÿæœ€å¥½ä¸ºå¢žåˆ ç‰¹å®š +对象类型的属性定义自己的属性结构体和å°è£…函数。 + +例如:é©±åŠ¨ç¨‹åºæ¨¡åž‹å®šä¹‰çš„ device_attribute 结构体如下: + +struct device_attribute { + struct attribute attr; + ssize_t (*show)(struct device *dev, struct device_attribute *attr, + char *buf); + ssize_t (*store)(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +}; + +int device_create_file(struct device *, const struct device_attribute *); +void device_remove_file(struct device *, const struct device_attribute *); + +ä¸ºäº†å®šä¹‰è®¾å¤‡å±žæ€§ï¼ŒåŒæ—¶å®šä¹‰äº†ä¸€ä¸‹è¾…助å®: + +#define DEVICE_ATTR(_name, _mode, _show, _store) \ +struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store) + +例如:声明 + +static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo); + +ç‰åŒäºŽå¦‚下代ç : + +static struct device_attribute dev_attr_foo = { + .attr = { + .name = "foo", + .mode = S_IWUSR | S_IRUGO, + .show = show_foo, + .store = store_foo, + }, +}; + + +å系统特有的回调函数 +~~~~~~~~~~~~~~~~~~~ + +当一个å系统定义一个新的属性类型时,必须实现一系列的 sysfs æ“作, +ä»¥å¸®åŠ©è¯»å†™è°ƒç”¨å®žçŽ°å±žæ€§æ‰€æœ‰è€…çš„æ˜¾ç¤ºå’Œå‚¨å˜æ–¹æ³•。 + +struct sysfs_ops { + ssize_t (*show)(struct kobject *, struct attribute *, char *); + ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t); +}; + +[å系统应已ç»å®šä¹‰äº†ä¸€ä¸ª struct kobj_type 结构体作为这个类型的 +æè¿°ç¬¦ï¼Œå¹¶åœ¨æ¤ä¿å˜ sysfs_ops 的指针。更多的信æ¯å‚è§ kobject çš„ +文档] + +sysfs 会为这个类型调用适当的方法。当一个文件被读写时,这个方法会 +将一般的kobject å’Œ attribute 结构体指针转æ¢ä¸ºé€‚å½“çš„æŒ‡é’ˆç±»åž‹åŽ +调用相关è”的函数。 + + +示例: + +#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); + ssize_t ret = -EIO; + + if (dev_attr->show) + ret = dev_attr->show(dev, dev_attr, buf); + if (ret >= (ssize_t)PAGE_SIZE) { + print_symbol("dev_attr_show: %s returned bad count\n", + (unsigned long)dev_attr->show); + } + return ret; +} + + + +è¯»å†™å±žæ€§æ•°æ® +~~~~~~~~~~~~ + +在声明属性时,必须指定 show() 或 store() 方法,以实现属性的 +è¯»æˆ–å†™ã€‚è¿™äº›æ–¹æ³•çš„ç±»åž‹åº”è¯¥å’Œä»¥ä¸‹çš„è®¾å¤‡å±žæ€§å®šä¹‰ä¸€æ ·ç®€å•。 + +ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf); +ssize_t (*store)(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); + +也就是说,他们应åªä»¥ä¸€ä¸ªå¤„ç†å¯¹è±¡ã€ä¸€ä¸ªå±žæ€§å’Œä¸€ä¸ªç¼“å†²æŒ‡é’ˆä½œä¸ºå‚æ•°ã€‚ + +sysfs 会分é…一个大å°ä¸º (PAGE_SIZE) çš„ç¼“å†²åŒºå¹¶ä¼ é€’ç»™è¿™ä¸ªæ–¹æ³•ã€‚ +Sysfs å°†ä¼šä¸ºæ¯æ¬¡è¯»å†™æ“作调用一次这个方法。这使得这些方法在执行时 +会出现以下的行为: + +- 在读方é¢ï¼ˆread(2)),show() 方法应该填充整个缓冲区。回想属性 + 应åªå¯¼å‡ºäº†ä¸€ä¸ªå±žæ€§å€¼æˆ–是一个åŒç±»åž‹å±žæ€§å€¼çš„æ•°ç»„,所以这个代价将 + ä¸ä¼šä¸å¤ªé«˜ã€‚ + + 这使得用户空间å¯ä»¥å±€éƒ¨åœ°è¯»å’Œä»»æ„çš„å‘剿œç´¢æ•´ä¸ªæ–‡ä»¶ã€‚如果用户空间 + å‘åŽæœç´¢åˆ°é›¶æˆ–使用‘0’å移执行一个pread(2)æ“作,show()方法将 + 冿¬¡è¢«è°ƒç”¨ï¼Œä»¥é‡æ–°å¡«å……缓å˜ã€‚ + +- 在写方é¢ï¼ˆwrite(2)),sysfs 希望在第一次写æ“作时得到整个缓冲区。 + ä¹‹åŽ Sysfs ä¼ é€’æ•´ä¸ªç¼“å†²åŒºç»™ store() 方法。 + + 当è¦å†™ sysfs æ–‡ä»¶æ—¶ï¼Œç”¨æˆ·ç©ºé—´è¿›ç¨‹åº”é¦–å…ˆè¯»å–æ•´ä¸ªæ–‡ä»¶ï¼Œä¿®è¯¥æƒ³è¦ + 改å˜çš„值,然åŽå›žå†™æ•´ä¸ªç¼“冲区。 + + 在读写属性值时,属性方法的执行应æ“作相åŒçš„缓冲区。 + +注记: + +- 写æ“作导致的 show() 方法é‡è½½ï¼Œä¼šå¿½ç•¥å½“剿–‡ä»¶ä½ç½®ã€‚ + +- 缓冲区应总是 PAGE_SIZE 大å°ã€‚对于i386,这个值为4096。 + +- show() 方法应该返回写入缓冲区的å—节数,也就是 snprintf()çš„ + 返回值。 + +- show() 应始终使用 snprintf()。 + +- store() 应返回缓冲区的已用å—节数。如果整个缓å˜éƒ½å·²å¡«æ»¡ï¼Œåªéœ€è¿”回 + count 傿•°ã€‚ + +- show() 或 store() å¯ä»¥è¿”å›žé”™è¯¯å€¼ã€‚å½“å¾—åˆ°ä¸€ä¸ªéžæ³•值,必须返回一个 + 错误值。 + +- ä¸€ä¸ªä¼ é€’ç»™æ–¹æ³•çš„å¯¹è±¡å°†ä¼šé€šè¿‡ sysfs 调用对象内嵌的引用计数固定在 + 内å˜ä¸ã€‚尽管如æ¤ï¼Œå¯¹è±¡ä»£è¡¨çš„物ç†å®žä½“(如设备)å¯èƒ½å·²ä¸å˜åœ¨ã€‚如有必è¦ï¼Œ + 应该实现一个检测机制。 + +一个简å•çš„(未ç»å®žéªŒè¯å®žçš„)设备属性实现如下: + +static ssize_t show_name(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%s\n", dev->name); +} + +static ssize_t store_name(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + snprintf(dev->name, sizeof(dev->name), "%.*s", + (int)min(count, sizeof(dev->name) - 1), buf); + return count; +} + +static DEVICE_ATTR(name, S_IRUGO, show_name, store_name); + + +(注æ„:真æ£çš„实现ä¸å…许用户空间设置设备å。) + +顶层目录布局 +~~~~~~~~~~~~ + +sysfs ç›®å½•çš„å®‰æŽ’æ˜¾ç¤ºäº†å†…æ ¸æ•°æ®ç»“构之间的关系。 + +顶层 sysfs 目录如下: + +block/ +bus/ +class/ +dev/ +devices/ +firmware/ +net/ +fs/ + +devices/ 包å«äº†ä¸€ä¸ªè®¾å¤‡æ ‘çš„æ–‡ä»¶ç³»ç»Ÿè¡¨ç¤ºã€‚ä»–ç›´æŽ¥æ˜ å°„äº†å†…éƒ¨çš„å†…æ ¸ +è®¾å¤‡æ ‘ï¼Œåæ˜ 了设备的层次结构。 + +bus/ 包å«äº†å†…æ ¸ä¸å„ç§æ€»çº¿ç±»åž‹çš„å¹³é¢ç›®å½•布局。æ¯ä¸ªæ€»çº¿ç›®å½•包å«ä¸¤ä¸ª +å目录: + + devices/ + drivers/ + +devices/ 包å«äº†ç³»ç»Ÿä¸å‡ºçŽ°çš„æ¯ä¸ªè®¾å¤‡çš„符å·é“¾æŽ¥ï¼Œä»–ä»¬æŒ‡å‘ root/ 下的 +设备目录。 + +drivers/ 包å«äº†æ¯ä¸ªå·²ä¸ºç‰¹å®šæ€»çº¿ä¸Šçš„设备而挂载的驱动程åºçš„目录(这里 +å‡å®šé©±åŠ¨æ²¡æœ‰è·¨è¶Šå¤šä¸ªæ€»çº¿ç±»åž‹)。 + +fs/ 包å«äº†ä¸€ä¸ªä¸ºæ–‡ä»¶ç³»ç»Ÿè®¾ç«‹çš„目录。现在æ¯ä¸ªæƒ³è¦å¯¼å‡ºå±žæ€§çš„æ–‡ä»¶ç³»ç»Ÿå¿…é¡» +在 fs/ 下创建自己的层次结构(å‚è§Documentation/filesystems/fuse.txt)。 + +dev/ 包å«ä¸¤ä¸ªå目录: char/ å’Œ block/。在这两个å目录ä¸ï¼Œæœ‰ä»¥ +<major>:<minor> æ ¼å¼å‘½å的符å·é“¾æŽ¥ã€‚这些符å·é“¾æŽ¥æŒ‡å‘ sysfs 目录 +ä¸ç›¸åº”的设备。/sys/dev æä¾›ä¸€ä¸ªé€šè¿‡ä¸€ä¸ª stat(2) æ“作结果,查找 +设备 sysfs 接å£å¿«æ·çš„æ–¹æ³•。 + +更多有关 driver-model 的特性信æ¯å¯ä»¥åœ¨ Documentation/driver-model/ +䏿‰¾åˆ°ã€‚ + + +TODO: 完æˆè¿™ä¸€èŠ‚ã€‚ + + +å½“å‰æŽ¥å£ +~~~~~~~~ + +以下的接å£å±‚æ™®éå˜åœ¨äºŽå½“å‰çš„sysfsä¸: + +- 设备 (include/linux/device.h) +---------------------------------- +结构体: + +struct device_attribute { + struct attribute attr; + ssize_t (*show)(struct device *dev, struct device_attribute *attr, + char *buf); + ssize_t (*store)(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +}; + +声明: + +DEVICE_ATTR(_name, _mode, _show, _store); + +增/åˆ å±žæ€§: + +int device_create_file(struct device *dev, const struct device_attribute * attr); +void device_remove_file(struct device *dev, const struct device_attribute * attr); + + +- æ€»çº¿é©±åŠ¨ç¨‹åº (include/linux/device.h) +-------------------------------------- +结构体: + +struct bus_attribute { + struct attribute attr; + ssize_t (*show)(struct bus_type *, char * buf); + ssize_t (*store)(struct bus_type *, const char * buf, size_t count); +}; + +声明: + +BUS_ATTR(_name, _mode, _show, _store) + +增/åˆ å±žæ€§: + +int bus_create_file(struct bus_type *, struct bus_attribute *); +void bus_remove_file(struct bus_type *, struct bus_attribute *); + + +- è®¾å¤‡é©±åŠ¨ç¨‹åº (include/linux/device.h) +----------------------------------------- + +结构体: + +struct driver_attribute { + struct attribute attr; + ssize_t (*show)(struct device_driver *, char * buf); + ssize_t (*store)(struct device_driver *, const char * buf, + size_t count); +}; + +声明: + +DRIVER_ATTR(_name, _mode, _show, _store) + +增/åˆ å±žæ€§ï¼š + +int driver_create_file(struct device_driver *, const struct driver_attribute *); +void driver_remove_file(struct device_driver *, const struct driver_attribute *); + + +文档 +~~~~ + +sysfs 目录结构以åŠå…¶ä¸åŒ…å«çš„å±žæ€§å®šä¹‰äº†ä¸€ä¸ªå†…æ ¸ä¸Žç”¨æˆ·ç©ºé—´ä¹‹é—´çš„ ABI。 +对于任何 ABI,其自身的稳定和适当的文档是éžå¸¸é‡è¦çš„。所有新的 sysfs +属性必须在 Documentation/ABI 䏿œ‰æ–‡æ¡£ã€‚è¯¦è§ Documentation/ABI/README。 diff --git a/Documentation/zh_CN/gpio.txt b/Documentation/zh_CN/gpio.txt new file mode 100644 index 0000000000000000000000000000000000000000..4fa7b4e6f856ab163f7403b8c0a3aceb57ce9764 --- /dev/null +++ b/Documentation/zh_CN/gpio.txt @@ -0,0 +1,658 @@ +Chinese translated version of Documentation/gpio.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: Grant Likely <grant.likely@secretlab.ca> + Linus Walleij <linus.walleij@linaro.org> +Chinese maintainer: Fu Wei <tekkamanninja@gmail.com> +--------------------------------------------------------------------- +Documentation/gpio.txt çš„ä¸æ–‡ç¿»è¯‘ + +如果想评论或更新本文的内容,请直接è”ç³»åŽŸæ–‡æ¡£çš„ç»´æŠ¤è€…ã€‚å¦‚æžœä½ ä½¿ç”¨è‹±æ–‡ +äº¤æµæœ‰å›°éš¾çš„è¯ï¼Œä¹Ÿå¯ä»¥å‘䏿–‡ç‰ˆç»´æŠ¤è€…求助。如果本翻译更新ä¸åŠæ—¶æˆ–者翻 +译å˜åœ¨é—®é¢˜ï¼Œè¯·è”ç³»ä¸æ–‡ç‰ˆç»´æŠ¤è€…。 +英文版维护者: Grant Likely <grant.likely@secretlab.ca> + Linus Walleij <linus.walleij@linaro.org> +䏿–‡ç‰ˆç»´æŠ¤è€…: 傅炜 Fu Wei <tekkamanninja@gmail.com> +䏿–‡ç‰ˆç¿»è¯‘者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +䏿–‡ç‰ˆæ ¡è¯‘者: 傅炜 Fu Wei <tekkamanninja@gmail.com> + + +ä»¥ä¸‹ä¸ºæ£æ–‡ +--------------------------------------------------------------------- +GPIO æŽ¥å£ + +本文档æä¾›äº†ä¸€ä¸ªåœ¨Linux下访问GPIO的公约概述。 + +这些函数以 gpio_* 作为å‰ç¼€ã€‚其他的函数ä¸å…è®¸ä½¿ç”¨è¿™æ ·çš„å‰ç¼€æˆ–相关的 +__gpio_* å‰ç¼€ã€‚ + + +什么是GPIO? +========== +"通用输入/输出å£"(GPIO)æ˜¯ä¸€ä¸ªçµæ´»çš„由软件控制的数å—ä¿¡å·ã€‚ä»–ä»¬å¯ +由多ç§èŠ¯ç‰‡æä¾›,且对于从事嵌入å¼å’Œå®šåˆ¶ç¡¬ä»¶çš„ Linux å¼€å‘者æ¥è¯´æ˜¯ +比较熟悉。æ¯ä¸ªGPIO éƒ½ä»£è¡¨ä¸€ä¸ªè¿žæŽ¥åˆ°ç‰¹å®šå¼•è„šæˆ–çƒæ …阵列(BGA)å°è£…ä¸ +“çƒç â€çš„一个ä½ã€‚电路æ¿åŽŸç†å›¾æ˜¾ç¤ºäº† GPIO 与外部硬件的连接关系。 +驱动å¯ä»¥ç¼–写æˆé€šç”¨ä»£ç ,以使æ¿çº§å¯åЍ代ç å¯ä¼ 递引脚é…置数æ®ç»™é©±åŠ¨ã€‚ + +片上系统 (SOC) 处ç†å™¨å¯¹ GPIO 有很大的ä¾èµ–。在æŸäº›æƒ…况下,æ¯ä¸ª +éžä¸“用引脚都å¯é…置为 GPIO,且大多数芯片都最少有一些 GPIO。 +å¯ç¼–程逻辑器件(类似 FPGA) å¯ä»¥æ–¹ä¾¿åœ°æä¾› GPIO。åƒç”µæºç®¡ç†å’Œ +音频编解ç å™¨è¿™æ ·çš„å¤šåŠŸèƒ½èŠ¯ç‰‡ç»å¸¸ç•™æœ‰ä¸€äº›è¿™æ ·çš„引脚æ¥å¸®åŠ©é‚£äº›å¼•è„š +匮ä¹çš„ SOCã€‚åŒæ—¶è¿˜æœ‰é€šè¿‡ I2C 或 SPI 串行总线连接的“GPIO扩展器†+芯片。大多数 PC çš„å—æ¡¥æœ‰ä¸€äº›æ‹¥æœ‰ GPIO 能力的引脚 (åªæœ‰BIOS +固件æ‰çŸ¥é“如何使用他们)。 + +GPIO çš„å®žé™…åŠŸèƒ½å› ç³»ç»Ÿè€Œå¼‚ã€‚é€šå¸¸ç”¨æ³•æœ‰: + + - 输出值å¯å†™ (高电平=1,低电平=0)。一些芯片也有如何驱动这些值的选项, + 例如åªå…è®¸è¾“å‡ºä¸€ä¸ªå€¼ã€æ”¯æŒâ€œçº¿ä¸Žâ€åŠå…¶ä»–å–值类似的模å¼(值得注æ„的是 + “开æ¼â€ä¿¡å·) + + - 输入值å¯è¯»(1ã€0)。一些芯片支æŒå¼•脚在é…ç½®ä¸ºâ€œè¾“å‡ºâ€æ—¶å›žè¯»ï¼Œè¿™å¯¹äºŽç±»ä¼¼ + “线与â€çš„æƒ…况(以支æŒåŒå‘ä¿¡å·)是éžå¸¸æœ‰ç”¨çš„。GPIO 控制器å¯èƒ½æœ‰è¾“å…¥ + 去毛刺/消抖逻辑,这有时需è¦è½¯ä»¶æŽ§åˆ¶ã€‚ + + - 输入通常å¯ä½œä¸º IRQ ä¿¡å·,一般是沿触å‘,但有时是电平触å‘ã€‚è¿™æ ·çš„ IRQ + å¯èƒ½é…置为系统唤醒事件,以将系统从低功耗状æ€ä¸‹å”¤é†’。 + + - 通常一个 GPIO æ ¹æ®ä¸åŒäº§å“电路æ¿çš„需求,å¯ä»¥é…置为输入或输出,也有仅 + 支æŒå•å‘的。 + + - 大部分 GPIO å¯ä»¥åœ¨æŒæœ‰è‡ªæ—‹é”时访问,但是通常由串行总线扩展的 GPIO + ä¸å…è®¸æŒæœ‰è‡ªæ—‹é”。但æŸäº›ç³»ç»Ÿä¹Ÿæ”¯æŒè¿™ç§ç±»åž‹ã€‚ + +对于给定的电路æ¿,æ¯ä¸ª GPIO 都用于æŸä¸ªç‰¹å®šçš„目的,如监控 MMC/SD å¡çš„ +æ’å…¥/ç§»é™¤ã€æ£€æµ‹å¡çš„å†™ä¿æŠ¤çŠ¶æ€ã€é©±åЍ LEDã€é…置收å‘å™¨ã€æ¨¡æ‹Ÿä¸²è¡Œæ€»çº¿ã€ +å¤ä½ç¡¬ä»¶çœ‹é—¨ç‹—ã€æ„ŸçŸ¥å¼€å…³çжæ€ç‰ç‰ã€‚ + + +GPIO 公约 +========= +注æ„,这个å«åšâ€œå…¬çº¦â€ï¼Œå› ä¸ºè¿™ä¸æ˜¯å¼ºåˆ¶æ€§çš„,ä¸éµå¾ªè¿™ä¸ªå…¬çº¦æ˜¯æ— 伤大雅的, +å› ä¸ºæ¤æ—¶å¯ç§»æ¤æ€§å¹¶ä¸é‡è¦ã€‚GPIO 常用于æ¿çº§ç‰¹å®šçš„电路逻辑,甚至å¯èƒ½ +éšç€ç”µè·¯æ¿çš„版本而改å˜ï¼Œä¸”ä¸å¯èƒ½åœ¨ä¸åŒèµ°çº¿çš„电路æ¿ä¸Šä½¿ç”¨ã€‚仅有在少数 +功能上æ‰å…·æœ‰å¯ç§»æ¤æ€§ï¼Œå…¶ä»–功能是平å°ç‰¹å®šã€‚这也是由于“胶åˆâ€çš„é€»è¾‘é€ æˆçš„。 + +æ¤å¤–,这ä¸éœ€è¦ä»»ä½•çš„æ‰§è¡Œæ¡†æž¶ï¼Œåªæ˜¯ä¸€ä¸ªæŽ¥å£ã€‚æŸä¸ªå¹³å°å¯èƒ½é€šè¿‡ä¸€ä¸ªç®€å•地 +访问芯片寄å˜å™¨çš„内è”函数æ¥å®žçŽ°å®ƒï¼Œå…¶ä»–å¹³å°å¯èƒ½é€šè¿‡å§”托一系列ä¸åŒçš„GPIO +控制器的抽象函数æ¥å®žçŽ°å®ƒã€‚(有一些å¯é€‰çš„代ç 能支æŒè¿™ç§ç–略的实现,本文档 +åŽé¢ä¼šä»‹ç»ï¼Œä½†ä½œä¸º GPIO 接å£çš„客户端驱动程åºå¿…é¡»ä¸Žå®ƒçš„å®žçŽ°æ— å…³ã€‚) + +也就是说,如果在他们的平å°ä¸Šæ”¯æŒè¿™ä¸ªå…¬çº¦ï¼Œé©±åŠ¨åº”å°½å¯èƒ½çš„ä½¿ç”¨å®ƒã€‚å¹³å° +必须在 Kconfig ä¸å£°æ˜Žå¯¹ GENERIC_GPIOçš„æ”¯æŒ (布尔型 true),并æä¾› +一个 <asm/gpio.h> æ–‡ä»¶ã€‚é‚£äº›è°ƒç”¨æ ‡å‡† GPIO 函数的驱动应该在 Kconfig +å…¥å£ä¸å£°æ˜Žä¾èµ–GENERIC_GPIOã€‚å½“é©±åŠ¨åŒ…å«æ–‡ä»¶: + + #include <linux/gpio.h> + +则 GPIO 函数是å¯ç”¨,æ— è®ºæ˜¯â€œçœŸå®žä»£ç â€è¿˜æ˜¯ç»ä¼˜åŒ–过的è¯å¥ã€‚å¦‚æžœä½ éµå®ˆ +è¿™ä¸ªå…¬çº¦ï¼Œå½“ä½ çš„ä»£ç 完æˆåŽï¼Œå¯¹å…¶ä»–的开å‘者æ¥è¯´ä¼šæ›´å®¹æ˜“看懂和维护。 + +注æ„,这些æ“ä½œåŒ…å«æ‰€ç”¨å¹³å°çš„ I/O å±éšœä»£ç ï¼Œé©±åŠ¨æ— é¡»æ˜¾å¼åœ°è°ƒç”¨ä»–们。 + + +æ ‡è¯† GPIO +--------- +GPIO æ˜¯é€šè¿‡æ— ç¬¦å·æ•´åž‹æ¥æ ‡è¯†çš„,范围是 0 到 MAX_INT。ä¿ç•™â€œè´Ÿâ€æ•° +用于其他目的,ä¾‹å¦‚æ ‡è¯†ä¿¡å·â€œåœ¨è¿™ä¸ªæ¿å上ä¸å¯ç”¨â€æˆ–指示错误。未接触底层 +硬件的代ç 会忽略这些整数。 + +å¹³å°ä¼šå®šä¹‰è¿™äº›æ•´æ•°çš„用法,且通常使用 #define æ¥å®šä¹‰ GPIOï¼Œè¿™æ · +æ¿çº§ç‰¹å®šçš„å¯åЍ代ç å¯ä»¥ç›´æŽ¥å…³è”相应的原ç†å›¾ã€‚相对æ¥è¯´ï¼Œé©±åŠ¨åº”è¯¥ä»…ä½¿ç”¨ +å¯åЍ代ç ä¼ é€’è¿‡æ¥çš„ GPIO ç¼–å·ï¼Œä½¿ç”¨ platform_data ä¿å˜æ¿çº§ç‰¹å®š +引脚é…ç½®æ•°æ® (åŒæ—¶è¿˜æœ‰å…¶ä»–é¡»è¦çš„æ¿çº§ç‰¹å®šæ•°æ®),é¿å…å¯èƒ½å‡ºçŽ°çš„é—®é¢˜ã€‚ + +例如一个平å°ä½¿ç”¨ç¼–å· 32-159 æ¥æ ‡è¯† GPIO,而在å¦ä¸€ä¸ªå¹³å°ä½¿ç”¨ç¼–å·0-63 +æ ‡è¯†ä¸€ç»„ GPIO 控制器,64-79æ ‡è¯†å¦ä¸€ç±» GPIO 控制器,ä¸”åœ¨ä¸€ä¸ªå«æœ‰ +FPGA 的特定æ¿å上使用 80-95。编å·ä¸ä¸€å®šè¦è¿žç»,那些平å°ä¸ï¼Œä¹Ÿå¯ä»¥ +使用编å·2000-2063æ¥æ ‡è¯†ä¸€ä¸ª I2C 接å£çš„ GPIO 扩展器ä¸çš„ GPIO。 + +å¦‚æžœä½ è¦åˆå§‹åŒ–ä¸€ä¸ªå¸¦æœ‰æ— æ•ˆ GPIO ç¼–å·çš„结构体,å¯ä»¥ä½¿ç”¨ä¸€äº›è´Ÿç¼–ç +(如"-EINVAL"),那将使其永远ä¸ä¼šæ˜¯æœ‰æ•ˆã€‚æ¥æµ‹è¯•è¿™æ ·ä¸€ä¸ªç»“æž„ä½“ä¸çš„ç¼–å· +是å¦å…³è”一个 GPIOï¼Œä½ å¯ä½¿ç”¨ä»¥ä¸‹æ–言: + + int gpio_is_valid(int number); + +如果编å·ä¸å˜åœ¨ï¼Œåˆ™è¯·æ±‚和释放 GPIO çš„å‡½æ•°å°†æ‹’ç»æ‰§è¡Œç›¸å…³æ“作(è§ä¸‹æ–‡)。 +å…¶ä»–ç¼–å·ä¹Ÿå¯èƒ½è¢«æ‹’ç»,比如一个编å·å¯èƒ½å˜åœ¨ï¼Œä½†æš‚时在给定的电路上ä¸å¯ç”¨ã€‚ + +ä¸€ä¸ªå¹³å°æ˜¯å¦æ”¯æŒå¤šä¸ª GPIO 控制器为平å°ç‰¹å®šçš„å®žçŽ°é—®é¢˜ï¼Œå°±åƒæ˜¯å¦å¯ä»¥ +在 GPIO ç¼–å·ç©ºé—´ä¸æœ‰â€œç©ºæ´žâ€å’Œæ˜¯å¦å¯ä»¥åœ¨è¿è¡Œæ—¶æ·»åŠ æ–°çš„æŽ§åˆ¶å™¨ä¸€æ ·ã€‚ +这些问题会影å“其他事情,包括相邻的 GPIO ç¼–å·æ˜¯å¦å˜åœ¨ç‰ã€‚ + +使用 GPIO +--------- +对于一个 GPIO,系统应该åšçš„第一件事情就是通过 gpio_request() +函数分é…它,è§ä¸‹æ–‡ã€‚ + +æŽ¥ä¸‹æ¥æ˜¯è®¾ç½®I/Oæ–¹å‘,这通常是在æ¿çº§å¯åЍ代ç ä¸ä¸ºæ‰€ä½¿ç”¨çš„ GPIO 设置 +platform_device 时完æˆã€‚ + + /* 设置为输入或输出, 返回 0 或负的错误代ç */ + int gpio_direction_input(unsigned gpio); + int gpio_direction_output(unsigned gpio, int value); + +返回值为零代表æˆåŠŸï¼Œå¦åˆ™è¿”回一个负的错误代ç ã€‚è¿™ä¸ªè¿”å›žå€¼éœ€è¦æ£€æŸ¥ï¼Œå› 为 +get/set(获å–/设置)函数调用没法返回错误,且有å¯èƒ½æ˜¯é…置错误。通常, +ä½ åº”è¯¥åœ¨è¿›ç¨‹ä¸Šä¸‹æ–‡ä¸è°ƒç”¨è¿™äº›å‡½æ•°ã€‚然而,对于自旋é”安全的 GPIO,在æ¿å +å¯åŠ¨çš„æ—©æœŸã€è¿›ç¨‹å¯åЍå‰ä½¿ç”¨ä»–们也是å¯ä»¥çš„。 + +对于作为输出的 GPIO,为其æä¾›åˆå§‹è¾“出值,对于é¿å…在系统å¯åŠ¨æœŸé—´å‡ºçŽ° +ä¿¡å·æ¯›åˆºæ˜¯å¾ˆæœ‰å¸®åŠ©çš„ã€‚ + +ä¸ºäº†ä¸Žä¼ ç»Ÿçš„ GPIO 接å£å…¼å®¹, 在设置一个 GPIO æ–¹å‘æ—¶ï¼Œå¦‚果它还未被申请, +则éšå«äº†ç”³è¯·é‚£ä¸ª GPIO çš„æ“作(è§ä¸‹æ–‡)。这ç§å…¼å®¹æ€§æ£åœ¨ä»Žå¯é€‰çš„ gpiolib +框架ä¸ç§»é™¤ã€‚ + +如果这个 GPIO ç¼–ç ä¸å˜åœ¨ï¼Œæˆ–者特定的 GPIO ä¸èƒ½ç”¨äºŽé‚£ç§æ¨¡å¼ï¼Œåˆ™æ–¹å‘ +设置å¯èƒ½å¤±è´¥ã€‚ä¾èµ–å¯åŠ¨å›ºä»¶æ¥æ£ç¡®åœ°è®¾ç½®æ–¹å‘通常是一个å主æ„ï¼Œå› ä¸ºå®ƒå¯èƒ½ +除了å¯åЍLinuxï¼Œå¹¶æ²¡æœ‰åšæ›´å¤šçš„验è¯å·¥ä½œã€‚(åŒç†, æ¿åçš„å¯åЍ代ç å¯èƒ½éœ€è¦ +将这个å¤ç”¨çš„引脚设置为 GPIO,并æ£ç¡®åœ°é…置上拉/下拉电阻。) + + +访问自旋é”安全的 GPIO +------------------- +大多数 GPIO 控制器å¯ä»¥é€šè¿‡å†…å˜è¯»/写指令æ¥è®¿é—®ã€‚这些指令ä¸ä¼šä¼‘çœ ,å¯ä»¥ +安全地在硬(éžçº¿ç¨‹)䏿–例程和类似的上下文ä¸å®Œæˆã€‚ + +对于那些用 gpio_cansleep()测试总是返回失败的 GPIO(è§ä¸‹æ–‡),使用 +以下的函数访问: + + /* GPIO 输入:返回零或éžé›¶ */ + int gpio_get_value(unsigned gpio); + + /* GPIO 输出 */ + void gpio_set_value(unsigned gpio, int value); + +GPIO值是布尔值,零表示低电平,éžé›¶è¡¨ç¤ºé«˜ç”µå¹³ã€‚当读å–一个输出引脚的值时, +è¿”å›žå€¼åº”è¯¥æ˜¯å¼•è„šä¸Šçš„å€¼ã€‚è¿™ä¸ªå€¼ä¸æ€»æ˜¯å’Œè¾“å‡ºå€¼ç›¸ç¬¦ï¼Œå› ä¸ºå˜åœ¨å¼€æ¼è¾“出信å·å’Œ +输出延迟问题。 + +以上的 get/set å‡½æ•°æ— é”™è¯¯è¿”å›žå€¼ï¼Œå› ä¸ºä¹‹å‰ gpio_direction_*()应已检查过 +其是å¦ä¸ºâ€œæ— 效GPIOâ€ã€‚æ¤å¤–ï¼Œè¿˜éœ€è¦æ³¨æ„çš„æ˜¯å¹¶ä¸æ˜¯æ‰€æœ‰å¹³å°éƒ½å¯ä»¥ä»Žè¾“出引脚 +ä¸è¯»å–æ•°æ®ï¼Œå¯¹äºŽä¸èƒ½è¯»å–的引脚应总返回零。å¦å¤–,对那些在原åä¸Šä¸‹æ–‡ä¸æ— 法 +安全访问的 GPIO (è¯‘è€…æ³¨ï¼šå› ä¸ºè®¿é—®å¯èƒ½å¯¼è‡´ä¼‘çœ )使用这些函数是ä¸åˆé€‚çš„ +(è§ä¸‹æ–‡)。 + +在 GPIO ç¼–å·(还有输出ã€å€¼)为常数的情况下,鼓励通过平å°ç‰¹å®šçš„实现æ¥ä¼˜åŒ– +这两个函数æ¥è®¿é—® GPIO å€¼ã€‚è¿™ç§æƒ…况(读写一个硬件寄å˜å™¨)下åªéœ€è¦å‡ æ¡æŒ‡ä»¤ +是很æ£å¸¸çš„,ä¸”æ— é¡»è‡ªæ—‹é”。这ç§ä¼˜åŒ–函数比起那些在å程åºä¸ŠèŠ±è´¹è®¸å¤šæŒ‡ä»¤çš„ +函数å¯ä»¥ä½¿å¾—模拟接å£(译者注:例如 GPIO 模拟 I2Cã€1-wire 或 SPI)çš„ +应用(在空间和时间上都)更具效率。 + + +访问å¯èƒ½ä¼‘çœ çš„ GPIO +----------------- +æŸäº› GPIO 控制器必须通过基于总线(如 I2C 或 SPI)的消æ¯è®¿é—®ã€‚读或写这些 +GPIO 值的命令需è¦ç‰å¾…å…¶ä¿¡æ¯æŽ’åˆ°é˜Ÿé¦–æ‰å‘é€å‘½ä»¤ï¼Œå†èŽ·å¾—å…¶åé¦ˆã€‚æœŸé—´éœ€è¦ +ä¼‘çœ ï¼Œè¿™ä¸èƒ½åœ¨ IRQ 例程(䏿–上下文)䏿‰§è¡Œã€‚ + +æ”¯æŒæ¤ç±» GPIO 的平å°é€šè¿‡ä»¥ä¸‹å‡½æ•°è¿”回éžé›¶å€¼æ¥åŒºåˆ†å‡ºè¿™ç§ GPIO。(æ¤å‡½æ•°éœ€è¦ +一个之å‰é€šè¿‡ gpio_request 分é…到的有效 GPIO ç¼–å·): + + int gpio_cansleep(unsigned gpio); + +ä¸ºäº†è®¿é—®è¿™ç§ GPIO,å†…æ ¸å®šä¹‰äº†ä¸€å¥—ä¸åŒçš„函数: + + /* GPIO 输入:返回零或éžé›¶ ,å¯èƒ½ä¼šä¼‘çœ */ + int gpio_get_value_cansleep(unsigned gpio); + + /* GPIO 输出,å¯èƒ½ä¼šä¼‘çœ */ + void gpio_set_value_cansleep(unsigned gpio, int value); + + +è®¿é—®è¿™æ ·çš„ GPIO 需è¦ä¸€ä¸ªå…è®¸ä¼‘çœ çš„ä¸Šä¸‹æ–‡ï¼Œä¾‹å¦‚çº¿ç¨‹ IRQ 处ç†ä¾‹ç¨‹ï¼Œå¹¶ç”¨ä»¥ä¸Šçš„ +访问函数替æ¢é‚£äº›æ²¡æœ‰ cansleep()åŽç¼€çš„自旋é”安全访问函数。 + +除了这些访问函数å¯èƒ½ä¼‘çœ ï¼Œä¸”å®ƒä»¬æ“作的 GPIO ä¸èƒ½åœ¨ç¡¬ä»¶ IRQ 处ç†ä¾‹ç¨‹ä¸è®¿é—®çš„ +事实,这些处ç†ä¾‹ç¨‹å®žé™…上和自旋é”å®‰å…¨çš„å‡½æ•°æ˜¯ä¸€æ ·çš„ã€‚ + +** 除æ¤ä¹‹å¤– ** 调用设置和é…ç½®æ¤ç±» GPIO 的函数也必须在å…è®¸ä¼‘çœ çš„ä¸Šä¸‹æ–‡ä¸ï¼Œ +å› ä¸ºå®ƒä»¬å¯èƒ½ä¹Ÿéœ€è¦è®¿é—® GPIO 控制器芯片: (这些设置函数通常在æ¿çº§å¯åŠ¨ä»£ç æˆ–者 +驱动探测/æ–开代ç ä¸ï¼Œæ‰€ä»¥è¿™æ˜¯ä¸€ä¸ªå®¹æ˜“æ»¡è¶³çš„çº¦æŸæ¡ä»¶ã€‚) + + gpio_direction_input() + gpio_direction_output() + gpio_request() + +## gpio_request_one() +## gpio_request_array() +## gpio_free_array() + + gpio_free() + gpio_set_debounce() + + + +声明和释放 GPIO +---------------------------- +为了有助于æ•获系统é…置错误,定义了两个函数。 + + /* 申请 GPIO, 返回 0 或负的错误代ç . + * éžç©ºæ ‡ç¾å¯èƒ½æœ‰åŠ©äºŽè¯Šæ–. + */ + int gpio_request(unsigned gpio, const char *label); + + /* 释放之å‰å£°æ˜Žçš„ GPIO */ + void gpio_free(unsigned gpio); + +å°†æ— æ•ˆçš„ GPIO ç¼–ç ä¼ é€’ç»™ gpio_request()会导致失败,申请一个已使用这个 +函数声明过的 GPIO 也会失败。gpio_request()çš„è¿”å›žå€¼å¿…é¡»æ£€æŸ¥ã€‚ä½ åº”è¯¥åœ¨ +进程上下文ä¸è°ƒç”¨è¿™äº›å‡½æ•°ã€‚然而,对于自旋é”安全的 GPIO,在æ¿åå¯åŠ¨çš„æ—©æœŸã€ +è¿›å…¥è¿›ç¨‹ä¹‹å‰æ˜¯å¯ä»¥ç”³è¯·çš„。 + +这个函数完æˆä¸¤ä¸ªåŸºæœ¬çš„ç›®æ ‡ã€‚ä¸€æ˜¯æ ‡è¯†é‚£äº›å®žé™…ä¸Šå·²ä½œä¸º GPIO 使用的信å·çº¿ï¼Œ +è¿™æ ·ä¾¿äºŽæ›´å¥½åœ°è¯Šæ–;系统å¯èƒ½éœ€è¦æœåŠ¡å‡ ç™¾ä¸ªå¯ç”¨çš„ GPIO,但是对于任何一个 +给定的电路æ¿é€šå¸¸åªæœ‰ä¸€äº›è¢«ä½¿ç”¨ã€‚å¦ä¸€ä¸ªç›®çš„æ˜¯æ•获冲çªï¼ŒæŸ¥æ˜Žé”™è¯¯:如两个或 +更多驱动错误地认为他们已ç»ç‹¬å 了æŸä¸ªä¿¡å·çº¿,或是错误地认为移除一个管ç†ç€ +æŸä¸ªå·²æ¿€æ´»ä¿¡å·çš„驱动是安全的。也就是说,申请 GPIO 的作用类似一ç§é”机制。 + +æŸäº›å¹³å°å¯èƒ½ä¹Ÿä½¿ç”¨ GPIO 作为电æºç®¡ç†æ¿€æ´»ä¿¡å·(ä¾‹å¦‚é€šè¿‡å…³é—æœªä½¿ç”¨èŠ¯ç‰‡åŒºå’Œ +简å•åœ°å…³é—æœªä½¿ç”¨æ—¶é’Ÿ)。 + +对于 GPIO 使用 pinctrl å系统已知的引脚,å系统应该被告知其使用情况; +一个 gpiolib 驱动的 .request()æ“作应调用 pinctrl_request_gpio(), +而 gpiolib 驱动的 .free()æ“作应调用 pinctrl_free_gpio()。pinctrl +å系统å…许 pinctrl_request_gpio()在æŸä¸ªå¼•脚或引脚组以å¤ç”¨å½¢å¼â€œå±žäºŽâ€ +一个设备时都æˆåŠŸè¿”å›žã€‚ + +任何须将 GPIO ä¿¡å·å¯¼å‘适当引脚的引脚å¤ç”¨ç¡¬ä»¶çš„编程应该å‘生在 GPIO +驱动的 .direction_input()或 .direction_output()函数ä¸ï¼Œä»¥åŠ +任何输出 GPIO 值的设置之åŽã€‚è¿™æ ·å¯ä½¿ä»Žå¼•脚特殊功能到 GPIO çš„è½¬æ¢ +ä¸ä¼šåœ¨å¼•脚产生毛刺波形。有时当用一个 GPIO 实现其信å·é©±åŠ¨ä¸€ä¸ªéž GPIO +硬件模å—的解决方案时,就需è¦è¿™ç§æœºåˆ¶ã€‚ + +æŸäº›å¹³å°å…许部分或所有 GPIO ä¿¡å·ä½¿ç”¨ä¸åŒçš„引脚。类似的,GPIO 或引脚的 +å…¶ä»–æ–¹é¢ä¹Ÿéœ€è¦é…置,如上拉/下拉。平å°è½¯ä»¶åº”该在对这些 GPIO 调用 +gpio_request()å‰å°†è¿™ç±»ç»†èŠ‚é…置好,例如使用 pinctrl åç³»ç»Ÿçš„æ˜ å°„è¡¨ï¼Œ +使得 GPIO çš„ç”¨æˆ·æ— é¡»å…³æ³¨è¿™äº›ç»†èŠ‚ã€‚ + +还有一个值得注æ„的是在释放 GPIO å‰ï¼Œä½ å¿…é¡»åœæ¢ä½¿ç”¨å®ƒã€‚ + + +注æ„:申请一个 GPIO 并没有以任何方å¼é…置它,åªä¸è¿‡æ ‡è¯†é‚£ä¸ª GPIO 处于使用 +状æ€ã€‚必须有å¦å¤–çš„ä»£ç æ¥å¤„ç†å¼•脚é…ç½®(如控制 GPIO 使用的引脚ã€ä¸Šæ‹‰/下拉)。 +考虑到大多数情况下声明 GPIO 之åŽå°±ä¼šç«‹å³é…置它们,所以定义了以下三个辅助函数: + + /* 申请一个 GPIO ä¿¡å·, åŒæ—¶é€šè¿‡ç‰¹å®šçš„'flags'åˆå§‹åŒ–é…ç½®, + * å…¶ä»–å’Œ gpio_request()çš„å‚æ•°å’Œè¿”å›žå€¼ç›¸åŒ + * + */ + int gpio_request_one(unsigned gpio, unsigned long flags, const char *label); + + /* 在å•个函数ä¸ç”³è¯·å¤šä¸ª GPIO + */ + int gpio_request_array(struct gpio *array, size_t num); + + /* 在å•个函数ä¸é‡Šæ”¾å¤šä¸ª GPIO + */ + void gpio_free_array(struct gpio *array, size_t num); + +这里 'flags' 当å‰å®šä¹‰å¯æŒ‡å®šä»¥ä¸‹å±žæ€§: + + * GPIOF_DIR_IN - é…置方å‘为输入 + * GPIOF_DIR_OUT - é…置方å‘为输出 + + * GPIOF_INIT_LOW - 在作为输出时,åˆå§‹å€¼ä¸ºä½Žç”µå¹³ + * GPIOF_INIT_HIGH - 在作为输出时,åˆå§‹å€¼ä¸ºé«˜ç”µå¹³ + * GPIOF_OPEN_DRAIN - gpio引脚为开æ¼ä¿¡å· + * GPIOF_OPEN_SOURCE - gpioå¼•è„šä¸ºæºæžå¼€è·¯ä¿¡å· + + * GPIOF_EXPORT_DIR_FIXED - å°† gpio 导出到 sysfsï¼Œå¹¶ä¿æŒæ–¹å‘ + * GPIOF_EXPORT_DIR_CHANGEABLE - åŒæ ·æ˜¯å¯¼å‡º, 但å…è®¸æ”¹å˜æ–¹å‘ + +å› ä¸º GPIOF_INIT_* 仅有在é…置为输出的时候æ‰å˜åœ¨,所以有效的组åˆä¸º: + + * GPIOF_IN - é…置为输入 + * GPIOF_OUT_INIT_LOW - é…置为输出,å¹¶åˆå§‹åŒ–为低电平 + * GPIOF_OUT_INIT_HIGH - é…置为输出,å¹¶åˆå§‹åŒ–为高电平 + +当设置 flag 为 GPIOF_OPEN_DRAIN 时,则å‡è®¾å¼•脚是开æ¼ä¿¡å·ã€‚è¿™æ ·çš„å¼•è„š +å°†ä¸ä¼šåœ¨è¾“出模å¼ä¸‹ç½®1ã€‚è¿™æ ·çš„å¼•è„šéœ€è¦è¿žæŽ¥ä¸Šæ‹‰ç”µé˜»ã€‚é€šè¿‡ä½¿èƒ½è¿™ä¸ªæ ‡å¿—ï¼Œgpio库 +å°†ä¼šåœ¨è¢«è¦æ±‚输出模å¼ä¸‹ç½®1时将引脚å˜ä¸ºè¾“å…¥çŠ¶æ€æ¥ä½¿å¼•脚置高。引脚在输出模å¼ä¸‹ +通过置0使其输出低电平。 + +当设置 flag 为 GPIOF_OPEN_SOURCE 时,则å‡è®¾å¼•è„šä¸ºæºæžå¼€è·¯ä¿¡å·ã€‚è¿™æ ·çš„å¼•è„š +å°†ä¸ä¼šåœ¨è¾“出模å¼ä¸‹ç½®0ã€‚è¿™æ ·çš„å¼•è„šéœ€è¦è¿žæŽ¥ä¸‹æ‹‰ç”µé˜»ã€‚é€šè¿‡ä½¿èƒ½è¿™ä¸ªæ ‡å¿—ï¼Œgpio库 +å°†ä¼šåœ¨è¢«è¦æ±‚输出模å¼ä¸‹ç½®0时将引脚å˜ä¸ºè¾“å…¥çŠ¶æ€æ¥ä½¿å¼•脚置低。引脚在输出模å¼ä¸‹ +通过置1使其输出高电平。 + +å°†æ¥è¿™äº›æ ‡å¿—å¯èƒ½æ‰©å±•åˆ°æ”¯æŒæ›´å¤šçš„属性。 + +更进一æ¥,为了更简å•地声明/释放多个 GPIO,'struct gpio'被引进æ¥å°è£…所有 +这三个领域: + + struct gpio { + unsigned gpio; + unsigned long flags; + const char *label; + }; + +一个典型的用例: + + static struct gpio leds_gpios[] = { + { 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* é»˜è®¤å¼€å¯ */ + { 33, GPIOF_OUT_INIT_LOW, "Green LED" }, /* é»˜è®¤å…³é— */ + { 34, GPIOF_OUT_INIT_LOW, "Red LED" }, /* é»˜è®¤å…³é— */ + { 35, GPIOF_OUT_INIT_LOW, "Blue LED" }, /* é»˜è®¤å…³é— */ + { ... }, + }; + + err = gpio_request_one(31, GPIOF_IN, "Reset Button"); + if (err) + ... + + err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios)); + if (err) + ... + + gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios)); + + +GPIO æ˜ å°„åˆ° IRQ +-------------------- +GPIO ç¼–å·æ˜¯æ— ç¬¦å·æ•´æ•°;IRQ ç¼–å·ä¹Ÿæ˜¯ã€‚这些构æˆäº†ä¸¤ä¸ªé€»è¾‘上ä¸åŒçš„命å空间 +(GPIO 0 ä¸ä¸€å®šä½¿ç”¨ IRQ 0)ã€‚ä½ å¯ä»¥é€šè¿‡ä»¥ä¸‹å‡½æ•°åœ¨å®ƒä»¬ä¹‹é—´å®žçŽ°æ˜ å°„: + + /* æ˜ å°„ GPIO ç¼–å·åˆ° IRQ ç¼–å· */ + int gpio_to_irq(unsigned gpio); + + /* æ˜ å°„ IRQ ç¼–å·åˆ° GPIO ç¼–å· (å°½é‡é¿å…使用) */ + int irq_to_gpio(unsigned irq); + +它们的返回值为对应命å空间的相关编å·ï¼Œæˆ–是负的错误代ç (å¦‚æžœæ— æ³•æ˜ å°„)。 +(例如,æŸäº› GPIO æ— æ³•åšä¸º IRQ 使用。)以下的编å·é”™è¯¯æ˜¯æœªç»æ£€æµ‹çš„:使用一个 +未通过 gpio_direction_input()é…置为输入的 GPIO ç¼–å·ï¼Œæˆ–者使用一个 +å¹¶éžæ¥æºäºŽgpio_to_irq()çš„ IRQ ç¼–å·ã€‚ + +è¿™ä¸¤ä¸ªæ˜ å°„å‡½æ•°å¯èƒ½ä¼šåœ¨ä¿¡å·ç¼–å·çš„åŠ å‡è®¡ç®—过程上花些时间。它们ä¸å¯ä¼‘çœ ã€‚ + +gpio_to_irq()返回的éžé”™è¯¯å€¼å¯ä»¥ä¼ 递给 request_irq()或者 free_irq()。 +它们通常通过æ¿çº§ç‰¹å®šçš„åˆå§‹åŒ–代ç å˜æ”¾åˆ°å¹³å°è®¾å¤‡çš„ IRQ 资æºä¸ã€‚注æ„:IRQ +触å‘选项是 IRQ 接å£çš„一部分,如 IRQF_TRIGGER_FALLING,系统唤醒能力 +也是如æ¤ã€‚ + +irq_to_gpio()返回的éžé”™è¯¯å€¼å¤§å¤šæ•°é€šå¸¸å¯ä»¥è¢« gpio_get_value()所使用, +比如在 IRQ æ˜¯æ²¿è§¦å‘æ—¶åˆå§‹åŒ–或更新驱动状æ€ã€‚æ³¨æ„æŸäº›å¹³å°ä¸æ”¯æŒåæ˜ å°„,所以 +ä½ åº”è¯¥å°½é‡é¿å…使用它。 + + +模拟开æ¼ä¿¡å· +---------------------------- +æœ‰æ—¶åœ¨åªæœ‰ä½Žç”µå¹³ä¿¡å·ä½œä¸ºå®žé™…驱动结果(译者注:多个输出连接于一点,逻辑电平 +结果为所有输出的逻辑与)的时候,共享的信å·çº¿éœ€è¦ä½¿ç”¨â€œå¼€æ¼â€ä¿¡å·ã€‚(è¯¥æœ¯è¯ +适用于 CMOS 管;而 TTL 用“集电æžå¼€è·¯â€ã€‚)一个上拉电阻使信å·ä¸ºé«˜ç”µå¹³ã€‚è¿™ +有时被称为“线与â€ã€‚实际上,从负逻辑(低电平为真)的角度æ¥çœ‹ï¼Œè¿™æ˜¯ä¸€ä¸ªâ€œçº¿æˆ–â€ã€‚ + +一个开æ¼ä¿¡å·çš„常è§ä¾‹å是共享的低电平使能 IRQ ä¿¡å·çº¿ã€‚æ¤å¤–,有时åŒå‘æ•°æ®æ€»çº¿ +ä¿¡å·ä¹Ÿä½¿ç”¨æ¼æžå¼€è·¯ä¿¡å·ã€‚ + +æŸäº› GPIO 控制器直接支æŒå¼€æ¼è¾“å‡ºï¼Œè¿˜æœ‰è®¸å¤šä¸æ”¯æŒã€‚å½“ä½ éœ€è¦å¼€æ¼ä¿¡å·ï¼Œä½† +硬件åˆä¸ç›´æŽ¥æ”¯æŒçš„æ—¶å€™ï¼Œä¸€ä¸ªå¸¸ç”¨çš„æ–¹æ³•是用任何å³å¯ä½œè¾“入也å¯ä½œè¾“出的 GPIO +å¼•è„šæ¥æ¨¡æ‹Ÿ: + + LOW: gpio_direction_output(gpio, 0) ... 这代ç 驱动信å·å¹¶è¦†ç›– + 上拉é…置。 + + HIGH: gpio_direction_input(gpio) ... 这代ç å…³é—输出,所以上拉电阻 + (或其他的一些器件)控制了信å·ã€‚ + +å¦‚æžœä½ å°†ä¿¡å·çº¿â€œé©±åЍâ€ä¸ºé«˜ç”µå¹³ï¼Œä½†æ˜¯ gpio_get_value(gpio)报告了一个 +低电平(åœ¨é€‚å½“çš„ä¸Šå‡æ—¶é—´åŽ)ï¼Œä½ å°±å¯ä»¥çŸ¥é“是其他的一些组件将共享信å·çº¿æ‹‰ä½Žäº†ã€‚ +è¿™ä¸ä¸€å®šæ˜¯é”™è¯¯çš„。一个常è§çš„例å就是 I2C 时钟的延长:一个需è¦è¾ƒæ…¢æ—¶é’Ÿçš„ +从设备延迟 SCK çš„ä¸Šå‡æ²¿ï¼Œè€Œ I2C 主设备相应地调整其信å·ä¼ 输速率。 + + +这些公约忽略了什么? +================ +这些公约忽略的最大一件事就是引脚å¤ç”¨ï¼Œå› 为这属于高度芯片特定的属性且 +没有å¯ç§»æ¤æ€§ã€‚æŸä¸ªå¹³å°å¯èƒ½ä¸éœ€è¦æ˜Žç¡®çš„å¤ç”¨ä¿¡æ¯ï¼›æœ‰çš„对于任æ„给定的引脚 +å¯èƒ½åªæœ‰ä¸¤ä¸ªåŠŸèƒ½é€‰é¡¹ï¼›æœ‰çš„å¯èƒ½æ¯ä¸ªå¼•脚有八个功能选项;有的å¯èƒ½å¯ä»¥å°† +å‡ ä¸ªå¼•è„šä¸çš„任何一个作为给定的 GPIO。(是的,这些例å都æ¥è‡ªäºŽå½“å‰è¿è¡Œ +Linux 的系统。) + +在æŸäº›ç³»ç»Ÿä¸,与引脚å¤ç”¨ç›¸å…³çš„æ˜¯é…置和使能集æˆçš„上ã€ä¸‹æ‹‰æ¨¡å¼ã€‚并䏿˜¯æ‰€æœ‰ +å¹³å°éƒ½æ”¯æŒè¿™ç§æ¨¡å¼,或者ä¸ä¼šä»¥ç›¸åŒçš„æ–¹å¼æ¥æ”¯æŒè¿™ç§æ¨¡å¼ï¼›ä¸”ä»»ä½•ç»™å®šçš„ç”µè·¯æ¿ +å¯èƒ½ä½¿ç”¨å¤–置的上拉(或下拉)电阻,这时芯片上的就ä¸åº”该使用。(å½“ä¸€ä¸ªç”µè·¯éœ€è¦ +5kOhm 的拉动电阻,芯片上的 100 kOhm 电阻就ä¸èƒ½åšåˆ°ã€‚)åŒæ ·çš„,驱动能力 +(2 mA vs 20 mA)和电压(1.8V vs 3.3V)是平å°ç‰¹å®šé—®é¢˜,å°±åƒæ¨¡åž‹ä¸€æ ·åœ¨ +å¯é…置引脚和 GPIO 之间(没)有一一对应的关系。 + +还有其他一些系统特定的机制没有在这里指出,例如上述的输入去毛刺和线与输出 +选项。硬件å¯èƒ½æ”¯æŒæ‰¹é‡è¯»æˆ–写 GPIO,但是那一般是é…置相关的:对于处于åŒä¸€ +å—区(bank)çš„GPIO。(GPIO 通常以 16 或 32 个组æˆä¸€ä¸ªåŒºå—,一个给定的 +ç‰‡ä¸Šç³»ç»Ÿä¸€èˆ¬æœ‰å‡ ä¸ªè¿™æ ·çš„åŒºå—。)æŸäº›ç³»ç»Ÿå¯ä»¥é€šè¿‡è¾“出 GPIO è§¦å‘ IRQ, +或者从并éžä»¥ GPIO 管ç†çš„引脚å–å€¼ã€‚è¿™äº›æœºåˆ¶çš„ç›¸å…³ä»£ç æ²¡æœ‰å¿…è¦å…·æœ‰å¯ç§»æ¤æ€§ã€‚ + +当å‰ï¼ŒåЍæ€å®šä¹‰ GPIO 并䏿˜¯æ ‡å‡†çš„,例如作为é…置一个带有æŸäº› GPIO 扩展器的 +é™„åŠ ç”µè·¯æ¿çš„副作用。 + +GPIO 实现者的框架 (å¯é€‰) +===================== +å‰é¢æåˆ°äº†ï¼Œæœ‰ä¸€ä¸ªå¯é€‰çš„实现框架,让平å°ä½¿ç”¨ç›¸åŒçš„编程接å£ï¼Œæ›´åŠ ç®€å•åœ°æ”¯æŒ +ä¸åŒç§ç±»çš„ GPIO 控制器。这个框架称为"gpiolib"。 + +作为一个辅助调试功能,如果 debugfs å¯ç”¨ï¼Œå°±ä¼šæœ‰ä¸€ä¸ª /sys/kernel/debug/gpio +文件。通过这个框架,它å¯ä»¥åˆ—出所有注册的控制器,以åŠå½“剿£åœ¨ä½¿ç”¨ä¸çš„ GPIO +的状æ€ã€‚ + + +控制器驱动: gpio_chip +------------------- +åœ¨æ¡†æž¶ä¸æ¯ä¸ª GPIO 控制器都包装为一个 "struct gpio_chip",他包å«äº† +该类型的æ¯ä¸ªæŽ§åˆ¶å™¨çš„常用信æ¯: + + - 设置 GPIO æ–¹å‘的方法 + - 用于访问 GPIO 值的方法 + - 告知调用其方法是å¦å¯èƒ½ä¼‘çœ çš„æ ‡å¿— + - å¯é€‰çš„ debugfs ä¿¡æ¯å¯¼å‡ºæ–¹æ³• (显示类似上拉é…ç½®ä¸€æ ·çš„é¢å¤–状æ€) + - è¯Šæ–æ ‡ç¾ + +也包å«äº†æ¥è‡ª device.platform_data çš„æ¯ä¸ªå®žä¾‹çš„æ•°æ®ï¼šå®ƒç¬¬ä¸€ä¸ª GPIO çš„ +ç¼–å·å’Œå®ƒå¯ç”¨çš„ GPIO 的数é‡ã€‚ + +实现 gpio_chip 的代ç 应支æŒå¤šæŽ§åˆ¶å™¨å®žä¾‹ï¼Œè¿™å¯èƒ½ä½¿ç”¨é©±åŠ¨æ¨¡åž‹ã€‚é‚£äº›ä»£ç è¦ +é…ç½®æ¯ä¸ª gpio_chip,并å‘èµ·gpiochip_add()。å¸è½½ä¸€ä¸ª GPIO 控制器很少è§ï¼Œ +但在必è¦çš„æ—¶å€™å¯ä»¥ä½¿ç”¨ gpiochip_remove()。 + +大部分 gpio_chip 是一个实例特定结构体的一部分,而并ä¸å°† GPIO 接å£å•独 +暴露出æ¥,比如编å€ã€ç”µæºç®¡ç†ç‰ã€‚类似编解ç å™¨è¿™æ ·çš„èŠ¯ç‰‡ä¼šæœ‰å¤æ‚çš„éž GPIO +状æ€ã€‚ + +任何一个 debugfs ä¿¡æ¯å¯¼å‡ºæ–¹æ³•通常应该忽略还未申请作为 GPIO 的信å·çº¿ã€‚ +他们å¯ä»¥ä½¿ç”¨ gpiochip_is_requested()测试,当这个 GPIO å·²ç»ç”³è¯·è¿‡äº† +å°±è¿”å›žç›¸å…³çš„æ ‡ç¾ï¼Œå¦åˆ™è¿”回 NULL。 + + +平尿”¯æŒ +------- +为了支æŒè¿™ä¸ªæ¡†æž¶ï¼Œä¸€ä¸ªå¹³å°çš„ Kconfig 文件将会 "select"(选择) +ARCH_REQUIRE_GPIOLIB 或 ARCH_WANT_OPTIONAL_GPIOLIB,并让它的 +<asm/gpio.h> åŒ…å« <asm-generic/gpio.h>ï¼ŒåŒæ—¶å®šä¹‰ä¸‰ä¸ªæ–¹æ³•: +gpio_get_value()ã€gpio_set_value()å’Œ gpio_cansleep()。 + +它也应æä¾›ä¸€ä¸ª ARCH_NR_GPIOS çš„å®šä¹‰å€¼ï¼Œè¿™æ ·å¯ä»¥æ›´å¥½åœ°åæ˜ è¯¥å¹³å° GPIO +的实际数é‡,节çœé™æ€è¡¨çš„空间。(这个定义值应该包å«ç‰‡ä¸Šç³»ç»Ÿå†…建 GPIO å’Œ +GPIO 扩展器ä¸çš„æ•°æ®ã€‚) + +ARCH_REQUIRE_GPIOLIB æ„å‘³ç€ gpiolib æ ¸å¿ƒåœ¨è¿™ä¸ªæž„æž¶ä¸å°†æ€»æ˜¯ç¼–è¯‘è¿›å†…æ ¸ã€‚ + +ARCH_WANT_OPTIONAL_GPIOLIB æ„å‘³ç€ gpiolib æ ¸å¿ƒé»˜è®¤å…³é—,且用户å¯ä»¥ +使能它,å¹¶å°†å…¶ç¼–è¯‘è¿›å†…æ ¸(å¯é€‰)。 + +如果这些选项都没被选择,该平å°å°±ä¸é€šè¿‡ GPIO-lib æ”¯æŒ GPIO,且代ç ä¸å¯ä»¥ +被用户使能。 + +以下这些方法的实现å¯ä»¥ç›´æŽ¥ä½¿ç”¨æ¡†æž¶ä»£ç ,并总是通过 gpio_chip 调度: + + #define gpio_get_value __gpio_get_value + #define gpio_set_value __gpio_set_value + #define gpio_cansleep __gpio_cansleep + +这些定义å¯ä»¥ç”¨æ›´ç†æƒ³çš„实现方法替代,那就是使用ç»è¿‡é€»è¾‘优化的内è”函数æ¥è®¿é—® +基于特定片上系统的 GPIO。例如,若引用的 GPIO (寄å˜å™¨ä½åç§»)是常é‡â€œ12â€ï¼Œ +è¯»å–æˆ–设置它å¯èƒ½åªéœ€å°‘则两或三个指令,且ä¸ä¼šä¼‘çœ ã€‚å½“è¿™æ ·çš„ä¼˜åŒ–æ— æ³•å®žçŽ°æ—¶ï¼Œ +那些函数必须使用框架æä¾›çš„代ç ,那就至少è¦å‡ åæ¡æŒ‡ä»¤æ‰å¯ä»¥å®žçŽ°ã€‚å¯¹äºŽç”¨ GPIO +模拟的 I/O 接å£, 如æ¤ç²¾ç®€æŒ‡ä»¤æ˜¯å¾ˆæœ‰æ„义的。 + +对于片上系统,平å°ç‰¹å®šä»£ç 为片上 GPIO æ¯ä¸ªåŒº(bank)定义并注册 gpio_chip +实例。那些 GPIO åº”è¯¥æ ¹æ®èŠ¯ç‰‡åŽ‚å•†çš„æ–‡æ¡£è¿›è¡Œç¼–ç /æ ‡ç¾,并直接和电路æ¿åŽŸç†å›¾ +对应。他们应该开始于零并终æ¢äºŽå¹³å°ç‰¹å®šçš„é™åˆ¶ã€‚这些 GPIO(代ç )通常从 +arch_initcall()或者更早的地方集æˆè¿›å¹³å°åˆå§‹åŒ–代ç ,使这些 GPIO 总是å¯ç”¨ï¼Œ +且他们通常å¯ä»¥ä½œä¸º IRQ 使用。 + +æ¿çº§æ”¯æŒ +------- +对于外部 GPIO 控制器(例如 I2C 或 SPI 扩展器ã€ä¸“用芯片ã€å¤šåŠŸèƒ½å™¨ä»¶ã€FPGA +或 CPLD),大多数常用æ¿çº§ç‰¹å®šä»£ç 都å¯ä»¥æ³¨å†ŒæŽ§åˆ¶å™¨è®¾å¤‡ï¼Œå¹¶ä¿è¯ä»–ä»¬çš„é©±åŠ¨çŸ¥é“ +gpiochip_add()所使用的 GPIO ç¼–å·ã€‚他们的起始编å·é€šå¸¸è·Ÿåœ¨å¹³å°ç‰¹å®šçš„ GPIO +ç¼–å·ä¹‹åŽã€‚ + +例如æ¿çº§å¯åЍ代ç 应该创建结构体指明芯片公开的 GPIO 范围,并使用 platform_data +å°†å…¶ä¼ é€’ç»™æ¯ä¸ª GPIO 扩展器芯片。然åŽèŠ¯ç‰‡é©±åŠ¨ä¸çš„ probe()例程å¯ä»¥å°†è¿™ä¸ª +æ•°æ®ä¼ 递给 gpiochip_add()。 + +åˆå§‹åŒ–顺åºå¾ˆé‡è¦ã€‚例如,如果一个设备ä¾èµ–基于 I2C çš„(扩展)GPIO,那么它的 +probe()例程就应该在那个 GPIO æœ‰æ•ˆä»¥åŽæ‰å¯ä»¥è¢«è°ƒç”¨ã€‚è¿™æ„味ç€è®¾å¤‡åº”该在 +GPIO å¯ä»¥å·¥ä½œä¹‹åŽæ‰å¯è¢«æ³¨å†Œã€‚解决这类ä¾èµ–çš„çš„ä¸€ç§æ–¹æ³•æ˜¯è®©è¿™ç§ gpio_chip +æŽ§åˆ¶å™¨å‘æ¿çº§ç‰¹å®šä»£ç æä¾› setup()å’Œ teardown()回调函数。一旦所有必须的 +资æºå¯ç”¨ä¹‹åŽï¼Œè¿™äº›æ¿çº§ç‰¹å®šçš„回调函数将会注册设备,并å¯ä»¥åœ¨è¿™äº› GPIO 控制器 +è®¾å¤‡å˜æˆæ— 效时移除它们。 + + +用户空间的 Sysfs 接å£(å¯é€‰) +======================== +使用“gpiolibâ€å®žçŽ°æ¡†æž¶çš„å¹³å°å¯ä»¥é€‰æ‹©é…置一个 GPIO çš„ sysfs 用户接å£ã€‚ +è¿™ä¸åŒäºŽ debugfs 接å£ï¼Œå› 为它æä¾›çš„æ˜¯å¯¹ GPIOæ–¹å‘和值的控制,而ä¸åªæ˜¾ç¤º +一个GPIO çš„çŠ¶æ€æ‘˜è¦ã€‚æ¤å¤–,它å¯ä»¥å‡ºçŽ°åœ¨æ²¡æœ‰è°ƒè¯•æ”¯æŒçš„产å“级系统ä¸ã€‚ + +例如,通过适当的系统硬件文档,用户空间å¯ä»¥çŸ¥é“ GIOP #23 控制 Flash +å˜å‚¨å™¨çš„å†™ä¿æŠ¤(ç”¨äºŽä¿æŠ¤å…¶ä¸ Bootloader 分区)。产å“的系统å‡çº§å¯èƒ½éœ€è¦ +ä¸´æ—¶è§£é™¤è¿™ä¸ªä¿æŠ¤ï¼šé¦–å…ˆå¯¼å…¥ä¸€ä¸ª GPIO,改å˜å…¶è¾“出状æ€ï¼Œç„¶åŽåœ¨é‡æ–°ä½¿èƒ½å†™ä¿æŠ¤ +å‰å‡çº§ä»£ç 。通常情况下,GPIO #23 是ä¸ä¼šè¢«è§¦åŠçš„ï¼Œå¹¶ä¸”å†…æ ¸ä¹Ÿä¸éœ€è¦çŸ¥é“他。 + +æ ¹æ®é€‚当的硬件文档,æŸäº›ç³»ç»Ÿçš„用户空间 GPIO å¯ä»¥ç”¨äºŽç¡®å®šç³»ç»Ÿé…置数æ®ï¼Œ +è¿™äº›æ•°æ®æ˜¯æ ‡å‡†å†…æ ¸ä¸çŸ¥é“的。在æŸäº›ä»»åŠ¡ä¸ï¼Œç®€å•的用户空间 GPIO 驱动å¯èƒ½æ˜¯ +系统真æ£éœ€è¦çš„。 + +注æ„ï¼šæ ‡å‡†å†…æ ¸é©±åŠ¨ä¸å·²ç»å˜åœ¨é€šç”¨çš„“LED 和按键â€GPIO 任务,分别是: +"leds-gpio" å’Œ "gpio_keys"ã€‚è¯·ä½¿ç”¨è¿™äº›æ¥æ›¿ä»£ç›´æŽ¥è®¿é—® GPIOï¼Œå› ä¸ºé›†æˆåœ¨ +å†…æ ¸æ¡†æž¶ä¸çš„è¿™ç±»é©±åŠ¨æ¯”ä½ åœ¨ç”¨æˆ·ç©ºé—´çš„ä»£ç æ›´å¥½ã€‚ + + +Sysfs ä¸çš„路径 +-------------- +在/sys/class/gpio 䏿œ‰ 3 类入å£: + + - 用于在用户空间控制 GPIO 的控制接å£; + + - GPIOs 本身;ä»¥åŠ + + - GPIO 控制器 ("gpio_chip" 实例)。 + +é™¤äº†è¿™äº›æ ‡å‡†çš„æ–‡ä»¶,还包å«â€œdeviceâ€ç¬¦å·é“¾æŽ¥ã€‚ + +æŽ§åˆ¶æŽ¥å£æ˜¯åªå†™çš„: + + /sys/class/gpio/ + + "export" ... 用户空间å¯ä»¥é€šè¿‡å†™å…¶ç¼–å·åˆ°è¿™ä¸ªæ–‡ä»¶ï¼Œè¦æ±‚å†…æ ¸å¯¼å‡º + 一个 GPIO 的控制到用户空间。 + + 例如: å¦‚æžœå†…æ ¸ä»£ç æ²¡æœ‰ç”³è¯· GPIO #19,"echo 19 > export" + 将会为 GPIO #19 创建一个 "gpio19" 节点。 + + "unexport" ... 导出到用户空间的逆æ“作。 + + 例如: "echo 19 > unexport" 将会移除使用"export"文件导出的 + "gpio19" 节点。 + +GPIO ä¿¡å·çš„路径类似 /sys/class/gpio/gpio42/ (对于 GPIO #42 æ¥è¯´), +并有如下的读/写属性: + + /sys/class/gpio/gpioN/ + + "direction" ... 读å–得到 "in" 或 "out"。这个值通常è¿è¡Œå†™å…¥ã€‚ + 写入"out" æ—¶,å…¶å¼•è„šçš„é»˜è®¤è¾“å‡ºä¸ºä½Žç”µå¹³ã€‚ä¸ºäº†ç¡®ä¿æ— æ•…éšœè¿è¡Œï¼Œ + "low" 或 "high" 的电平值应该写入 GPIO çš„é…置,作为åˆå§‹è¾“出值。 + + 注æ„:å¦‚æžœå†…æ ¸ä¸æ”¯æŒæ”¹å˜ GPIO 的方å‘ï¼Œæˆ–è€…åœ¨å¯¼å‡ºæ—¶å†…æ ¸ä»£ç æ²¡æœ‰ + 明确å…许用户空间å¯ä»¥é‡æ–°é…ç½® GPIO æ–¹å‘,那么这个属性将ä¸å˜åœ¨ã€‚ + + "value" ... 读å–得到 0 (低电平) 或 1 (高电平)。如果 GPIO é…置为 + 输出,这个值å…许写æ“作。任何éžé›¶å€¼éƒ½ä»¥é«˜ç”µå¹³çœ‹å¾…。 + + 如果引脚å¯ä»¥é…ç½®ä¸ºä¸æ–ä¿¡å·ï¼Œä¸”如果已ç»é…ç½®äº†äº§ç”Ÿä¸æ–çš„æ¨¡å¼ + (è§"edge"çš„æè¿°ï¼‰ï¼Œä½ å¯ä»¥å¯¹è¿™ä¸ªæ–‡ä»¶ä½¿ç”¨è½®è¯¢æ“作(poll(2)), + 且轮询æ“ä½œä¼šåœ¨ä»»ä½•ä¸æ–è§¦å‘æ—¶è¿”å›žã€‚å¦‚æžœä½ ä½¿ç”¨è½®è¯¢æ“作(poll(2)), + 请在 events ä¸è®¾ç½® POLLPRI å’Œ POLLERRã€‚å¦‚æžœä½ ä½¿ç”¨è½®è¯¢æ“作 + (select(2)),请在 exceptfds è®¾ç½®ä½ æœŸæœ›çš„æ–‡ä»¶æè¿°ç¬¦ã€‚在 + 轮询æ“作(poll(2))返回之åŽï¼Œæ—¢å¯ä»¥é€šè¿‡ lseek(2)æ“ä½œè¯»å– + sysfs 文件的开始部分,也å¯ä»¥å…³é—è¿™ä¸ªæ–‡ä»¶å¹¶é‡æ–°æ‰“开它æ¥è¯»å–æ•°æ®ã€‚ + + "edge" ... 读å–得到“noneâ€ã€â€œrisingâ€ã€â€œfallingâ€æˆ–者“bothâ€ã€‚ + 将这些å—符串写入这个文件å¯ä»¥é€‰æ‹©æ²¿è§¦å‘模å¼ï¼Œä¼šä½¿å¾—轮询æ“作 + (select(2))在"value"文件ä¸è¿”回。 + + 这个文件仅有在这个引脚å¯ä»¥é…置为å¯äº§ç”Ÿä¸æ–输入引脚时,æ‰å˜åœ¨ã€‚ + + "active_low" ... 读å–得到 0 (å‡) 或 1 (真)。写入任何éžé›¶å€¼å¯ä»¥ + 翻转这个属性的(读写)值。已å˜åœ¨æˆ–之åŽé€šè¿‡"edge"属性设置了"rising" + å’Œ "falling" æ²¿è§¦å‘æ¨¡å¼çš„轮询æ“作(poll(2))将会éµå¾ªè¿™ä¸ªè®¾ç½®ã€‚ + +GPIO 控制器的路径类似 /sys/class/gpio/gpiochip42/ (对于从#42 GPIO +开始实现控制的控制器),并有ç€ä»¥ä¸‹åªè¯»å±žæ€§: + + /sys/class/gpio/gpiochipN/ + + "base" ... 与以上的 N 相åŒ,代表æ¤èŠ¯ç‰‡ç®¡ç†çš„第一个 GPIO çš„ç¼–å· + + "label" ... ç”¨äºŽè¯Šæ– (并䏿€»æ˜¯åªæœ‰å”¯ä¸€å€¼) + + "ngpio" ... æ¤æŽ§åˆ¶å™¨æ‰€ç®¡ç†çš„ GPIO æ•°é‡(而 GPIO ç¼–å·ä»Ž N 到 + N + ngpio - 1) + +大多数情况下,电路æ¿çš„æ–‡æ¡£åº”å½“æ ‡æ˜Žæ¯ä¸ª GPIO 的使用目的。但是那些编å·å¹¶ä¸æ€»æ˜¯ +固定的,例如在扩展å¡ä¸Šçš„ GPIOä¼šæ ¹æ®æ‰€ä½¿ç”¨çš„ä¸»æ¿æˆ–æ‰€åœ¨å †å æž¶æž„ä¸å…¶ä»–çš„æ¿å而 +有所ä¸åŒã€‚åœ¨è¿™ç§æƒ…况下,ä½ å¯èƒ½éœ€è¦ä½¿ç”¨ gpiochip 节点(å°½å¯èƒ½åœ°ç»“åˆç”µè·¯å›¾)æ¥ +ç¡®å®šç»™å®šä¿¡å·æ‰€ç”¨çš„ GPIO ç¼–å·ã€‚ + + +ä»Žå†…æ ¸ä»£ç ä¸å¯¼å‡º +------------- +å†…æ ¸ä»£ç å¯ä»¥æ˜Žç¡®åœ°ç®¡ç†é‚£äº›å·²é€šè¿‡ gpio_request()申请的 GPIO 的导出: + + /* 导出 GPIO 到用户空间 */ + int gpio_export(unsigned gpio, bool direction_may_change); + + /* gpio_export()的逆æ“作 */ + void gpio_unexport(); + + /* 创建一个 sysfs 连接到已导出的 GPIO 节点 */ + int gpio_export_link(struct device *dev, const char *name, + unsigned gpio) + + /* æ”¹å˜ sysfs ä¸çš„一个 GPIO èŠ‚ç‚¹çš„æžæ€§ */ + int gpio_sysfs_set_active_low(unsigned gpio, int value); + +åœ¨ä¸€ä¸ªå†…æ ¸é©±åŠ¨ç”³è¯·ä¸€ä¸ª GPIO 之åŽï¼Œå®ƒå¯ä»¥é€šè¿‡ gpio_export()使其在 sysfs +接å£ä¸å¯è§ã€‚该驱动å¯ä»¥æŽ§åˆ¶ä¿¡å·æ–¹å‘是å¦å¯ä¿®æ”¹ã€‚这有助于防æ¢ç”¨æˆ·ç©ºé—´ä»£ç æ— æ„é—´ +ç ´åé‡è¦çš„系统状æ€ã€‚ + +这个明确的导出有助于(通过使æŸäº›å®žéªŒæ›´å®¹æ˜“æ¥)调试,也å¯ä»¥æä¾›ä¸€ä¸ªå§‹ç»ˆå˜åœ¨çš„æŽ¥å£ï¼Œ +与文档é…åˆä½œä¸ºæ¿çº§æ”¯æŒåŒ…的一部分。 + +在 GPIO 被导出之åŽï¼Œgpio_export_link()å…许在 sysfs 文件系统的任何地方 +创建一个到这个 GPIO sysfs 节点的符å·é“¾æŽ¥ã€‚è¿™æ ·é©±åŠ¨å°±å¯ä»¥é€šè¿‡ä¸€ä¸ªæè¿°æ€§çš„ +åå—,在 sysfs ä¸ä»–们所拥有的设备下æä¾›ä¸€ä¸ª(到这个 GPIO sysfs 节点的)接å£ã€‚ + +驱动å¯ä»¥ä½¿ç”¨ gpio_sysfs_set_active_low() æ¥åœ¨ç”¨æˆ·ç©ºé—´éšè—电路æ¿ä¹‹é—´ +GPIO çº¿çš„æžæ€§å·®å¼‚。这个仅对 sysfs 接å£èµ·ä½œç”¨ã€‚æžæ€§çš„æ”¹å˜å¯ä»¥åœ¨ gpio_export() +å‰åŽè¿›è¡Œ,且之å‰ä½¿èƒ½çš„轮询æ“作(poll(2))支æŒ(ä¸Šå‡æˆ–䏋陿²¿)å°†ä¼šè¢«é‡æ–°é…ç½®æ¥éµå¾ª +这个设置。 diff --git a/Documentation/zh_CN/video4linux/omap3isp.txt b/Documentation/zh_CN/video4linux/omap3isp.txt new file mode 100644 index 0000000000000000000000000000000000000000..67ffbf352ae0c252d67d8c262d4a416de97ce6f7 --- /dev/null +++ b/Documentation/zh_CN/video4linux/omap3isp.txt @@ -0,0 +1,277 @@ +Chinese translated version of Documentation/video4linux/omap3isp.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: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + Sakari Ailus <sakari.ailus@iki.fi> + David Cohen <dacohen@gmail.com> +Chinese maintainer: Fu Wei <tekkamanninja@gmail.com> +--------------------------------------------------------------------- +Documentation/video4linux/omap3isp.txt çš„ä¸æ–‡ç¿»è¯‘ + +如果想评论或更新本文的内容,请直接è”ç³»åŽŸæ–‡æ¡£çš„ç»´æŠ¤è€…ã€‚å¦‚æžœä½ ä½¿ç”¨è‹±æ–‡ +äº¤æµæœ‰å›°éš¾çš„è¯ï¼Œä¹Ÿå¯ä»¥å‘䏿–‡ç‰ˆç»´æŠ¤è€…求助。如果本翻译更新ä¸åŠæ—¶æˆ–者翻 +译å˜åœ¨é—®é¢˜ï¼Œè¯·è”ç³»ä¸æ–‡ç‰ˆç»´æŠ¤è€…。 +英文版维护者: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + Sakari Ailus <sakari.ailus@iki.fi> + David Cohen <dacohen@gmail.com> +䏿–‡ç‰ˆç»´æŠ¤è€…: 傅炜 Fu Wei <tekkamanninja@gmail.com> +䏿–‡ç‰ˆç¿»è¯‘者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +䏿–‡ç‰ˆæ ¡è¯‘者: 傅炜 Fu Wei <tekkamanninja@gmail.com> + + +ä»¥ä¸‹ä¸ºæ£æ–‡ +--------------------------------------------------------------------- +OMAP 3 图åƒä¿¡å·å¤„ç†å™¨ (ISP) 驱动 + +Copyright (C) 2010 Nokia Corporation +Copyright (C) 2009 Texas Instruments, Inc. + +è”系人: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + Sakari Ailus <sakari.ailus@iki.fi> + David Cohen <dacohen@gmail.com> + + +ä»‹ç» +=== + +本文档介ç»äº†ç”± drivers/media/video/omap3isp åŠ è½½çš„å¾·å·žä»ªå™¨ +(TI)OMAP 3 图åƒä¿¡å·å¤„ç†å™¨ (ISP) 驱动。原始驱动由德州仪器(TI) +编写,但æ¤åŽç”±è¯ºåŸºäºšé‡å†™äº†ä¸¤æ¬¡ã€‚ + +驱动已在以下 OMAP 3 ç³»åˆ—çš„èŠ¯ç‰‡ä¸æˆåŠŸä½¿ç”¨ï¼š + + 3430 + 3530 + 3630 + +驱动实现了 V4L2ã€åª’体控制器和 v4l2_subdev 接å£ã€‚支æŒå†…æ ¸ä¸ä½¿ç”¨ +v4l2_subdev 接å£çš„ä¼ æ„Ÿå™¨ã€é•œå¤´å’Œé—ªå…‰ç¯é©±åŠ¨ã€‚ + + +拆分为å设备 +========== + +OMAP 3 ISP 被拆分为 V4L2 å设备,ISPä¸çš„æ¯ä¸ªæ¨¡å—都由一个å设备 +æ¥è¡¨ç¤ºã€‚æ¯ä¸ªå设备å‘用户空间æä¾›ä¸€ä¸ª V4L2 å设备接å£ã€‚ + + OMAP3 ISP CCP2 + OMAP3 ISP CSI2a + OMAP3 ISP CCDC + OMAP3 ISP preview + OMAP3 ISP resizer + OMAP3 ISP AEWB + OMAP3 ISP AF + OMAP3 ISP histogram + +ISP 䏿¯ä¸ªå¯èƒ½çš„连接都通过一个链接嵌入到媒体控制器接å£ä¸ã€‚详è§ä¾‹ç¨‹ [2]。 + + +控制 OMAP 3 ISP +============== + +通常,对 OMAP 3 ISP çš„é…ç½®ä¼šåœ¨ä¸‹ä¸€å¸§èµ·å§‹æ—¶ç”Ÿæ•ˆã€‚åœ¨ä¼ æ„Ÿå™¨åž‚ç›´æ¶ˆéšæœŸé—´ï¼Œ +模å—å˜ä¸ºç©ºé—²æ—¶å®Œæˆé…置。在内å˜åˆ°å†…å˜çš„æ“ä½œä¸ï¼Œè§†é¢‘管é“一次处ç†ä¸€å¸§ã€‚ +应用é…置应在帧间完æˆã€‚ + +ISP ä¸çš„æ‰€æœ‰æ¨¡å—,除 CSI-2 å’Œ (å¯èƒ½å˜åœ¨çš„)CCP2 接收器外,都必须 +接收完整的帧数æ®ã€‚å› æ¤ï¼Œä¼ 感器必须ä¿è¯ä»Žä¸å‘é€éƒ¨åˆ†å¸§æ•°æ®ç»™ISP。 + +Autoidle(自动空闲)功能至少在 3430 çš„ ISP 模å—ä¸ç¡®å®žå˜åœ¨ä¸€äº›é—®é¢˜ã€‚ +当 omap3isp 模å—傿•° autoidle éžé›¶æ—¶ï¼Œautoidle(自动空闲)功能 +仅在 3630 ä¸å¯ç”¨äº†ã€‚ + + +事件机制 +====== + +OMAP 3 ISP 驱动在 CCDC 和统计(AEWBã€AF å’Œ 直方图)åè®¾å¤‡ä¸æ”¯æŒ +V4L2 事件机制接å£ã€‚ + +CCDC å设备通过 HS_VS 䏿–ï¼Œå¤„ç† V4L2_EVENT_FRAME_SYNC 类型 +事件,用于告知帧起始。早期版本的驱动则使用 V4L2_EVENT_OMAP3ISP_HS_VS。 +当在 CCDC 模å—ä¸æŽ¥æ”¶åˆ°èµ·å§‹å¸§çš„ç¬¬ä¸€è¡Œæ—¶ï¼Œä¼šå‡†ç¡®åœ°è§¦å‘事件。这个事件 +å¯ä»¥åœ¨ CCDC å设备ä¸â€œè®¢é˜…â€ã€‚ + +ï¼ˆå½“ä½¿ç”¨å¹¶è¡ŒæŽ¥å£æ—¶ï¼Œå¿…é¡»æ³¨æ„æ£ç¡®åœ°é…ç½® VS ä¿¡å·æžæ€§ã€‚而当使用串行接收时 +è¿™ä¸ªä¼šè‡ªåŠ¨æ ¡æ£ã€‚) + +æ¯ä¸ªç»Ÿè®¡å设备都å¯ä»¥äº§ç”Ÿäº‹ä»¶ã€‚æ¯å½“一个统计缓冲区å¯ç”±ç”¨æˆ·ç©ºé—´åº”ç”¨ç¨‹åº +通过 VIDIOC_OMAP3ISP_STAT_REQ IOCTL æ“ä½œèŽ·å–æ—¶ï¼Œå°±ä¼šäº§ç”Ÿä¸€ä¸ª +事件。当å‰å˜åœ¨ä»¥ä¸‹äº‹ä»¶ï¼š + + V4L2_EVENT_OMAP3ISP_AEWB + V4L2_EVENT_OMAP3ISP_AF + V4L2_EVENT_OMAP3ISP_HIST + +这些 ioctl 的事件数æ®ç±»åž‹ä¸º struct omap3isp_stat_event_status +ç»“æž„ä½“ã€‚å¦‚æžœå‡ºçŽ°è®¡ç®—é”™è¯¯çš„ç»Ÿè®¡ï¼Œä¹ŸåŒæ ·ä¼šäº§ç”Ÿä¸€ä¸ªäº‹ä»¶ï¼Œä½†æ²¡æœ‰ç›¸å…³çš„统计 +æ•°æ®ç¼“å†²åŒºã€‚è¿™ç§æƒ…况下 omap3isp_stat_event_status.buf_err 会被 +设置为éžé›¶å€¼ã€‚ + + +ç§æœ‰ IOCTL +========== + +OMAP 3 ISP é©±åŠ¨æ”¯æŒæ ‡å‡†çš„ V4L2 IOCTL 以åŠå¯èƒ½å˜åœ¨ä¸”实用的控制。但 +ISP æä¾›çš„许多功能都ä¸åœ¨æ ‡å‡† IOCTL 之列,例如 gamma(伽马)表和统计 +æ•°æ®é‡‡é›†é…ç½®ç‰ã€‚ + +é€šå¸¸ï¼Œä¼šæœ‰ä¸€ä¸ªç§æœ‰ ioctl 用于é…ç½®æ¯ä¸ªåŒ…å«ç¡¬ä»¶ä¾èµ–功能的模å—。 + +支æŒä»¥ä¸‹ç§æœ‰ IOCTL: + + VIDIOC_OMAP3ISP_CCDC_CFG + VIDIOC_OMAP3ISP_PRV_CFG + VIDIOC_OMAP3ISP_AEWB_CFG + VIDIOC_OMAP3ISP_HIST_CFG + VIDIOC_OMAP3ISP_AF_CFG + VIDIOC_OMAP3ISP_STAT_REQ + VIDIOC_OMAP3ISP_STAT_EN + +在 include/linux/omap3isp.h ä¸æè¿°äº†è¿™äº› ioctl ä½¿ç”¨çš„å‚æ•°ç»“构体。 +与特定 ISP 模å—相关的 ISP 自身的详细功能在技术å‚考手册 (TRMs)䏿œ‰ +æè¿°ï¼Œè¯¦è§æ–‡æ¡£ç»“尾。 + +虽然在ä¸ä½¿ç”¨ä»»ä½•ç§æœ‰ IOCTL 的情况下使用 ISP 驱动是å¯èƒ½çš„ï¼Œä½†è¿™æ ·æ— æ³• +获得最佳的图åƒè´¨é‡ã€‚AEWBã€AF å’Œ 直方图(译者注:一般用于自动æ›å…‰å’Œå¢žç›Š +控制,以åŠå›¾åƒå‡è¡¡ç‰ï¼‰æ¨¡å—æ— æ³•åœ¨æœªä½¿ç”¨é€‚å½“çš„ç§æœ‰ IOCTL é…置的情况下使用。 + + +CCDC å’Œ previewï¼ˆé¢„è§ˆï¼‰æ¨¡å— IOCTL +=============================== + +VIDIOC_OMAP3ISP_CCDC_CFG å’Œ VIDIOC_OMAP3ISP_PRV_CFG IOCTL +被分别用于é…ç½®ã€å¯ç”¨å’Œç¦ç”¨ CCDC å’Œ preview(预览)模å—的功能。在它们 +所控制的模å—ä¸ï¼Œä¸¤ä¸ª IOCTL 控制多ç§åŠŸèƒ½ã€‚VIDIOC_OMAP3ISP_CCDC_CFG IOCTL +接å—ä¸€ä¸ªæŒ‡å‘ omap3isp_ccdc_update_config ç»“æž„ä½“çš„æŒ‡é’ˆä½œä¸ºå®ƒçš„å‚æ•°ã€‚ +åŒæ ·çš„,VIDIOC_OMAP3ISP_PRV_CFG 接å—ä¸€ä¸ªæŒ‡å‘ omap3isp_prev_update_config +结构体的指针。以上两个结构体定义ä½äºŽ [1]。 + +这些结构体ä¸çš„ update åŸŸæ ‡è¯†æ˜¯å¦é’ˆå¯¹æŒ‡å®šçš„功能更新é…置,而 flag 域 +åˆ™æ ‡è¯†æ˜¯å¯ç”¨è¿˜æ˜¯ç¦ç”¨æ¤åŠŸèƒ½ã€‚ + +update å’Œ flag 使ޥå—以下掩ç 值。CCDC å’Œ preview(预览)模å—çš„ +æ¯ä¸ªå•独功能都与一个 flag å…³è”(ç¦ç”¨æˆ–å¯ç”¨ï¼›åœ¨ç»“æž„ä½“ä¸ flag 域的 +一部分)和一个指å‘功能é…置数æ®çš„æŒ‡é’ˆã€‚ + +对于 VIDIOC_OMAP3ISP_CCDC_CFG,下é¢åˆ—出了 update å’Œ flag 域 +ä¸çš„æœ‰æ•ˆå€¼ã€‚ 这些值å¯èƒ½ä¼šåœ¨åŒä¸€ä¸ª IOCTL 调用ä¸é…置多个功能。 + + OMAP3ISP_CCDC_ALAW + OMAP3ISP_CCDC_LPF + OMAP3ISP_CCDC_BLCLAMP + OMAP3ISP_CCDC_BCOMP + OMAP3ISP_CCDC_FPC + OMAP3ISP_CCDC_CULL + OMAP3ISP_CCDC_CONFIG_LSC + OMAP3ISP_CCDC_TBL_LSC + +针对 VIDIOC_OMAP3ISP_PRV_CFG 的相应值如下: + + OMAP3ISP_PREV_LUMAENH + OMAP3ISP_PREV_INVALAW + OMAP3ISP_PREV_HRZ_MED + OMAP3ISP_PREV_CFA + OMAP3ISP_PREV_CHROMA_SUPP + OMAP3ISP_PREV_WB + OMAP3ISP_PREV_BLKADJ + OMAP3ISP_PREV_RGB2RGB + OMAP3ISP_PREV_COLOR_CONV + OMAP3ISP_PREV_YC_LIMIT + OMAP3ISP_PREV_DEFECT_COR + OMAP3ISP_PREV_GAMMABYPASS + OMAP3ISP_PREV_DRK_FRM_CAPTURE + OMAP3ISP_PREV_DRK_FRM_SUBTRACT + OMAP3ISP_PREV_LENS_SHADING + OMAP3ISP_PREV_NF + OMAP3ISP_PREV_GAMMA + +在å¯ç”¨æŸä¸ªåŠŸèƒ½çš„æ—¶å€™ï¼Œç›¸å…³çš„é…ç½®æ•°æ®æŒ‡é’ˆä¸å¯ä¸º NULL。在ç¦ç”¨æŸä¸ªåŠŸèƒ½æ—¶ï¼Œ +é…ç½®æ•°æ®æŒ‡é’ˆä¼šè¢«å¿½ç•¥ã€‚ + + +ç»Ÿè®¡æ¨¡å— IOCTL +============= + +统计å设备相较于其他å设备æä¾›äº†æ›´å¤šåЍæ€é…置选项。在图åƒå¤„ç†æµæ°´çº¿å¤„于 +å·¥ä½œçŠ¶æ€æ—¶ï¼Œå®ƒä»¬å¯ä»¥è¢«å¯ç”¨ã€ç¦ç”¨å’Œé‡é…。 + +ç»Ÿè®¡æ¨¡å—æ€»æ˜¯ä»Ž CCDC ä¸èŽ·å–è¾“å…¥çš„å›¾åƒæ•°æ®ï¼ˆç”±äºŽç›´æ–¹å›¾å†…å˜è¯»å–未实现)。 +统计数æ®å¯ç”±ç”¨æˆ·é€šè¿‡ç»Ÿè®¡åè®¾å¤‡èŠ‚ç‚¹ä½¿ç”¨ç§æœ‰ IOCTL 获å–。 + +AEWBã€AF å’Œ 直方图å设备æä¾›çš„ç§æœ‰ IOCTL æžå¤§ç¨‹åº¦ä¸Šå应了 ISP 硬件 +æä¾›çš„寄å˜å™¨çº§æŽ¥å£ã€‚有些方é¢çº¯ç²¹å’Œé©±åŠ¨ç¨‹åºçš„实现相关,这些将在下é¢è®¨è®ºã€‚ + +VIDIOC_OMAP3ISP_STAT_EN +----------------------- + +è¿™ä¸ªç§æœ‰ IOCTL å¯ç”¨/ç¦ç”¨ 一个统计模å—。如果这个申请在视频æµå¯åЍå‰å®Œæˆï¼Œ +å®ƒå°†åœ¨è§†é¢‘æµæ°´çº¿å¼€å§‹å·¥ä½œæ—¶ç”Ÿæ•ˆã€‚å¦‚æžœè§†é¢‘æµæ°´çº¿å·²ç»å¤„于工作状æ€äº†ï¼Œå®ƒå°†åœ¨ +CCDC å˜ä¸ºç©ºé—²æ—¶ç”Ÿæ•ˆã€‚ + +VIDIOC_OMAP3ISP_AEWB_CFG, VIDIOC_OMAP3ISP_HIST_CFG and VIDIOC_OMAP3ISP_AF_CFG +----------------------------------------------------------------------------- + +这些 IOCTL 用于é…置模å—ã€‚å®ƒä»¬è¦æ±‚用户应用程åºå¯¹ç¡¬ä»¶æœ‰æ·±å…¥çš„认识。对 +大多数域的解释å¯ä»¥åœ¨ OMAP çš„ TRM 䏿‰¾åˆ°ã€‚以下两个域对于以上所有的 +ç§æœ‰ IOCTL é…置都很常è§ï¼Œç”±äºŽä»–们没有在 TRM 䏿åŠï¼Œæ•…需è¦å¯¹å…¶æœ‰ +更好的认识。 + +omap3isp_[h3a_af/h3a_aewb/hist]_config.buf_size: + +模å—在内部处ç†è‡ªèº«ç¼“å†²ã€‚å¯¹æ¨¡å—æ•°æ®è¾“出所必需的缓å˜å¤§å°ä¾èµ–于已申请的é…置。 +虽然驱动支æŒåœ¨è§†é¢‘æµå·¥ä½œæ—¶é‡æ–°é…置,但对于所需缓å˜é‡å¤§äºŽæ¨¡å—å¯ç”¨æ—¶å†…部 +æ‰€åˆ†é…æ•°é‡çš„æƒ…å†µï¼Œåˆ™ä¸æ”¯æŒé‡æ–°é…ç½®ã€‚åœ¨è¿™ç§æƒ…况下将返回 -EBUSY。为了é¿å… +æ¤ç±»çŠ¶å†µï¼Œæ— è®ºæ˜¯ç¦ç”¨/é‡é…/å¯ç”¨æ¨¡å—,还是第一次é…置时申请必须的缓å˜å¤§å°ï¼Œ +都应在模å—ç¦ç”¨çš„æƒ…况下进行。 + +内部缓冲分é…的大å°éœ€ç»¼åˆè€ƒè™‘所申请é…置的最å°ç¼“å˜é‡ä»¥åŠ buf_size åŸŸä¸ +所设的值。如果 buf_size 域在[minimum(最å°å€¼ï¼‰, maximum(最大值)] +缓冲大å°èŒƒå›´ä¹‹å¤–,则应该将其调整到其范围ä¸ã€‚驱动则会选择最大值。æ£ç¡®çš„ +buf_size 值将回写到用户应用程åºä¸ã€‚ + +omap3isp_[h3a_af/h3a_aewb/hist]_config.config_counter: + +由于é…置并未在申请之åŽåŒæ¥ç”Ÿæ•ˆï¼Œé©±åŠ¨å¿…é¡»æä¾›ä¸€ä¸ªè·Ÿè¸ªè¿™ç±»ä¿¡æ¯çš„æ–¹æ³•, +以æä¾›æ›´å‡†ç¡®çš„æ•°æ®ã€‚在一个é…置被申请之åŽï¼Œè¿”回到用户空间应用程åºçš„ +config_counter 是一个与其é…ç½®ç›¸å…³çš„å”¯ä¸€å€¼ã€‚å½“ç”¨æˆ·åº”ç”¨ç¨‹åºæŽ¥æ”¶åˆ° +一个缓冲å¯ç”¨æˆ–一个新的缓冲申请事件时,这个 config_counter 用于 +一个缓冲数æ®å’Œä¸€ä¸ªé…置的匹é…。 + +VIDIOC_OMAP3ISP_STAT_REQ +------------------------ + +å°†å†…éƒ¨ç¼“å†²é˜Ÿåˆ—ä¸æœ€æ—©çš„æ•°æ®å‘é€åˆ°ç”¨æˆ·ç©ºé—´ï¼Œç„¶åŽä¸¢å¼ƒæ¤ç¼“冲区。 +omap3isp_stat_data.frame_number 域与视频缓冲的 field_count +域相匹é…。 + + +技术å‚考手册 (TRMs) 和其他文档 +========================== + +OMAP 3430 TRM: +<URL:http://focus.ti.com/pdfs/wtbu/OMAP34xx_ES3.1.x_PUBLIC_TRM_vZM.zip> +å‚考于 2011-03-05. + +OMAP 35xx TRM: +<URL:http://www.ti.com/litv/pdf/spruf98o> å‚考于 2011-03-05. + +OMAP 3630 TRM: +<URL:http://focus.ti.com/pdfs/wtbu/OMAP36xx_ES1.x_PUBLIC_TRM_vQ.zip> +å‚考于 2011-03-05. + +DM 3730 TRM: +<URL:http://www.ti.com/litv/pdf/sprugn4h> å‚考于 2011-03-06. + + +å‚考资料 +======= + +[1] include/linux/omap3isp.h + +[2] http://git.ideasonboard.org/?p=media-ctl.git;a=summary diff --git a/Documentation/zh_CN/video4linux/v4l2-framework.txt b/Documentation/zh_CN/video4linux/v4l2-framework.txt new file mode 100644 index 0000000000000000000000000000000000000000..3e74f13af4266d8d7aab3b3910b93a73bc1b5b71 --- /dev/null +++ b/Documentation/zh_CN/video4linux/v4l2-framework.txt @@ -0,0 +1,983 @@ +Chinese translated version of Documentation/video4linux/v4l2-framework.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: Mauro Carvalho Chehab <mchehab@infradead.org> +Chinese maintainer: Fu Wei <tekkamanninja@gmail.com> +--------------------------------------------------------------------- +Documentation/video4linux/v4l2-framework.txt çš„ä¸æ–‡ç¿»è¯‘ + +如果想评论或更新本文的内容,请直接è”ç³»åŽŸæ–‡æ¡£çš„ç»´æŠ¤è€…ã€‚å¦‚æžœä½ ä½¿ç”¨è‹±æ–‡ +äº¤æµæœ‰å›°éš¾çš„è¯ï¼Œä¹Ÿå¯ä»¥å‘䏿–‡ç‰ˆç»´æŠ¤è€…求助。如果本翻译更新ä¸åŠæ—¶æˆ–者翻 +译å˜åœ¨é—®é¢˜ï¼Œè¯·è”ç³»ä¸æ–‡ç‰ˆç»´æŠ¤è€…。 +英文版维护者: Mauro Carvalho Chehab <mchehab@infradead.org> +䏿–‡ç‰ˆç»´æŠ¤è€…: 傅炜 Fu Wei <tekkamanninja@gmail.com> +䏿–‡ç‰ˆç¿»è¯‘者: 傅炜 Fu Wei <tekkamanninja@gmail.com> +䏿–‡ç‰ˆæ ¡è¯‘者: 傅炜 Fu Wei <tekkamanninja@gmail.com> + + +ä»¥ä¸‹ä¸ºæ£æ–‡ +--------------------------------------------------------------------- +V4L2 驱动框架概览 +============== + +本文档æè¿° V4L2 框架所æä¾›çš„å„ç§ç»“构和它们之间的关系。 + + +ä»‹ç» +---- + +大部分现代 V4L2 设备由多个 IC 组æˆï¼Œåœ¨ /dev 下导出多个设备节点, +å¹¶åŒæ—¶åˆ›å»ºéž V4L2 设备(如 DVBã€ALSAã€FBã€I2C 和红外输入设备)。 +由于这ç§ç¡¬ä»¶çš„夿‚性,V4L2 驱动也å˜å¾—éžå¸¸å¤æ‚。 + +尤其是 V4L2 å¿…é¡»æ”¯æŒ IC 实现音视频的多路å¤ç”¨å’Œç¼–è§£ç ï¼Œè¿™å°±æ›´å¢žåŠ äº†å…¶ +夿‚性。通常这些 IC 通过一个或多个 I2C æ€»çº¿è¿žæŽ¥åˆ°ä¸»æ¡¥é©±åŠ¨å™¨ï¼Œä½†ä¹Ÿå¯ +使用其他总线。这些设备称为“å设备â€ã€‚ + +长期以æ¥ï¼Œè¿™ä¸ªæ¡†æž¶ä»…é™äºŽé€šè¿‡ video_device 结构体创建 V4L 设备节点, +并使用 video_buf 处ç†è§†é¢‘缓冲(注:本文ä¸è®¨è®º video_buf 框架)。 + +è¿™æ„å‘³ç€æ‰€æœ‰é©±åŠ¨å¿…é¡»è‡ªå·±è®¾ç½®è®¾å¤‡å®žä¾‹å¹¶è¿žæŽ¥åˆ°å设备。其ä¸ä¸€éƒ¨åˆ†è¦æ£ç¡®åœ° +å®Œæˆæ˜¯æ¯”è¾ƒå¤æ‚的,使得许多驱动都没有æ£ç¡®åœ°å®žçŽ°ã€‚ + +由于框架的缺失,有很多通用代ç 都ä¸å¯é‡å¤åˆ©ç”¨ã€‚ + +å› æ¤ï¼Œè¿™ä¸ªæ¡†æž¶æž„建所有驱动都需è¦çš„基本结构å—,而统一的框架将使通用代ç +创建æˆå®žç”¨å‡½æ•°å¹¶åœ¨æ‰€æœ‰é©±åЍä¸å…±äº«å˜å¾—æ›´åŠ å®¹æ˜“ã€‚ + + +驱动结构 +------- + +所有 V4L2 驱动都有如下结构: + +1) æ¯ä¸ªè®¾å¤‡å®žä¾‹çš„结构体--包å«å…¶è®¾å¤‡çжæ€ã€‚ + +2) åˆå§‹åŒ–和控制å设备的方法(如果有)。 + +3) 创建 V4L2 设备节点 (/dev/videoXã€/dev/vbiX å’Œ /dev/radioX) + 并跟踪设备节点的特定数æ®ã€‚ + +4) ç‰¹å®šæ–‡ä»¶å¥æŸ„结构体--åŒ…å«æ¯ä¸ªæ–‡ä»¶å¥æŸ„的数æ®ã€‚ + +5) 视频缓冲处ç†ã€‚ + +以下是它们的åˆç•¥å…³ç³»å›¾ï¼š + + device instances(设备实例) + | + +-sub-device instances(å设备实例) + | + \-V4L2 device nodes(V4L2 设备节点) + | + \-filehandle instancesï¼ˆæ–‡ä»¶å¥æŸ„实例) + + +框架结构 +------- + +该框架éžå¸¸ç±»ä¼¼é©±åŠ¨ç»“æž„ï¼šå®ƒæœ‰ä¸€ä¸ª v4l2_device 结构用于ä¿å˜è®¾å¤‡ +实例的数æ®ï¼›ä¸€ä¸ª v4l2_subdev 结构体代表å设备实例;video_device +结构体ä¿å˜ V4L2 设备节点的数æ®ï¼›å°†æ¥ v4l2_fh ç»“æž„ä½“å°†è·Ÿè¸ªæ–‡ä»¶å¥æŸ„ +实例(暂未尚未实现)。 + +V4L2 框架也å¯ä¸Žåª’体框架整åˆï¼ˆå¯é€‰çš„)。如果驱动设置了 v4l2_device +结构体的 mdev 域,å设备和视频节点的入å£å°†è‡ªåŠ¨å‡ºçŽ°åœ¨åª’ä½“æ¡†æž¶ä¸ã€‚ + + +v4l2_device 结构体 +---------------- + +æ¯ä¸ªè®¾å¤‡å®žä¾‹éƒ½é€šè¿‡ v4l2_device (v4l2-device.h)结构体æ¥è¡¨ç¤ºã€‚ +简å•设备å¯ä»¥ä»…分é…这个结构体,但在大多数情况下,都会将这个结构体 +嵌入到一个更大的结构体ä¸ã€‚ + +ä½ å¿…é¡»æ³¨å†Œè¿™ä¸ªè®¾å¤‡å®žä¾‹ï¼š + + v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev); + +注册æ“作将会åˆå§‹åŒ– v4l2_device 结构体。如果 dev->driver_data 域 +为 NULLï¼Œå°±å°†å…¶æŒ‡å‘ v4l2_dev。 + +需è¦ä¸Žåª’体框架整åˆçš„驱动必须手动设置 dev->driver_data,指å‘åŒ…å« +v4l2_device 结构体实例的驱动特定设备结构体。这å¯ä»¥åœ¨æ³¨å†Œ V4L2 设备 +实例å‰é€šè¿‡ dev_set_drvdata() 函数完æˆã€‚åŒæ—¶å¿…须设置 v4l2_device +结构体的 mdev 域,指å‘适当的åˆå§‹åŒ–并注册过的 media_device 实例。 + +如果 v4l2_dev->name 为空,则它将被设置为从 dev ä¸è¡ç”Ÿå‡ºçš„值(为了 +æ›´åŠ ç²¾ç¡®ï¼Œå½¢å¼ä¸ºé©±åЍååŽè·Ÿ bus_idï¼‰ã€‚å¦‚æžœä½ åœ¨è°ƒç”¨ v4l2_device_register +å‰å·²ç»è®¾ç½®å¥½äº†ï¼Œåˆ™ä¸ä¼šè¢«ä¿®æ”¹ã€‚如果 dev 为 NULLï¼Œåˆ™ä½ *å¿…é¡»*在调用 +v4l2_device_register å‰è®¾ç½® v4l2_dev->name。 + +ä½ å¯ä»¥åŸºäºŽé©±åЍå和驱动的全局 atomic_t 类型的实例编å·ï¼Œé€šè¿‡ +v4l2_device_set_name() 设置 nameã€‚è¿™æ ·ä¼šç”Ÿæˆç±»ä¼¼ ivtv0ã€ivtv1 ç‰ +åå—。若驱动å以数å—结尾,则会在编å·å’Œé©±åЍåé—´æ’å…¥ä¸€ä¸ªç ´æŠ˜å·ï¼Œå¦‚: +cx18-0ã€cx18-1 ç‰ã€‚æ¤å‡½æ•°è¿”回实例编å·ã€‚ + +第一个 “devâ€ å‚æ•°é€šå¸¸æ˜¯ä¸€ä¸ªæŒ‡å‘ pci_devã€usb_interface 或 +platform_device 的指针。很少使其为 NULLï¼Œé™¤éžæ˜¯ä¸€ä¸ªISA设备或者 +当一个设备创建了多个 PCI 设备,使得 v4l2_dev æ— æ³•ä¸Žä¸€ä¸ªç‰¹å®šçš„çˆ¶è®¾å¤‡ +å…³è”。 + +ä½ ä¹Ÿå¯ä»¥æä¾›ä¸€ä¸ª notify() 回调,使å设备å¯ä»¥è°ƒç”¨å®ƒå®žçŽ°äº‹ä»¶é€šçŸ¥ã€‚ +但这个设置与å设备相关。å设备支æŒçš„任何通知必须在 +include/media/<subdevice>.h ä¸å®šä¹‰ä¸€ä¸ªæ¶ˆæ¯å¤´ã€‚ + +注销 v4l2_device 使用如下函数: + + v4l2_device_unregister(struct v4l2_device *v4l2_dev); + +如果 dev->driver_data åŸŸæŒ‡å‘ v4l2_dev,将会被é‡ç½®ä¸º NULLã€‚æ³¨é”€åŒæ—¶ +ä¼šè‡ªåŠ¨ä»Žè®¾å¤‡ä¸æ³¨é”€æ‰€æœ‰å设备。 + +å¦‚æžœä½ æœ‰ä¸€ä¸ªçƒæ’拔设备(如USB设备),则当æ–å¼€å‘ç”Ÿæ—¶ï¼Œçˆ¶è®¾å¤‡å°†æ— æ•ˆã€‚ +由于 v4l2_device 有一个指å‘çˆ¶è®¾å¤‡çš„æŒ‡é’ˆå¿…é¡»è¢«æ¸…é™¤ï¼ŒåŒæ—¶æ ‡å¿—父设备 +已消失,所以必须调用以下函数: + + v4l2_device_disconnect(struct v4l2_device *v4l2_dev); + +这个函数并*ä¸*注销åè®¾å¤‡ï¼Œå› æ¤ä½ ä¾ç„¶è¦è°ƒç”¨ v4l2_device_unregister() +å‡½æ•°ã€‚å¦‚æžœä½ çš„é©±åŠ¨å™¨å¹¶éžçƒæ’拔的,就没有必è¦è°ƒç”¨ v4l2_device_disconnect()。 + +æœ‰æ—¶ä½ éœ€è¦é历所有被特定驱动注册的设备。这通常å‘生在多个设备驱动使用 +åŒä¸€ä¸ªç¡¬ä»¶çš„æƒ…况下。如:ivtvfb 驱动是一个使用 ivtv 硬件的帧缓冲驱动, +åŒæ—¶ alsa 驱动也使用æ¤ç¡¬ä»¶ã€‚ + +ä½ å¯ä»¥ä½¿ç”¨å¦‚下例程é历所有注册的设备: + +static int callback(struct device *dev, void *p) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + + /* 测试这个设备是å¦å·²ç»åˆå§‹åŒ– */ + if (v4l2_dev == NULL) + return 0; + ... + return 0; +} + +int iterate(void *p) +{ + struct device_driver *drv; + int err; + + /* 在PCI 总线上查找ivtv驱动。 + pci_bus_type是全局的. 对于USB总线使用usb_bus_type。 */ + drv = driver_find("ivtv", &pci_bus_type); + /* é历所有的ivtv设备实例 */ + err = driver_for_each_device(drv, NULL, p, callback); + put_driver(drv); + return err; +} + +æœ‰æ—¶ä½ éœ€è¦ä¸€ä¸ªè®¾å¤‡å®žä¾‹çš„è¿è¡Œè®¡æ•°ã€‚è¿™ä¸ªé€šå¸¸ç”¨äºŽæ˜ å°„ä¸€ä¸ªè®¾å¤‡å®žä¾‹åˆ°ä¸€ä¸ª +模å—选择数组的索引。 + +æŽ¨èæ–¹æ³•如下: + +static atomic_t drv_instance = ATOMIC_INIT(0); + +static int __devinit drv_probe(struct pci_dev *pdev, + const struct pci_device_id *pci_id) +{ + ... + state->instance = atomic_inc_return(&drv_instance) - 1; +} + +å¦‚æžœä½ æœ‰å¤šä¸ªè®¾å¤‡èŠ‚ç‚¹ï¼Œå¯¹äºŽçƒæ’拔设备,知é“何时注销 v4l2_device 结构体 +å°±æ¯”è¾ƒå›°éš¾ã€‚ä¸ºæ¤ v4l2_device 有引用计数支æŒã€‚当调用 video_register_device +æ—¶å¢žåŠ å¼•ç”¨è®¡æ•°ï¼Œè€Œè®¾å¤‡èŠ‚ç‚¹é‡Šæ”¾æ—¶å‡å°å¼•用计数。当引用计数为零,则 +v4l2_device çš„release() å›žè°ƒå°†è¢«æ‰§è¡Œã€‚ä½ å°±å¯ä»¥åœ¨æ¤æ—¶åšæœ€åŽçš„æ¸…ç†å·¥ä½œã€‚ + +如果创建了其他设备节点(比如 ALSAï¼‰ï¼Œåˆ™ä½ å¯ä»¥é€šè¿‡ä»¥ä¸‹å‡½æ•°æ‰‹åŠ¨å¢žå‡ +引用计数: + +void v4l2_device_get(struct v4l2_device *v4l2_dev); + +或: + +int v4l2_device_put(struct v4l2_device *v4l2_dev); + +由于引用技术åˆå§‹åŒ–为 1 ï¼Œä½ ä¹Ÿéœ€è¦åœ¨ disconnect() 回调(对于 USB è®¾å¤‡ï¼‰ä¸ +调用 v4l2_device_put,或者 remove() 回调(例如对于 PCI 设备),å¦åˆ™ +引用计数将永远ä¸ä¼šä¸º 0 。 + +v4l2_subdev结构体 +------------------ + +许多驱动需è¦ä¸Žå设备通信。这些设备å¯ä»¥å®Œæˆå„ç§ä»»åŠ¡ï¼Œä½†é€šå¸¸ä»–ä»¬è´Ÿè´£ +音视频å¤ç”¨å’Œç¼–è§£ç 。如网络摄åƒå¤´çš„åè®¾å¤‡é€šå¸¸æ˜¯ä¼ æ„Ÿå™¨å’Œæ‘„åƒå¤´æŽ§åˆ¶å™¨ã€‚ + +这些一般为 I2C 接å£è®¾å¤‡ï¼Œä½†å¹¶ä¸ä¸€å®šéƒ½æ˜¯ã€‚为了给驱动æä¾›è°ƒç”¨å设备的 +统一接å£ï¼Œv4l2_subdev 结构体(v4l2-subdev.h)产生了。 + +æ¯ä¸ªå设备驱动都必须有一个 v4l2_subdev 结构体。这个结构体å¯ä»¥å•独 +代表一个简å•çš„å设备,也å¯ä»¥åµŒå…¥åˆ°ä¸€ä¸ªæ›´å¤§çš„结构体ä¸ï¼Œä¸Žæ›´å¤šè®¾å¤‡çŠ¶æ€ +ä¿¡æ¯ä¿å˜åœ¨ä¸€èµ·ã€‚通常有一个下级设备结构体(比如:i2c_client)包å«äº† +å†…æ ¸åˆ›å»ºçš„è®¾å¤‡æ•°æ®ã€‚建议使用 v4l2_set_subdevdata() 将这个结构体的 +指针ä¿å˜åœ¨ v4l2_subdev çš„ç§æœ‰æ•°æ®åŸŸ(dev_priv)ä¸ã€‚这使得通过 v4l2_subdev +找到实际的低层总线特定设备数æ®å˜å¾—容易。 + +ä½ åŒæ—¶éœ€è¦ä¸€ä¸ªä»Žä½Žå±‚ç»“æž„ä½“èŽ·å– v4l2_subdev 指针的方法。对于常用的 +i2c_client 结构体,i2c_set_clientdata() 函数å¯ç”¨äºŽä¿å˜ä¸€ä¸ª v4l2_subdev +æŒ‡é’ˆï¼›å¯¹äºŽå…¶ä»–æ€»çº¿ä½ å¯èƒ½éœ€è¦ä½¿ç”¨å…¶ä»–相关函数。 + +桥驱动ä¸ä¹Ÿåº”ä¿å˜æ¯ä¸ªåè®¾å¤‡çš„ç§æœ‰æ•°æ®ï¼Œæ¯”如一个指å‘特定桥的å„è®¾å¤‡ç§æœ‰ +æ•°æ®çš„æŒ‡é’ˆã€‚ä¸ºæ¤ v4l2_subdev 结构体æä¾›ä¸»æœºç§æœ‰æ•°æ®åŸŸ(host_priv), +å¹¶å¯é€šè¿‡ v4l2_get_subdev_hostdata() å’Œ v4l2_set_subdev_hostdata() +访问。 + +ä»Žæ€»çº¿æ¡¥é©±åŠ¨çš„è§†è§’ï¼Œé©±åŠ¨åŠ è½½å设备模å—并以æŸç§æ–¹å¼èŽ·å¾— v4l2_subdev +结构体指针。对于 i2c 总线设备相对简å•:调用 i2c_get_clientdata()。 +对于其他总线也需è¦åšç±»ä¼¼çš„æ“ä½œã€‚é’ˆå¯¹ I2C 总线上的åè®¾å¤‡è¾…åŠ©å‡½æ•°å¸®ä½ +完æˆäº†å¤§éƒ¨åˆ†å¤æ‚的工作。 + +æ¯ä¸ª v4l2_subdev 都包å«å设备驱动需è¦å®žçŽ°çš„å‡½æ•°æŒ‡é’ˆï¼ˆå¦‚æžœå¯¹æ¤è®¾å¤‡ +ä¸é€‚用,å¯ä¸ºNULL)。由于å设备å¯å®Œæˆè®¸å¤šä¸åŒçš„工作,而在一个庞大的 +函数指针结构体ä¸é€šå¸¸ä»…有少数有用的函数实现其功能肯定ä¸åˆé€‚。所以, +å‡½æ•°æŒ‡é’ˆæ ¹æ®å…¶å®žçŽ°çš„åŠŸèƒ½è¢«åˆ†ç±»ï¼Œæ¯ä¸€ç±»éƒ½æœ‰è‡ªå·±çš„函数指针结构体。 + +顶层函数指针结构体包å«äº†æŒ‡å‘å„类函数指针结构体的指针,如果å设备驱动 +䏿”¯æŒè¯¥ç±»å‡½æ•°ä¸çš„任何一个功能,则指å‘该类结构体的指针为NULL。 + +这些结构体定义如下: + +struct v4l2_subdev_core_ops { + int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip); + int (*log_status)(struct v4l2_subdev *sd); + int (*init)(struct v4l2_subdev *sd, u32 val); + ... +}; + +struct v4l2_subdev_tuner_ops { + ... +}; + +struct v4l2_subdev_audio_ops { + ... +}; + +struct v4l2_subdev_video_ops { + ... +}; + +struct v4l2_subdev_pad_ops { + ... +}; + +struct v4l2_subdev_ops { + const struct v4l2_subdev_core_ops *core; + const struct v4l2_subdev_tuner_ops *tuner; + const struct v4l2_subdev_audio_ops *audio; + const struct v4l2_subdev_video_ops *video; + const struct v4l2_subdev_pad_ops *video; +}; + +å…¶ä¸ coreï¼ˆæ ¸å¿ƒï¼‰å‡½æ•°é›†é€šå¸¸å¯ç”¨äºŽæ‰€æœ‰å设备,其他类别的实现ä¾èµ–于 +å设备。如视频设备å¯èƒ½ä¸æ”¯æŒéŸ³é¢‘æ“作函数,å之亦然。 + +è¿™æ ·çš„è®¾ç½®åœ¨é™åˆ¶äº†å‡½æ•°æŒ‡é’ˆæ•°é‡çš„åŒæ—¶ï¼Œè¿˜ä½¿å¢žåŠ æ–°çš„æ“作函数和分类 +å˜å¾—较为容易。 + +å设备驱动å¯ä½¿ç”¨å¦‚下函数åˆå§‹åŒ– v4l2_subdev 结构体: + + v4l2_subdev_init(sd, &ops); + +ç„¶åŽï¼Œä½ 必须用一个唯一的åå—åˆå§‹åŒ– subdev->name,并åˆå§‹åŒ–模å—çš„ +owner 域。若使用 i2c è¾…åŠ©å‡½æ•°ï¼Œè¿™äº›éƒ½ä¼šå¸®ä½ å¤„ç†å¥½ã€‚ + +若需åŒåª’体框架整åˆï¼Œä½ 必须调用 media_entity_init() åˆå§‹åŒ– v4l2_subdev +结构体ä¸çš„ media_entity 结构体(entity 域): + + struct media_pad *pads = &my_sd->pads; + int err; + + err = media_entity_init(&sd->entity, npads, pads, 0); + +pads 数组必须预先åˆå§‹åŒ–ã€‚æ— é¡»æ‰‹åŠ¨è®¾ç½® media_entity çš„ type å’Œ +name 域,但如有必è¦ï¼Œrevision 域必须åˆå§‹åŒ–。 + +当(任何)å设备节点被打开/å…³é—,对 entity 的引用将被自动获å–/释放。 + +在å设备被注销之åŽï¼Œä¸è¦å¿˜è®°æ¸…ç† media_entity 结构体: + + media_entity_cleanup(&sd->entity); + +如果å设备驱动趋å‘于处ç†è§†é¢‘å¹¶æ•´åˆè¿›äº†åª’体框架,必须使用 v4l2_subdev_pad_ops +替代 v4l2_subdev_video_ops å®žçŽ°æ ¼å¼ç›¸å…³çš„功能。 + +è¿™ç§æƒ…况下,å设备驱动应该设置 link_validate 域,以æä¾›å®ƒè‡ªèº«çš„链接 +验è¯å‡½æ•°ã€‚链接验è¯å‡½æ•°åº”对管é“(两端链接的都是 V4L2 å设备)ä¸çš„æ¯ä¸ª +链接调用。驱动还è¦è´Ÿè´£éªŒè¯åè®¾å¤‡å’Œè§†é¢‘èŠ‚ç‚¹é—´æ ¼å¼é…置的æ£ç¡®æ€§ã€‚ + +如果 link_validate æ“作没有设置,默认的 v4l2_subdev_link_validate_default() +函数将会被调用。这个函数ä¿è¯å®½ã€é«˜å’Œåª’体总线åƒç´ æ ¼å¼åœ¨é“¾æŽ¥çš„æ”¶å‘两端 +都一致。å设备驱动除了它们自己的检测外,也å¯ä»¥è‡ªç”±ä½¿ç”¨è¿™ä¸ªå‡½æ•°ä»¥æ‰§è¡Œ +ä¸Šé¢æåˆ°çš„æ£€æŸ¥ã€‚ + +设备(桥)驱动程åºå¿…é¡»å‘ v4l2_device 注册 v4l2_subdev: + + int err = v4l2_device_register_subdev(v4l2_dev, sd); + +如果å设备模å—åœ¨å®ƒæ³¨å†Œå‰æ¶ˆå¤±ï¼Œè¿™ä¸ªæ“作å¯èƒ½å¤±è´¥ã€‚在这个函数æˆåŠŸè¿”å›žåŽï¼Œ +subdev->dev 域就指å‘了 v4l2_device。 + +如果 v4l2_device 父设备的 mdev åŸŸä¸ºéž NULL 值,则å设备实体将被自动 +注册为媒体设备。 + +注销å设备则å¯ç”¨å¦‚下函数: + + v4l2_device_unregister_subdev(sd); + +æ¤åŽï¼Œå设备模å—å°±å¯å¸è½½ï¼Œä¸” sd->dev == NULL。 + +注册之设备åŽï¼Œå¯é€šè¿‡ä»¥ä¸‹æ–¹å¼ç›´æŽ¥è°ƒç”¨å…¶æ“作函数: + + err = sd->ops->core->g_chip_ident(sd, &chip); + +但使用如下å®ä¼šæ¯”较容易且åˆé€‚: + + err = v4l2_subdev_call(sd, core, g_chip_ident, &chip); + +这个å®å°†ä¼šåš NULL 指针检查,如果 subdev 为 NULL,则返回-ENODEV;如果 +subdev->core 或 subdev->core->g_chip_ident 为 NULL,则返回 -ENOIOCTLCMDï¼› +å¦åˆ™å°†è¿”回 subdev->ops->core->g_chip_ident ops 调用的实际结果。 + +有时也å¯èƒ½åŒæ—¶è°ƒç”¨æ‰€æœ‰æˆ–一系列å设备的æŸä¸ªæ“作函数: + + v4l2_device_call_all(v4l2_dev, 0, core, g_chip_ident, &chip); + +任何䏿”¯æŒæ¤æ“作的åè®¾å¤‡éƒ½ä¼šè¢«è·³è¿‡ï¼Œå¹¶å¿½ç•¥é”™è¯¯è¿”å›žå€¼ã€‚ä½†å¦‚æžœä½ éœ€è¦ +检查出错ç ,则å¯ä½¿ç”¨å¦‚下函数: + + err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_chip_ident, &chip); + +除 -ENOIOCTLCMD 外的任何错误都会跳出循环并返回错误值。如果(除 -ENOIOCTLCMD +外)没有错误å‘生,则返回 0。 + +å¯¹äºŽä»¥ä¸Šä¸¤ä¸ªå‡½æ•°çš„ç¬¬äºŒä¸ªå‚æ•°ä¸ºç»„ ID。如果为 0,则所有å设备都会执行 +这个æ“ä½œã€‚å¦‚æžœä¸ºéž 0 å€¼ï¼Œåˆ™åªæœ‰é‚£äº›ç»„ ID 匹é…çš„å设备æ‰ä¼šæ‰§è¡Œæ¤æ“作。 +在桥驱动注册一个å设备å‰ï¼Œå¯ä»¥è®¾ç½® sd->grp_id 为任何期望值(默认值为 +0)。这个值属于桥驱动,且å设备驱动将ä¸ä¼šä¿®æ”¹å’Œä½¿ç”¨å®ƒã€‚ + +组 ID 赋予了桥驱动更多对于如何调用回调的控制。例如,电路æ¿ä¸Šæœ‰å¤šä¸ª +音频芯片,æ¯ä¸ªéƒ½æœ‰æ”¹å˜éŸ³é‡çš„èƒ½åŠ›ã€‚ä½†å½“ç”¨æˆ·æƒ³è¦æ”¹å˜éŸ³é‡çš„æ—¶å€™ï¼Œé€šå¸¸ +åªæœ‰ä¸€ä¸ªä¼šè¢«å®žé™…ä½¿ç”¨ã€‚ä½ å¯ä»¥å¯¹è¿™æ ·çš„å设备设置组 ID 为(例如 AUDIO_CONTROLLER) +并在调用 v4l2_device_call_all() 时指定它为组 ID 值。这就ä¿è¯äº†åªæœ‰ +需è¦çš„å设备æ‰ä¼šæ‰§è¡Œè¿™ä¸ªå›žè°ƒã€‚ + +如果å设备需è¦é€šçŸ¥å®ƒçš„ v4l2_device 父设备一个事件,å¯ä»¥è°ƒç”¨ +v4l2_subdev_notify(sd, notification, arg)ã€‚è¿™ä¸ªå®æ£€æŸ¥æ˜¯å¦æœ‰ä¸€ä¸ª +notify() 回调被注册,如果没有,返回 -ENODEV。å¦åˆ™è¿”回 notify() 调用 +结果。 + +使用 v4l2_subdev 的好处在于它是一个通用结构体,且ä¸åŒ…å«ä»»ä½•底层硬件 +ä¿¡æ¯ã€‚所有驱动å¯ä»¥åŒ…å«å¤šä¸ª I2C 总线的å设备,但也有å设备是通过 GPIO +控制。这个区别仅在é…置设备时有关系,一旦å设备注册完æˆï¼Œå¯¹äºŽ v4l2 +å系统æ¥è¯´å°±å®Œå…¨é€æ˜Žäº†ã€‚ + + +V4L2 å设备用户空间API +-------------------- + +除了通过 v4l2_subdev_ops ç»“æž„å¯¼å‡ºçš„å†…æ ¸ API,V4L2 å设备也å¯ä»¥ç›´æŽ¥ +é€šè¿‡ç”¨æˆ·ç©ºé—´åº”ç”¨ç¨‹åºæ¥æŽ§åˆ¶ã€‚ + +å¯ä»¥åœ¨ /dev ä¸åˆ›å»ºå为 v4l-subdevX 设备节点,以通过其直接访问å设备。 +如果å设备支æŒç”¨æˆ·ç©ºé—´ç›´æŽ¥é…置,必须在注册å‰è®¾ç½® V4L2_SUBDEV_FL_HAS_DEVNODE +æ ‡å¿—ã€‚ + +注册å设备之åŽï¼Œ v4l2_device 驱动会通过调用 v4l2_device_register_subdev_nodes() +函数为所有已注册并设置了 V4L2_SUBDEV_FL_HAS_DEVNODE çš„å设备创建 +设备节点。这些设备节点会在åè®¾å¤‡æ³¨é”€æ—¶è‡ªåŠ¨åˆ é™¤ã€‚ + +è¿™äº›è®¾å¤‡èŠ‚ç‚¹å¤„ç† V4L2 API 的一个å集。 + +VIDIOC_QUERYCTRL +VIDIOC_QUERYMENU +VIDIOC_G_CTRL +VIDIOC_S_CTRL +VIDIOC_G_EXT_CTRLS +VIDIOC_S_EXT_CTRLS +VIDIOC_TRY_EXT_CTRLS + + 这些 ioctls 控制与 V4L2 ä¸å®šä¹‰çš„一致。他们行为相åŒï¼Œå”¯ä¸€çš„ + ä¸åŒæ˜¯ä»–们åªå¤„ç†åè®¾å¤‡çš„æŽ§åˆ¶å®žçŽ°ã€‚æ ¹æ®é©±åŠ¨ç¨‹åºï¼Œè¿™äº›æŽ§åˆ¶ä¹Ÿ + å¯ä»¥é€šè¿‡ä¸€ä¸ªï¼ˆæˆ–多个) V4L2 设备节点访问。 + +VIDIOC_DQEVENT +VIDIOC_SUBSCRIBE_EVENT +VIDIOC_UNSUBSCRIBE_EVENT + + 这些 ioctls 事件与 V4L2 ä¸å®šä¹‰çš„一致。他们行为相åŒï¼Œå”¯ä¸€çš„ + ä¸åŒæ˜¯ä»–们åªå¤„ç†åè®¾å¤‡äº§ç”Ÿçš„äº‹ä»¶ã€‚æ ¹æ®é©±åŠ¨ç¨‹åºï¼Œè¿™äº›äº‹ä»¶ä¹Ÿ + å¯ä»¥é€šè¿‡ä¸€ä¸ªï¼ˆæˆ–多个) V4L2 设备节点上报。 + + è¦ä½¿ç”¨äº‹ä»¶é€šçŸ¥çš„å设备驱动,在注册å设备å‰å¿…须在 v4l2_subdev::flags + ä¸è®¾ç½® V4L2_SUBDEV_USES_EVENTS 并在 v4l2_subdev::nevents + ä¸åˆå§‹åŒ–事件队列深度。注册完æˆåŽï¼Œäº‹ä»¶ä¼šåœ¨ v4l2_subdev::devnode + 设备节点ä¸åƒé€šå¸¸ä¸€æ ·è¢«æŽ’队。 + + 为æ£ç¡®æ”¯æŒäº‹ä»¶æœºåˆ¶ï¼Œpoll() 文件æ“作也应被实现。 + +ç§æœ‰ ioctls + + ä¸åœ¨ä»¥ä¸Šåˆ—表ä¸çš„æ‰€æœ‰ ioctls 会通过 core::ioctl æ“ä½œç›´æŽ¥ä¼ é€’ + ç»™å设备驱动。 + + +I2C å设备驱动 +------------- + +由于这些驱动很常è§ï¼Œæ‰€ä»¥å†…特æä¾›äº†ç‰¹å®šçš„辅助函数(v4l2-common.h)让这些 +è®¾å¤‡çš„ä½¿ç”¨æ›´åŠ å®¹æ˜“ã€‚ + +æ·»åŠ v4l2_subdev 支æŒçš„æŽ¨è方法是让 I2C 驱动将 v4l2_subdev 结构体 +嵌入到为æ¯ä¸ª I2C 设备实例创建的状æ€ç»“构体ä¸ã€‚而最简å•çš„è®¾å¤‡æ²¡æœ‰çŠ¶æ€ +ç»“æž„ä½“ï¼Œæ¤æ—¶å¯ä»¥ç›´æŽ¥åˆ›å»ºä¸€ä¸ª v4l2_subdev 结构体。 + +一个典型的状æ€ç»“构体如下所示(‘chipname’用芯片å代替): + +struct chipname_state { + struct v4l2_subdev sd; + ... /* é™„åŠ çš„çŠ¶æ€åŸŸ*/ +}; + +åˆå§‹åŒ– v4l2_subdev 结构体的方法如下: + + v4l2_i2c_subdev_init(&state->sd, client, subdev_ops); + +这个函数将填充 v4l2_subdev 结构体ä¸çš„æ‰€æœ‰åŸŸï¼Œå¹¶ä¿è¯ v4l2_subdev å’Œ +i2c_client 都指å‘å½¼æ¤ã€‚ + +åŒæ—¶ï¼Œä½ 也应该为从 v4l2_subdev 指针找到 chipname_state 结构体指针 +æ·»åŠ ä¸€ä¸ªè¾…åŠ©å†…è”函数。 + +static inline struct chipname_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct chipname_state, sd); +} + +使用以下函数å¯ä»¥é€šè¿‡ v4l2_subdev 结构体指针获得 i2c_client 结构体 +指针: + + struct i2c_client *client = v4l2_get_subdevdata(sd); + +而以下函数则相å,通过 i2c_client 结构体指针获得 v4l2_subdev 结构体 +指针: + + struct v4l2_subdev *sd = i2c_get_clientdata(client); + +当 remove()函数被调用å‰ï¼Œå¿…é¡»ä¿è¯å…ˆè°ƒç”¨ v4l2_device_unregister_subdev(sd)。 +æ¤æ“ä½œå°†ä¼šä»Žæ¡¥é©±åŠ¨ä¸æ³¨é”€å设备。å³ä½¿å设备没有注册,调用æ¤å‡½æ•°ä¹Ÿæ˜¯ +安全的。 + +å¿…é¡»è¿™æ ·åšçš„åŽŸå› æ˜¯ï¼šå½“æ¡¥é©±åŠ¨æ³¨é”€ i2c 适é…器时,remove()回调函数 +会被那个适é…器上的 i2c 设备调用。æ¤åŽï¼Œç›¸åº”çš„ v4l2_subdev 结构体 +å°±ä¸å˜åœ¨äº†ï¼Œæ‰€æœ‰å®ƒä»¬å¿…须先被注销。在 remove()回调函数ä¸è°ƒç”¨ +v4l2_device_unregister_subdev(sd),å¯ä»¥ä¿è¯æ‰§è¡Œæ€»æ˜¯æ£ç¡®çš„。 + + +桥驱动也有一些辅组函数å¯ç”¨ï¼š + +struct v4l2_subdev *sd = v4l2_i2c_new_subdev(v4l2_dev, adapter, + "module_foo", "chipid", 0x36, NULL); + +è¿™ä¸ªå‡½æ•°ä¼šåŠ è½½ç»™å®šçš„æ¨¡å—(如果没有模å—需è¦åŠ è½½ï¼Œå¯ä»¥ä¸º NULL), +并用给定的 i2c 适é…器结构体指针(i2c_adapter)和 器件地å€ï¼ˆchip/address) +ä½œä¸ºå‚æ•°è°ƒç”¨ i2c_new_device()。如果一切顺利,则就在 v4l2_device +䏿³¨å†Œäº†å设备。 + +ä½ ä¹Ÿå¯ä»¥åˆ©ç”¨ v4l2_i2c_new_subdev()的最åŽä¸€ä¸ªå‚æ•°ï¼Œä¼ é€’ä¸€ä¸ªå¯èƒ½çš„ +I2C åœ°å€æ•°ç»„,让函数自动探测。这些探测地å€åªæœ‰åœ¨å‰ä¸€ä¸ªå‚数为 0 çš„ +情况下使用。éžé›¶å‚æ•°æ„味ç€ä½ 知é“准确的 i2c 地å€ï¼Œæ‰€ä»¥æ¤æ—¶æ— 须进行 +探测。 + +如果出错,两个函数都返回 NULL。 + +注æ„ï¼šä¼ é€’ç»™ v4l2_i2c_new_subdev()çš„ chipid 通常与模å—å一致。 +它å…è®¸ä½ æŒ‡å®šä¸€ä¸ªèŠ¯ç‰‡çš„å˜ä½“,比如“saa7114â€æˆ–“saa7115â€ã€‚一般通过 +i2c 驱动自动探测。chipid 的使用是在今åŽéœ€è¦æ·±å…¥äº†è§£çš„事情。这个与 +i2c 驱动ä¸åŒï¼Œè¾ƒå®¹æ˜“混淆。è¦çŸ¥é“支æŒå“ªäº›èŠ¯ç‰‡å˜ä½“ï¼Œä½ å¯ä»¥æŸ¥é˜… i2c +驱动代ç çš„ i2c_device_id 表,上é¢åˆ—出了所有å¯èƒ½æ”¯æŒçš„芯片。 + +还有两个辅助函数: + +v4l2_i2c_new_subdev_cfgï¼šè¿™ä¸ªå‡½æ•°æ·»åŠ æ–°çš„ irq å’Œ platform_data +傿•°ï¼Œå¹¶æœ‰â€˜addr’和‘probed_addrsâ€™å‚æ•°ï¼šå¦‚æžœ addr éžé›¶ï¼Œåˆ™è¢«ä½¿ç”¨ +ï¼ˆä¸æŽ¢æµ‹å˜ä½“),å¦åˆ™ probed_addrs ä¸çš„地å€å°†ç”¨äºŽè‡ªåŠ¨æŽ¢æµ‹ã€‚ + +例如:以下代ç 将会探测地å€ï¼ˆ0x10): + +struct v4l2_subdev *sd = v4l2_i2c_new_subdev_cfg(v4l2_dev, adapter, + "module_foo", "chipid", 0, NULL, 0, I2C_ADDRS(0x10)); + +v4l2_i2c_new_subdev_board 使用一个 i2c_board_info 结构体,将其 +替代 irqã€platform_data å’Œ add r傿•°ä¼ 递给 i2c 驱动。 + +如果åè®¾å¤‡æ”¯æŒ s_config æ ¸å¿ƒæ“作,这个æ“作会在å设备é…置好之åŽä»¥ irq å’Œ +platform_data ä¸ºå‚æ•°è°ƒç”¨ã€‚早期的 v4l2_i2c_new_(probed_)subdev 函数 +åŒæ ·ä¹Ÿä¼šè°ƒç”¨ s_config,但仅在 irq 为 0 且 platform_data 为 NULL 时。 + +video_device结构体 +----------------- + +在 /dev ç›®å½•ä¸‹çš„å®žé™…è®¾å¤‡èŠ‚ç‚¹æ ¹æ® video_device 结构体(v4l2-dev.h) +创建。æ¤ç»“构体既å¯ä»¥åЍæ€åˆ†é…也å¯ä»¥åµŒå…¥åˆ°ä¸€ä¸ªæ›´å¤§çš„结构体ä¸ã€‚ + +动æ€åˆ†é…方法如下: + + struct video_device *vdev = video_device_alloc(); + + if (vdev == NULL) + return -ENOMEM; + + vdev->release = video_device_release; + +如果将其嵌入到一个大结构体ä¸ï¼Œåˆ™å¿…须自己实现 release()回调。 + + struct video_device *vdev = &my_vdev->vdev; + + vdev->release = my_vdev_release; + +release()回调必须被设置,且在最åŽä¸€ä¸ª video_device ç”¨æˆ·é€€å‡ºä¹‹åŽ +被调用。 + +默认的 video_device_release()å›žè°ƒåªæ˜¯è°ƒç”¨ kfree æ¥é‡Šæ”¾ä¹‹å‰åˆ†é…çš„ +内å˜ã€‚ + +ä½ åº”è¯¥è®¾ç½®è¿™äº›åŸŸï¼š + +- v4l2_dev: 设置为 v4l2_device 父设备。 + +- name: 设置为唯一的æè¿°æ€§è®¾å¤‡å。 + +- fops: 设置为已有的 v4l2_file_operations 结构体。 + +- ioctl_ops: å¦‚æžœä½ ä½¿ç”¨v4l2_ioctl_ops æ¥ç®€åŒ– ioctl 的维护 + (强烈建议使用,且将æ¥å¯èƒ½å˜ä¸ºå¼ºåˆ¶æ€§çš„!),然åŽè®¾ç½®ä½ 自己的 + v4l2_ioctl_ops 结构体. + +- lock: å¦‚æžœä½ è¦åœ¨é©±åЍä¸å®žçŽ°æ‰€æœ‰çš„é”æ“作,则设为 NULL 。å¦åˆ™ + å°±è¦è®¾ç½®ä¸€ä¸ªæŒ‡å‘ struct mutex_lock 结构体的指针,这个é”å°† + 在 unlocked_ioctl 文件æ“作被调用å‰ç”±å†…æ ¸èŽ·å¾—ï¼Œå¹¶åœ¨è°ƒç”¨è¿”å›žåŽ + 释放。详è§ä¸‹ä¸€èŠ‚ã€‚ + +- prio: ä¿æŒå¯¹ä¼˜å…ˆçº§çš„跟踪。用于实现 VIDIOC_G/S_PRIORITY。如果 + 设置为 NULL,则会使用 v4l2_device ä¸çš„ v4l2_prio_state 结构体。 + 如果è¦å¯¹æ¯ä¸ªè®¾å¤‡èŠ‚ç‚¹ï¼ˆç»„ï¼‰å®žçŽ°ç‹¬ç«‹çš„ä¼˜å…ˆçº§ï¼Œå¯ä»¥å°†å…¶æŒ‡å‘自己 + 实现的 v4l2_prio_state 结构体。 + +- parent: 仅在使用 NULL ä½œä¸ºçˆ¶è®¾å¤‡ç»“æž„ä½“å‚æ•°æ³¨å†Œ v4l2_device æ—¶ + 设置æ¤å‚æ•°ã€‚åªæœ‰åœ¨ä¸€ä¸ªç¡¬ä»¶è®¾å¤‡åŒ…å«å¤šä¸€ä¸ª PCI 设备,共享åŒä¸€ä¸ª + v4l2_device æ ¸å¿ƒæ—¶æ‰ä¼šå‘生。 + + cx88 驱动就是一个例å:一个 v4l2_device ç»“æž„ä½“æ ¸å¿ƒï¼Œè¢«ä¸€ä¸ªè£¸çš„ + 视频 PCI 设备(cx8800)和一个 MPEG PCI 设备(cx8802)共用。由于 + v4l2_device æ— æ³•ä¸Žç‰¹å®šçš„ PCI 设备关è”,所有没有设置父设备。但当 + video_device é…ç½®åŽï¼Œå°±çŸ¥é“使用哪个父 PCI 设备了。 + +- flags:å¯é€‰ã€‚å¦‚æžœä½ è¦è®©æ¡†æž¶å¤„ç†è®¾ç½® VIDIOC_G/S_PRIORITY ioctls, + 请设置 V4L2_FL_USE_FH_PRIOã€‚è¿™è¦æ±‚ä½ ä½¿ç”¨ v4l2_fh 结构体。 + ä¸€æ—¦æ‰€æœ‰é©±åŠ¨ä½¿ç”¨äº†æ ¸å¿ƒçš„ä¼˜å…ˆçº§å¤„ç†ï¼Œæœ€ç»ˆè¿™ä¸ªæ ‡å¿—将消失。但现在它 + 必须被显å¼è®¾ç½®ã€‚ + +å¦‚æžœä½ ä½¿ç”¨ v4l2_ioctl_ops,则应该在 v4l2_file_operations ç»“æž„ä½“ä¸ +设置 .unlocked_ioctl æŒ‡å‘ video_ioctl2。 + +请勿使用 .ioctlï¼å®ƒå·²è¢«åºŸå¼ƒï¼Œä»ŠåŽå°†æ¶ˆå¤±ã€‚ + +æŸäº›æƒ…å†µä¸‹ä½ è¦å‘Šè¯‰æ ¸å¿ƒï¼šä½ 在 v4l2_ioctl_ops 指定的æŸä¸ªå‡½æ•°åº”被忽略。 +ä½ å¯ä»¥åœ¨ video_device_register 被调用å‰é€šè¿‡ä»¥ä¸‹å‡½æ•°æ ‡è®°è¿™ä¸ª ioctls。 + +void v4l2_disable_ioctl(struct video_device *vdev, unsigned int cmd); + +åŸºäºŽå¤–éƒ¨å› ç´ ï¼ˆä¾‹å¦‚æŸä¸ªæ¿å¡å·²è¢«ä½¿ç”¨ï¼‰ï¼Œåœ¨ä¸åˆ›å»ºæ–°ç»“æž„ä½“çš„æƒ…å†µä¸‹ï¼Œä½ æƒ³ +è¦å…³é— v4l2_ioctl_ops 䏿Ÿä¸ªç‰¹æ€§å¾€å¾€éœ€è¦è¿™ä¸ªæœºåˆ¶ã€‚ + +v4l2_file_operations 结构体是 file_operations 的一个åé›†ã€‚å…¶ä¸»è¦ +åŒºåˆ«åœ¨äºŽï¼šå› inode 傿•°ä»Žæœªè¢«ä½¿ç”¨ï¼Œå®ƒå°†è¢«å¿½ç•¥ã€‚ + +如果需è¦ä¸Žåª’体框架整åˆï¼Œä½ 必须通过调用 media_entity_init() åˆå§‹åŒ– +嵌入在 video_device 结构体ä¸çš„ media_entity(entity 域)结构体: + + struct media_pad *pad = &my_vdev->pad; + int err; + + err = media_entity_init(&vdev->entity, 1, pad, 0); + +pads 数组必须预先åˆå§‹åŒ–ã€‚æ²¡æœ‰å¿…è¦æ‰‹åŠ¨è®¾ç½® media_entity çš„ type å’Œ +name 域。 + +当(任何)å设备节点被打开/å…³é—,对 entity 的引用将被自动获å–/释放。 + +v4l2_file_operations ä¸Žé” +-------------------------- + +ä½ å¯ä»¥åœ¨ video_device 结构体ä¸è®¾ç½®ä¸€ä¸ªæŒ‡å‘ mutex_lock 的指针。通常 +è¿™æ—¢å¯æ˜¯ä¸€ä¸ªé¡¶å±‚互斥é”也å¯ä¸ºè®¾å¤‡èŠ‚ç‚¹è‡ªèº«çš„äº’æ–¥é”。默认情况下,æ¤é” +用于 unlocked_ioctl,但为了使用 ioctls ä½ é€šè¿‡ä»¥ä¸‹å‡½æ•°å¯ç¦ç”¨é”定: + + void v4l2_disable_ioctl_locking(struct video_device *vdev, unsigned int cmd); + +例如: v4l2_disable_ioctl_locking(vdev, VIDIOC_DQBUF); + +ä½ å¿…é¡»åœ¨æ³¨å†Œ video_device å‰è°ƒç”¨è¿™ä¸ªå‡½æ•°ã€‚ + +特别是对于 USB 驱动程åºï¼ŒæŸäº›å‘½ä»¤ï¼ˆå¦‚设置控制)需è¦å¾ˆé•¿çš„æ—¶é—´ï¼Œå¯èƒ½ +需è¦è‡ªè¡Œä¸ºç¼“冲区队列的 ioctls 实现é”定。 + +å¦‚æžœä½ éœ€è¦æ›´ç»†ç²’度的é”ï¼Œä½ å¿…é¡»è®¾ç½® mutex_lock 为 NULL,并完全自己实现 +锿œºåˆ¶ã€‚ + +这完全由驱动开å‘è€…å†³å®šä½¿ç”¨ä½•ç§æ–¹æ³•ã€‚ç„¶è€Œï¼Œå¦‚æžœä½ çš„é©±åŠ¨å˜åœ¨é•¿å»¶æ—¶æ“作 +ï¼ˆä¾‹å¦‚ï¼Œæ”¹å˜ USB æ‘„åƒå¤´çš„æ›å…‰æ—¶é—´å¯èƒ½éœ€è¦è¾ƒé•¿æ—¶é—´ï¼‰ï¼Œè€Œä½ åˆæƒ³è®©ç”¨æˆ· +在ç‰å¾…é•¿å»¶æ—¶æ“ä½œå®ŒæˆæœŸé—´åšå…¶ä»–çš„äº‹ï¼Œåˆ™ä½ æœ€å¥½è‡ªå·±å®žçŽ°é”æœºåˆ¶ã€‚ + +如果指定一个é”,则所有 ioctl æ“作将在这个é”çš„ä½œç”¨ä¸‹ä¸²è¡Œæ‰§è¡Œã€‚å¦‚æžœä½ +使用 videobuf,则必须将åŒä¸€ä¸ªé”ä¼ é€’ç»™ videobuf 队列åˆå§‹åŒ–函数;如 +videobuf å¿…é¡»ç‰å¾…一帧的到达,则å¯ä¸´æ—¶è§£é”并在这之åŽé‡æ–°ä¸Šé”。如果驱动 +ä¹Ÿåœ¨ä»£ç æ‰§è¡ŒæœŸé—´ç‰å¾…,则å¯åšåŒæ ·çš„工作(临时解é”,å†ä¸Šé”)让其他进程 +å¯ä»¥åœ¨ç¬¬ä¸€ä¸ªè¿›ç¨‹é˜»å¡žæ—¶è®¿é—®è®¾å¤‡èŠ‚ç‚¹ã€‚ + +在使用 videobuf2 的情况下,必须实现 wait_prepare å’Œ wait_finish 回调 +在适当的时候解é”/åŠ é”ã€‚è¿›ä¸€æ¥æ¥è¯´ï¼Œå¦‚æžœä½ åœ¨ video_device 结构体ä¸ä½¿ç”¨ +é”,则必须在 wait_prepare å’Œ wait_finish ä¸å¯¹è¿™ä¸ªäº’æ–¥é”进行解é”/åŠ é”。 + +çƒæ’拔的æ–开实现也必须在调用 v4l2_device_disconnect å‰èŽ·å¾—é”。 + +video_device注册 +--------------- + +接下æ¥ä½ éœ€è¦æ³¨å†Œè§†é¢‘è®¾å¤‡ï¼šè¿™ä¼šä¸ºä½ åˆ›å»ºä¸€ä¸ªå—符设备。 + + err = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + if (err) { + video_device_release(vdev); /* or kfree(my_vdev); */ + return err; + } + +如果 v4l2_device 父设备的 mdev åŸŸä¸ºéž NULL 值,视频设备实体将自动 +注册为媒体设备。 + +注册哪ç§è®¾å¤‡æ˜¯æ ¹æ®ç±»åž‹ï¼ˆtypeï¼‰å‚æ•°ã€‚å˜åœ¨ä»¥ä¸‹ç±»åž‹ï¼š + +VFL_TYPE_GRABBER: 用于视频输入/输出设备的 videoX +VFL_TYPE_VBI: ç”¨äºŽåž‚ç›´æ¶ˆéšæ•°æ®çš„ vbiX (例如,éšè—å¼å—幕,图文电视) +VFL_TYPE_RADIO: 用于广æ’è°ƒè°å™¨çš„ radioX + +最åŽä¸€ä¸ªå‚æ•°è®©ä½ ç¡®å®šä¸€ä¸ªæ‰€æŽ§åˆ¶è®¾å¤‡çš„è®¾å¤‡èŠ‚ç‚¹å·æ•°é‡(例如 videoX ä¸çš„ X)。 +é€šå¸¸ä½ å¯ä»¥ä¼ å…¥-1,让 v4l2 框架自己选择第一个空闲的编å·ã€‚但是有时用户 +需è¦é€‰æ‹©ä¸€ä¸ªç‰¹å®šçš„节点å·ã€‚驱动å…许用户通过驱动模å—傿•°é€‰æ‹©ä¸€ä¸ªç‰¹å®šçš„ +è®¾å¤‡èŠ‚ç‚¹å·æ˜¯å¾ˆæ™®é的。这个编å·å°†ä¼šä¼ 递给这个函数,且 video_register_device +将会试图选择这个设备节点å·ã€‚如果这个编å·è¢«å 用,下一个空闲的设备节点 +ç¼–å·å°†è¢«é€‰ä¸ï¼Œå¹¶å‘å†…æ ¸æ—¥å¿—ä¸å‘é€ä¸€ä¸ªè¦å‘Šä¿¡æ¯ã€‚ + +å¦ä¸€ä¸ªä½¿ç”¨åœºæ™¯æ˜¯å½“é©±åŠ¨åˆ›å»ºå¤šä¸ªè®¾å¤‡æ—¶ã€‚è¿™ç§æƒ…况下,对ä¸åŒçš„视频设备在 +ç¼–å·ä¸Šä½¿ç”¨ä¸åŒçš„范围是很有用的。例如,视频æ•获设备从 0 开始,视频 +输出设备从 16 å¼€å§‹ã€‚æ‰€ä»¥ä½ å¯ä»¥ä½¿ç”¨æœ€åŽä¸€ä¸ªå‚æ•°æ¥æŒ‡å®šè®¾å¤‡èŠ‚ç‚¹å·æœ€å°å€¼ï¼Œ +而 v4l2 框架会试图选择第一个的空闲编å·ï¼ˆç‰äºŽæˆ–å¤§äºŽä½ æä¾›çš„ç¼–å·ï¼‰ã€‚ +如果失败,则它会就选择第一个空闲的编å·ã€‚ + +ç”±äºŽè¿™ç§æƒ…å†µä¸‹ï¼Œä½ ä¼šå¿½ç•¥æ— æ³•é€‰æ‹©ç‰¹å®šè®¾å¤‡èŠ‚ç‚¹å·çš„è¦å‘Šï¼Œåˆ™å¯è°ƒç”¨ +video_register_device_no_warn() 函数é¿å…è¦å‘Šä¿¡æ¯çš„产生。 + +åªè¦è®¾å¤‡èŠ‚ç‚¹è¢«åˆ›å»ºï¼Œä¸€äº›å±žæ€§ä¹Ÿä¼šåŒæ—¶åˆ›å»ºã€‚在 /sys/class/video4linux +目录ä¸ä½ 会找到这些设备。例如进入其ä¸çš„ video0 ç›®å½•ï¼Œä½ ä¼šçœ‹åˆ°â€˜name’和 +‘index’属性。‘name’属性值就是 video_device 结构体ä¸çš„‘name’域。 + +‘indexâ€™å±žæ€§å€¼å°±æ˜¯è®¾å¤‡èŠ‚ç‚¹çš„ç´¢å¼•å€¼ï¼šæ¯æ¬¡è°ƒç”¨ video_register_device(), +索引值都递增 1 。第一个视频设备节点总是从索引值 0 开始。 + +用户å¯ä»¥è®¾ç½® udev 规则,利用索引属性生æˆèŠ±å“¨çš„è®¾å¤‡å(例如:用‘mpegX’ +代表 MPEG 视频æ•获设备节点)。 + +在设备æˆåŠŸæ³¨å†ŒåŽï¼Œå°±å¯ä»¥ä½¿ç”¨è¿™äº›åŸŸï¼š + +- vfl_type: ä¼ é€’ç»™ video_register_device 的设备类型。 +- minor: 已指派的次设备å·ã€‚ +- num: è®¾å¤‡èŠ‚ç‚¹ç¼–å· (例如 videoX ä¸çš„ X)。 +- index: 设备索引å·ã€‚ + +å¦‚æžœæ³¨å†Œå¤±è´¥ï¼Œä½ å¿…é¡»è°ƒç”¨ video_device_release() æ¥é‡Šæ”¾å·²åˆ†é…çš„ +video_device 结构体;如果 video_device 是嵌入在自己创建的结构体ä¸ï¼Œ +ä½ ä¹Ÿå¿…é¡»é‡Šæ”¾å®ƒã€‚vdev->release() 回调ä¸ä¼šåœ¨æ³¨å†Œå¤±è´¥ä¹‹åŽè¢«è°ƒç”¨ï¼Œ +ä½ ä¹Ÿä¸åº”è¯•å›¾åœ¨æ³¨å†Œå¤±è´¥åŽæ³¨é”€è®¾å¤‡ã€‚ + + +video_device 注销 +---------------- + +当视频设备节点已被移除,ä¸è®ºæ˜¯å¸è½½é©±åŠ¨è¿˜æ˜¯USB设备æ–å¼€ï¼Œä½ éƒ½åº”æ³¨é”€ +它们: + + video_unregister_device(vdev); + +这个æ“作将从 sysfs ä¸ç§»é™¤è®¾å¤‡èŠ‚ç‚¹ï¼ˆå¯¼è‡´ udev 将其从 /dev ä¸ç§»é™¤ï¼‰ã€‚ + +video_unregister_device() 返回之åŽï¼Œå°±æ— æ³•å®Œæˆæ‰“å¼€æ“作。尽管如æ¤ï¼Œ +USB 设备的情况则ä¸åŒï¼ŒæŸäº›åº”用程åºå¯èƒ½ä¾ç„¶æ‰“å¼€ç€å…¶ä¸ä¸€ä¸ªå·²æ³¨é”€è®¾å¤‡ +节点。所以在注销之åŽï¼Œæ‰€æœ‰æ–‡ä»¶æ“作(当然除了 release )也应返回错误值。 + +当最åŽä¸€ä¸ªè§†é¢‘设备节点的用户退出,则 vdev->release() 回调会被调用, +å¹¶ä¸”ä½ å¯ä»¥åšæœ€åŽçš„æ¸…ç†æ“作。 + +ä¸è¦å¿˜è®°æ¸…ç†ä¸Žè§†é¢‘设备相关的媒体入å£ï¼ˆå¦‚果被åˆå§‹åŒ–过): + + media_entity_cleanup(&vdev->entity); + +è¿™å¯ä»¥åœ¨ release 回调ä¸å®Œæˆã€‚ + + +video_device 辅助函数 +--------------------- + +一些有用的辅助函数如下: + +- file/video_device ç§æœ‰æ•°æ® + +ä½ å¯ä»¥ç”¨ä»¥ä¸‹å‡½æ•°åœ¨ video_device 结构体ä¸è®¾ç½®/获å–é©±åŠ¨ç§æœ‰æ•°æ®ï¼š + +void *video_get_drvdata(struct video_device *vdev); +void video_set_drvdata(struct video_device *vdev, void *data); + +注æ„:在调用 video_register_device() 剿‰§è¡Œ video_set_drvdata() +是安全的。 + +而以下函数: + +struct video_device *video_devdata(struct file *file); + +返回 file ç»“æž„ä½“ä¸æ‹¥æœ‰çš„çš„ video_device 指针。 + +video_drvdata 辅助函数结åˆäº† video_get_drvdata å’Œ video_devdata +的功能: + +void *video_drvdata(struct file *file); + +ä½ å¯ä»¥ä½¿ç”¨å¦‚下代ç 从 video_device 结构体ä¸èŽ·å– v4l2_device 结构体 +指针: + +struct v4l2_device *v4l2_dev = vdev->v4l2_dev; + +- 设备节点å + +video_device è®¾å¤‡èŠ‚ç‚¹åœ¨å†…æ ¸ä¸çš„åç§°å¯ä»¥é€šè¿‡ä»¥ä¸‹å‡½æ•°èŽ·å¾— + +const char *video_device_node_name(struct video_device *vdev); + +这个åå—被用户空间工具(例如 udev)作为æç¤ºä¿¡æ¯ä½¿ç”¨ã€‚应尽å¯èƒ½ä½¿ç”¨ +æ¤åŠŸèƒ½ï¼Œè€Œéžè®¿é—® video_device::num å’Œ video_device::minor 域。 + + +视频缓冲辅助函数 +--------------- + +v4l2 æ ¸å¿ƒ API æä¾›äº†ä¸€ä¸ªå¤„ç†è§†é¢‘ç¼“å†²çš„æ ‡å‡†æ–¹æ³•(称为“videobufâ€)。 +这些方法使驱动å¯ä»¥é€šè¿‡ç»Ÿä¸€çš„æ–¹å¼å®žçް read()ã€mmap() å’Œ overlay()。 +ç›®å‰åœ¨è®¾å¤‡ä¸Šæ”¯æŒè§†é¢‘缓冲的方法有分散/èšé›† DMA(videobuf-dma-sg)〠+线性 DMA(videobuf-dma-contig)以åŠå¤§å¤šç”¨äºŽ USB 设备的用 vmalloc +分é…的缓冲(videobuf-vmalloc)。 + +请å‚阅 Documentation/video4linux/videobuf,以获得更多关于 videobuf +层的使用信æ¯ã€‚ + +v4l2_fh 结构体 +------------- + +v4l2_fh 结构体æä¾›ä¸€ä¸ªä¿å˜ç”¨äºŽ V4L2 æ¡†æž¶çš„æ–‡ä»¶å¥æŸ„特定数æ®çš„ç®€å•æ–¹æ³•。 +如果 video_device çš„ flag 设置了 V4L2_FL_USE_FH_PRIO æ ‡å¿—ï¼Œæ–°é©±åŠ¨ +必须使用 v4l2_fh ç»“æž„ä½“ï¼Œå› ä¸ºå®ƒä¹Ÿç”¨äºŽå®žçŽ°ä¼˜å…ˆçº§å¤„ç†ï¼ˆVIDIOC_G/S_PRIORITY)。 + +v4l2_fh 的用户(ä½äºŽ V4l2 框架ä¸ï¼Œå¹¶éžé©±åŠ¨ï¼‰å¯é€šè¿‡æµ‹è¯• +video_device->flags ä¸çš„ V4L2_FL_USES_V4L2_FH ä½å¾—知驱动是å¦ä½¿ç”¨ +v4l2_fh 作为他的 file->private_data 指针。这个ä½ä¼šåœ¨è°ƒç”¨ v4l2_fh_init() +时被设置。 + +v4l2_fh ç»“æž„ä½“ä½œä¸ºé©±åŠ¨è‡ªèº«æ–‡ä»¶å¥æŸ„结构体的一部分被分é…,且驱动在 +其打开函数ä¸å°† file->private_data 指å‘它。 + +在许多情况下,v4l2_fh 结构体会嵌入到一个更大的结构体ä¸ã€‚这钟情况下, +应该在 open() ä¸è°ƒç”¨ v4l2_fh_init+v4l2_fh_add,并在 release() ä¸ +调用 v4l2_fh_del+v4l2_fh_exit。 + +驱动å¯ä»¥é€šè¿‡ä½¿ç”¨ container_of 宿å–ä»–ä»¬è‡ªå·±çš„æ–‡ä»¶å¥æŸ„结构体。例如: + +struct my_fh { + int blah; + struct v4l2_fh fh; +}; + +... + +int my_open(struct file *file) +{ + struct my_fh *my_fh; + struct video_device *vfd; + int ret; + + ... + + my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL); + + ... + + v4l2_fh_init(&my_fh->fh, vfd); + + ... + + file->private_data = &my_fh->fh; + v4l2_fh_add(&my_fh->fh); + return 0; +} + +int my_release(struct file *file) +{ + struct v4l2_fh *fh = file->private_data; + struct my_fh *my_fh = container_of(fh, struct my_fh, fh); + + ... + v4l2_fh_del(&my_fh->fh); + v4l2_fh_exit(&my_fh->fh); + kfree(my_fh); + return 0; +} + +以下是 v4l2_fh 函数使用的简介: + +void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev) + + åˆå§‹åŒ–æ–‡ä»¶å¥æŸ„。这*å¿…é¡»*在驱动的 v4l2_file_operations->open() + å‡½æ•°ä¸æ‰§è¡Œã€‚ + +void v4l2_fh_add(struct v4l2_fh *fh) + + æ·»åŠ ä¸€ä¸ª v4l2_fh 到 video_device æ–‡ä»¶å¥æŸ„åˆ—è¡¨ã€‚ä¸€æ—¦æ–‡ä»¶å¥æŸ„ + åˆå§‹åŒ–完æˆå°±å¿…须调用。 + +void v4l2_fh_del(struct v4l2_fh *fh) + + 从 video_device() ä¸è§£é™¤æ–‡ä»¶å¥æŸ„的关è”ã€‚æ–‡ä»¶å¥æŸ„的退出函数也 + 将被调用。 + +void v4l2_fh_exit(struct v4l2_fh *fh) + + æ¸…ç†æ–‡ä»¶å¥æŸ„。在清ç†å®Œ v4l2_fh åŽï¼Œç›¸å…³å†…å˜ä¼šè¢«é‡Šæ”¾ã€‚ + + +如果 v4l2_fh 䏿˜¯åµŒå…¥åœ¨å…¶ä»–结构体ä¸çš„,则å¯ä»¥ç”¨è¿™äº›è¾…助函数: + +int v4l2_fh_open(struct file *filp) + + 分é…一个 v4l2_fh 结构体空间,åˆå§‹åŒ–å¹¶å°†å…¶æ·»åŠ åˆ° file 结构体相关的 + video_device 结构体ä¸ã€‚ + +int v4l2_fh_release(struct file *filp) + + 从 file 结构体相关的 video_device 结构体ä¸åˆ 除 v4l2_fh ï¼Œæ¸…ç† + v4l2_fh 并释放空间。 + +这两个函数å¯ä»¥æ’入到 v4l2_file_operation çš„ open() å’Œ release() +æ“作ä¸ã€‚ + + +æŸäº›é©±åŠ¨éœ€è¦åœ¨ç¬¬ä¸€ä¸ªæ–‡ä»¶å¥æŸ„打开和最åŽä¸€ä¸ªæ–‡ä»¶å¥æŸ„å…³é—的时候åšäº› +å·¥ä½œã€‚æ‰€ä»¥åŠ å…¥äº†ä¸¤ä¸ªè¾…åŠ©å‡½æ•°ä»¥æ£€æŸ¥ v4l2_fh ç»“æž„ä½“æ˜¯å¦æ˜¯ç›¸å…³è®¾å¤‡ +èŠ‚ç‚¹æ‰“å¼€çš„å”¯ä¸€æ–‡ä»¶å¥æŸ„。 + +int v4l2_fh_is_singular(struct v4l2_fh *fh) + + å¦‚æžœæ¤æ–‡ä»¶å¥æŸ„æ˜¯å”¯ä¸€æ‰“å¼€çš„æ–‡ä»¶å¥æŸ„,则返回 1 ,å¦åˆ™è¿”回 0 。 + +int v4l2_fh_is_singular_file(struct file *filp) + + 功能相åŒï¼Œä½†é€šè¿‡ filp->private_data 调用 v4l2_fh_is_singular。 + + +V4L2 事件机制 +----------- + +V4L2 事件机制æä¾›äº†ä¸€ä¸ªé€šç”¨çš„æ–¹æ³•å°†äº‹ä»¶ä¼ é€’åˆ°ç”¨æˆ·ç©ºé—´ã€‚é©±åŠ¨å¿…é¡»ä½¿ç”¨ +v4l2_fh æ‰èƒ½æ”¯æŒ V4L2 事件机制。 + + +事件通过一个类型和选择 ID æ¥å®šä¹‰ã€‚ID 对应一个 V4L2 对象,例如 +一个控制 ID。如果未使用,则 ID 为 0。 + +当用户订阅一个事件,驱动会为æ¤åˆ†é…一些 kevent 结构体。所以æ¯ä¸ª +事件组(类型ã€ID)都会有自己的一套 kevent 结构体。这ä¿è¯äº†å¦‚æžœ +ä¸€ä¸ªé©±åŠ¨çŸæ—¶é—´å†…产生了许多åŒç±»äº‹ä»¶ï¼Œä¸ä¼šè¦†ç›–其他类型的事件。 + +ä½†å¦‚æžœä½ æ”¶åˆ°çš„äº‹ä»¶æ•°é‡å¤§äºŽåŒç±»äº‹ä»¶ kevent çš„ä¿å˜æ•°é‡ï¼Œåˆ™æœ€æ—©çš„ +äº‹ä»¶å°†è¢«ä¸¢å¼ƒï¼Œå¹¶åŠ å…¥æ–°äº‹ä»¶ã€‚ + +æ¤å¤–,v4l2_subscribed_event 结构体内部有å¯ä¾›é©±åŠ¨è®¾ç½®çš„ merge() å’Œ +replace() 回调,这些回调会在新事件产生且没有多余空间的时候被调用。 +replace() å›žè°ƒè®©ä½ å¯ä»¥å°†æ—©æœŸäº‹ä»¶çš„å‡€è·æ›¿æ¢ä¸ºæ–°äº‹ä»¶çš„净è·ï¼Œå°†æ—©æœŸ +净è·çš„相关数æ®åˆå¹¶åˆ°æ›¿æ¢è¿›æ¥çš„æ–°å‡€è·ä¸ã€‚当该类型的事件仅分é…了一个 +kevent 结构体时,它将被调用。merge() å›žè°ƒè®©ä½ å¯ä»¥åˆå¹¶æœ€æ—©çš„äº‹ä»¶å‡€è· +到在它之åŽçš„那个事件净è·ä¸ã€‚当该类型的事件分é…了两个或更多 kevent +结构体时,它将被调用。 + +è¿™ç§æ–¹æ³•ä¸ä¼šæœ‰çжæ€ä¿¡æ¯ä¸¢å¤±ï¼Œåªä¼šå¯¼è‡´ä¸é—´æ¥éª¤ä¿¡æ¯ä¸¢å¤±ã€‚ + + +关于 replace/merge 回调的一个ä¸é”™çš„例å在 v4l2-event.c ä¸ï¼šç”¨äºŽ +控制事件的 ctrls_replace() å’Œ ctrls_merge() 回调。 + +注æ„:这些回调å¯ä»¥åœ¨ä¸æ–上下文ä¸è°ƒç”¨ï¼Œæ‰€ä»¥å®ƒä»¬å¿…须尽快完æˆå¹¶é€€å‡ºã€‚ + +有用的函数: + +void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev) + + å°†äº‹ä»¶åŠ å…¥è§†é¢‘è®¾å¤‡çš„é˜Ÿåˆ—ã€‚é©±åŠ¨ä»…è´Ÿè´£å¡«å…… type å’Œ data 域。 + 其他域由 V4L2 填充。 + +int v4l2_event_subscribe(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub, unsigned elems, + const struct v4l2_subscribed_event_ops *ops) + + video_device->ioctl_ops->vidioc_subscribe_event 必须检测驱动能 + 产生特定 id 的事件。然åŽè°ƒç”¨ v4l2_event_subscribe() æ¥è®¢é˜…该事件。 + + elems 傿•°æ˜¯è¯¥äº‹ä»¶çš„队列大å°ã€‚若为 0,V4L2 æ¡†æž¶å°†ä¼šï¼ˆæ ¹æ®äº‹ä»¶ç±»åž‹ï¼‰ + 填充默认值。 + + ops 傿•°å…许驱动指定一系列回调: + * add: å½“æ·»åŠ ä¸€ä¸ªæ–°ç›‘å¬è€…时调用(é‡å¤è®¢é˜…åŒä¸€ä¸ªäº‹ä»¶ï¼Œæ¤å›žè°ƒ + 仅被执行一次)。 + * del: 当一个监å¬è€…åœæ¢ç›‘嬿—¶è°ƒç”¨ã€‚ + * replace: 用‘新’事件替æ¢â€˜æ—©æœŸâ€˜äº‹ä»¶ã€‚ + * merge: 将‘早期‘事件åˆå¹¶åˆ°â€˜æ–°â€™äº‹ä»¶ä¸ã€‚ + 这四个调用都是å¯é€‰çš„ï¼Œå¦‚æžœä¸æƒ³æŒ‡å®šä»»ä½•回调,则 ops å¯ä¸º NULL。 + +int v4l2_event_unsubscribe(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) + + v4l2_ioctl_ops 结构体ä¸çš„ vidioc_unsubscribe_event 回调函数。 + 驱动程åºå¯ä»¥ç›´æŽ¥ä½¿ç”¨ v4l2_event_unsubscribe() 实现退订事件过程。 + + 特殊的 V4L2_EVENT_ALL 类型,å¯ç”¨äºŽé€€è®¢æ‰€æœ‰äº‹ä»¶ã€‚驱动å¯èƒ½åœ¨ç‰¹æ®Š + 情况下需è¦å𿤿“作。 + +int v4l2_event_pending(struct v4l2_fh *fh) + + 返回未决事件的数é‡ã€‚有助于实现轮询(poll)æ“作。 + +事件通过 poll ç³»ç»Ÿè°ƒç”¨ä¼ é€’åˆ°ç”¨æˆ·ç©ºé—´ã€‚é©±åŠ¨å¯ç”¨ +v4l2_fh->wait (wait_queue_head_t 类型)ä½œä¸ºå‚æ•°è°ƒç”¨ poll_wait()。 + +äº‹ä»¶åˆ†ä¸ºæ ‡å‡†äº‹ä»¶å’Œç§æœ‰äº‹ä»¶ã€‚æ–°çš„æ ‡å‡†äº‹ä»¶å¿…é¡»ä½¿ç”¨å¯ç”¨çš„æœ€å°äº‹ä»¶ç±»åž‹ +ç¼–å·ã€‚驱动必须从他们本类型的编å·èµ·å§‹å¤„分é…事件。类型的编å·èµ·å§‹ä¸º +V4L2_EVENT_PRIVATE_START + n * 1000 ï¼Œå…¶ä¸ n 为å¯ç”¨æœ€å°ç¼–å·ã€‚æ¯ä¸ª +类型ä¸çš„ç¬¬ä¸€ä¸ªäº‹ä»¶ç±»åž‹ç¼–å·æ˜¯ä¸ºä»¥åŽçš„使用ä¿ç•™çš„,所以第一个å¯ç”¨äº‹ä»¶ +ç±»åž‹ç¼–å·æ˜¯â€˜class base + 1’。 + +V4L2 事件机制的使用实例å¯ä»¥åœ¨ OMAP3 ISP 的驱动 +(drivers/media/video/omap3isp)䏿‰¾åˆ°ã€‚ diff --git a/MAINTAINERS b/MAINTAINERS index c98b1a1520d82735a7e2e95a83542f9c22c41cd2..0318e6264346e120376ead48fbb9923e93e41e6f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2865,7 +2865,9 @@ F: include/linux/firewire*.h F: tools/firewire/ FIRMWARE LOADER (request_firmware) -S: Orphan +M: Ming Lei <ming.lei@canonical.com> +L: linux-kernel@vger.kernel.org +S: Maintained F: Documentation/firmware_class/ F: drivers/base/firmware*.c F: include/linux/firmware.h diff --git a/arch/tile/Kconfig b/arch/tile/Kconfig index 932e4430f7f3323ef9be06d2f4b1f8ac98ae611f..c9a3c1fe72970347c6d36973d40c63b03ae5e4f1 100644 --- a/arch/tile/Kconfig +++ b/arch/tile/Kconfig @@ -412,14 +412,6 @@ config TILE_USB config NEED_BOUNCE_POOL def_bool USB_OHCI_HCD -config HOTPLUG - bool "Support for hot-pluggable devices" - ---help--- - Say Y here if you want to plug devices into your computer while - the system is running, and be able to use them quickly. In many - cases, the devices can likewise be unplugged at any time too. - One well-known example of this is USB. - source "drivers/pci/hotplug/Kconfig" endmenu diff --git a/arch/xtensa/Kconfig b/arch/xtensa/Kconfig index 8ed64cfae4ff1f46953315f04853ce06831394f5..744f5ee4ba415a289966cba6846152e6311de86a 100644 --- a/arch/xtensa/Kconfig +++ b/arch/xtensa/Kconfig @@ -172,24 +172,6 @@ config CMDLINE source "mm/Kconfig" -config HOTPLUG - bool "Support for hot-pluggable devices" - help - Say Y here if you want to plug devices into your computer while - the system is running, and be able to use them quickly. In many - cases, the devices can likewise be unplugged at any time too. - - One well known example of this is PCMCIA- or PC-cards, credit-card - size devices such as network cards, modems or hard drives which are - plugged into slots found on all modern laptop computers. Another - example, used on modern desktops as well as laptops, is USB. - - Enable HOTPLUG and build a modular kernel. Get agent software - (from <http://linux-hotplug.sourceforge.net/>) and install it. - Then your kernel will automatically call out to a user mode "policy - agent" (/sbin/hotplug) to load modules and set up software needed - to use devices as you hotplug them. - source "drivers/pcmcia/Kconfig" source "drivers/pci/hotplug/Kconfig" diff --git a/drivers/base/core.c b/drivers/base/core.c index 5e6e00bc1652a064530818e5f87416604eba7e25..abea76c36a4b96a10201bb4eeef5c0650e6644ae 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -184,6 +184,17 @@ static void device_release(struct kobject *kobj) struct device *dev = kobj_to_dev(kobj); struct device_private *p = dev->p; + /* + * Some platform devices are driven without driver attached + * and managed resources may have been acquired. Make sure + * all resources are released. + * + * Drivers still can add resources into device after device + * is deleted but alive, so release devres here to avoid + * possible memory leak. + */ + devres_release_all(dev); + if (dev->release) dev->release(dev); else if (dev->type && dev->type->release) @@ -1196,13 +1207,6 @@ void device_del(struct device *dev) bus_remove_device(dev); driver_deferred_probe_del(dev); - /* - * Some platform devices are driven without driver attached - * and managed resources may have been acquired. Make sure - * all resources are released. - */ - devres_release_all(dev); - /* Notify the platform of the removal, in case they * need to do anything... */ @@ -1861,26 +1865,20 @@ void device_shutdown(void) */ #ifdef CONFIG_PRINTK -int __dev_printk(const char *level, const struct device *dev, - struct va_format *vaf) +static int +create_syslog_header(const struct device *dev, char *hdr, size_t hdrlen) { - char dict[128]; - const char *level_extra = ""; - size_t dictlen = 0; const char *subsys; - - if (!dev) - return printk("%s(NULL device *): %pV", level, vaf); + size_t pos = 0; if (dev->class) subsys = dev->class->name; else if (dev->bus) subsys = dev->bus->name; else - goto skip; + return 0; - dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen, - "SUBSYSTEM=%s", subsys); + pos += snprintf(hdr + pos, hdrlen - pos, "SUBSYSTEM=%s", subsys); /* * Add device identifier DEVICE=: @@ -1896,32 +1894,63 @@ int __dev_printk(const char *level, const struct device *dev, c = 'b'; else c = 'c'; - dictlen++; - dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen, - "DEVICE=%c%u:%u", - c, MAJOR(dev->devt), MINOR(dev->devt)); + pos++; + pos += snprintf(hdr + pos, hdrlen - pos, + "DEVICE=%c%u:%u", + c, MAJOR(dev->devt), MINOR(dev->devt)); } else if (strcmp(subsys, "net") == 0) { struct net_device *net = to_net_dev(dev); - dictlen++; - dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen, - "DEVICE=n%u", net->ifindex); + pos++; + pos += snprintf(hdr + pos, hdrlen - pos, + "DEVICE=n%u", net->ifindex); } else { - dictlen++; - dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen, - "DEVICE=+%s:%s", subsys, dev_name(dev)); + pos++; + pos += snprintf(hdr + pos, hdrlen - pos, + "DEVICE=+%s:%s", subsys, dev_name(dev)); } -skip: - if (level[2]) - level_extra = &level[2]; /* skip past KERN_SOH "L" */ - return printk_emit(0, level[1] - '0', - dictlen ? dict : NULL, dictlen, - "%s %s: %s%pV", - dev_driver_string(dev), dev_name(dev), - level_extra, vaf); + return pos; +} +EXPORT_SYMBOL(create_syslog_header); + +int dev_vprintk_emit(int level, const struct device *dev, + const char *fmt, va_list args) +{ + char hdr[128]; + size_t hdrlen; + + hdrlen = create_syslog_header(dev, hdr, sizeof(hdr)); + + return vprintk_emit(0, level, hdrlen ? hdr : NULL, hdrlen, fmt, args); +} +EXPORT_SYMBOL(dev_vprintk_emit); + +int dev_printk_emit(int level, const struct device *dev, const char *fmt, ...) +{ + va_list args; + int r; + + va_start(args, fmt); + + r = dev_vprintk_emit(level, dev, fmt, args); + + va_end(args); + + return r; +} +EXPORT_SYMBOL(dev_printk_emit); + +static int __dev_printk(const char *level, const struct device *dev, + struct va_format *vaf) +{ + if (!dev) + return printk("%s(NULL device *): %pV", level, vaf); + + return dev_printk_emit(level[1] - '0', dev, + "%s %s: %pV", + dev_driver_string(dev), dev_name(dev), vaf); } -EXPORT_SYMBOL(__dev_printk); int dev_printk(const char *level, const struct device *dev, const char *fmt, ...) @@ -1936,6 +1965,7 @@ int dev_printk(const char *level, const struct device *dev, vaf.va = &args; r = __dev_printk(level, dev, &vaf); + va_end(args); return r; @@ -1955,6 +1985,7 @@ int func(const struct device *dev, const char *fmt, ...) \ vaf.va = &args; \ \ r = __dev_printk(kern_level, dev, &vaf); \ + \ va_end(args); \ \ return r; \ diff --git a/drivers/base/devres.c b/drivers/base/devres.c index 2360adb7a58f0b924402169edba8936e1257ee3f..8731979d668a0592cc966191f8af1df9ab155a60 100644 --- a/drivers/base/devres.c +++ b/drivers/base/devres.c @@ -143,6 +143,48 @@ void * devres_alloc(dr_release_t release, size_t size, gfp_t gfp) 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 diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 803cfc1597a9c89c5faf0edd0b76e418eb843d64..6e210802c37b5b4abbeb25657f6850a5faf7e1a4 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -21,6 +21,13 @@ #include <linux/firmware.h> #include <linux/slab.h> #include <linux/sched.h> +#include <linux/list.h> +#include <linux/async.h> +#include <linux/pm.h> +#include <linux/suspend.h> +#include <linux/syscore_ops.h> + +#include "base.h" MODULE_AUTHOR("Manuel Estrada Sainz"); MODULE_DESCRIPTION("Multi purpose firmware loading support"); @@ -85,23 +92,168 @@ 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; struct page **pages; int nr_pages; int page_array_size; + char fw_id[]; +}; + +struct fw_cache_entry { + struct list_head list; + char name[]; +}; + +struct firmware_priv { struct timer_list timeout; - struct device dev; bool nowait; - char fw_id[]; + struct device dev; + struct firmware_buf *buf; + struct firmware *fw; +}; + +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; + int i; + + pr_debug("%s: fw-%s buf=%p data=%p size=%u\n", + __func__, buf->fw_id, buf, buf->data, + (unsigned int)buf->size); + + spin_lock(&fwc->lock); + 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); + kfree(buf); +} + +static void fw_free_buf(struct firmware_buf *buf) +{ + kref_put(&buf->ref, __fw_free_buf); +} + static struct firmware_priv *to_firmware_priv(struct device *dev) { return container_of(dev, struct firmware_priv, dev); @@ -109,9 +261,10 @@ 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); } static ssize_t firmware_timeout_show(struct class *class, @@ -154,11 +307,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); @@ -168,7 +317,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; @@ -189,20 +338,16 @@ 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); } +/* firmware holds the ownership of pages */ 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); - } + WARN_ON(!fw->priv); + fw_free_buf(fw->priv); } /* Some architectures don't have PAGE_KERNEL_RO */ @@ -227,45 +372,33 @@ 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); + complete_all(&fw_buf->completion); break; } /* fallthrough */ @@ -273,7 +406,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,21 +422,21 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *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; @@ -314,11 +446,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; @@ -330,12 +462,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 *), @@ -344,24 +477,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,18 +517,19 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *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; @@ -408,17 +542,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; @@ -445,35 +579,120 @@ 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); + fw_priv->fw = firmware; setup_timer(&fw_priv->timeout, firmware_class_timeout, (u_long) fw_priv); 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; } +/* 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) +{ + fw->priv = buf; + fw->pages = buf->pages; + 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 + +static void _request_firmware_cleanup(const struct firmware **firmware_p) +{ + release_firmware(*firmware_p); + *firmware_p = NULL; +} + 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; + struct firmware_priv *fw_priv = NULL; + struct firmware_buf *buf; + int ret; if (!firmware_p) return ERR_PTR(-EINVAL); @@ -490,18 +709,46 @@ _request_firmware_prepare(const struct firmware **firmware_p, const char *name, return NULL; } - fw_priv = fw_create_instance(firmware, name, device, uevent, nowait); - if (IS_ERR(fw_priv)) { - release_firmware(firmware); + ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf); + if (!ret) + fw_priv = fw_create_instance(firmware, name, device, + uevent, nowait); + + if (IS_ERR(fw_priv) || ret < 0) { + kfree(firmware); *firmware_p = NULL; + return ERR_PTR(-ENOMEM); + } else if (fw_priv) { + fw_priv->buf = buf; + + /* + * bind with 'buf' now to avoid warning in failure path + * of requesting firmware. + */ + firmware->priv = buf; + return fw_priv; } - return fw_priv; -} -static void _request_firmware_cleanup(const struct firmware **firmware_p) -{ - release_firmware(*firmware_p); - *firmware_p = NULL; + /* share the cached buf, which is inprogessing or completed */ + check_status: + mutex_lock(&fw_lock); + if (test_bit(FW_STATUS_ABORT, &buf->status)) { + fw_priv = ERR_PTR(-ENOENT); + firmware->priv = buf; + _request_firmware_cleanup(firmware_p); + goto exit; + } else if (test_bit(FW_STATUS_DONE, &buf->status)) { + fw_priv = NULL; + fw_set_page_data(buf, firmware); + goto exit; + } + mutex_unlock(&fw_lock); + wait_for_completion(&buf->completion); + goto check_status; + +exit: + mutex_unlock(&fw_lock); + return fw_priv; } static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, @@ -509,6 +756,8 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, { int retval = 0; struct device *f_dev = &fw_priv->dev; + struct firmware_buf *buf = fw_priv->buf; + struct firmware_cache *fwc = &fw_cache; dev_set_uevent_suppress(f_dev, true); @@ -535,7 +784,7 @@ 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)); @@ -543,15 +792,40 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, 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); mutex_lock(&fw_lock); - if (!fw_priv->fw->size || test_bit(FW_STATUS_ABORT, &fw_priv->status)) + if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status)) retval = -ENOENT; - fw_priv->fw = NULL; + + /* + * add firmware name into devres list so that we can auto cache + * and uncache firmware for device. + * + * f_dev->parent may has been deleted already, but the problem + * should be fixed in devres or driver core. + */ + 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. + */ + if (!retval && 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_priv->fw); + + fw_priv->buf = NULL; mutex_unlock(&fw_lock); device_remove_file(f_dev, &dev_attr_loading); @@ -578,6 +852,8 @@ 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. **/ int request_firmware(const struct firmware **firmware_p, const char *name, @@ -659,6 +935,7 @@ static void request_firmware_work_func(struct work_struct *work) out: fw_work->cont(fw, fw_work->context); + put_device(fw_work->device); module_put(fw_work->module); kfree(fw_work); @@ -677,9 +954,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( @@ -705,18 +988,363 @@ 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_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); + list_for_each_entry(fce, &fwc->fw_names, list) { + if (!strcmp(fce->name, 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); + fwc->cnt++; + list_add(&fce->list, &fwc->fw_names); + spin_unlock(&fwc->name_lock); + + 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__); + + /* + * 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(); return class_register(&firmware_class); } 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 class_unregister(&firmware_class); } @@ -726,3 +1354,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/platform.c b/drivers/base/platform.c index a1a72250258705eb059b6601b086cf9d296b1d1b..ddeca142293ccbe378f5b5c207465f4e1f5e3056 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -20,9 +20,13 @@ #include <linux/err.h> #include <linux/slab.h> #include <linux/pm_runtime.h> +#include <linux/idr.h> #include "base.h" +/* For automatically allocated device IDs */ +static DEFINE_IDA(platform_devid_ida); + #define to_platform_driver(drv) (container_of((drv), struct platform_driver, \ driver)) @@ -99,6 +103,9 @@ struct resource *platform_get_resource_byname(struct platform_device *dev, for (i = 0; i < dev->num_resources; i++) { struct resource *r = &dev->resource[i]; + if (unlikely(!r->name)) + continue; + if (type == resource_type(r) && !strcmp(r->name, name)) return r; } @@ -263,7 +270,7 @@ EXPORT_SYMBOL_GPL(platform_device_add_data); */ int platform_device_add(struct platform_device *pdev) { - int i, ret = 0; + int i, ret; if (!pdev) return -EINVAL; @@ -273,10 +280,27 @@ int platform_device_add(struct platform_device *pdev) pdev->dev.bus = &platform_bus_type; - if (pdev->id != -1) + switch (pdev->id) { + default: dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); - else + break; + case PLATFORM_DEVID_NONE: dev_set_name(&pdev->dev, "%s", pdev->name); + break; + case PLATFORM_DEVID_AUTO: + /* + * Automatically allocated device ID. We mark it as such so + * that we remember it must be freed, and we append a suffix + * to avoid namespace collision with explicit IDs. + */ + ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL); + if (ret < 0) + goto err_out; + pdev->id = ret; + pdev->id_auto = true; + dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id); + break; + } for (i = 0; i < pdev->num_resources; i++) { struct resource *p, *r = &pdev->resource[i]; @@ -309,6 +333,11 @@ int platform_device_add(struct platform_device *pdev) return ret; failed: + if (pdev->id_auto) { + ida_simple_remove(&platform_devid_ida, pdev->id); + pdev->id = PLATFORM_DEVID_AUTO; + } + while (--i >= 0) { struct resource *r = &pdev->resource[i]; unsigned long type = resource_type(r); @@ -317,6 +346,7 @@ int platform_device_add(struct platform_device *pdev) release_resource(r); } + err_out: return ret; } EXPORT_SYMBOL_GPL(platform_device_add); @@ -336,6 +366,11 @@ void platform_device_del(struct platform_device *pdev) if (pdev) { device_del(&pdev->dev); + if (pdev->id_auto) { + ida_simple_remove(&platform_devid_ida, pdev->id); + pdev->id = PLATFORM_DEVID_AUTO; + } + for (i = 0; i < pdev->num_resources; i++) { struct resource *r = &pdev->resource[i]; unsigned long type = resource_type(r); diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 0113adc310dccc4a62a325cccde4221f34c5ce7e..b0b072a88f5fdaf8447ae6fb9133a613be8fac06 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -1324,3 +1324,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/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index e175c8ed4ec47667fd9f09514ed2801b51e2c21d..07122a9ef36e6592812aec1f97b3f830914be361 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -21,6 +21,12 @@ config EXTCON_GPIO Say Y here to enable GPIO based extcon support. Note that GPIO extcon supports single state per extcon instance. +config EXTCON_ADC_JACK + tristate "ADC Jack extcon support" + depends on IIO + help + Say Y here to enable extcon device driver based on ADC values. + config EXTCON_MAX77693 tristate "MAX77693 EXTCON Support" depends on MFD_MAX77693 @@ -41,7 +47,7 @@ config EXTCON_MAX8997 config EXTCON_ARIZONA tristate "Wolfson Arizona EXTCON support" - depends on MFD_ARIZONA + depends on MFD_ARIZONA && INPUT help Say Y here to enable support for external accessory detection with Wolfson Arizona devices. These are audio CODECs with diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 88961b33234818bbd29356140d17558f37c347ec..f98a3c4d46e0a7d76125de265ee34b0bea049bd4 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -2,8 +2,9 @@ # Makefile for external connector class (extcon) devices # -obj-$(CONFIG_EXTCON) += extcon_class.o -obj-$(CONFIG_EXTCON_GPIO) += extcon_gpio.o +obj-$(CONFIG_EXTCON) += extcon-class.o +obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o +obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c new file mode 100644 index 0000000000000000000000000000000000000000..60ac3fbb4cde7ef1163105ef7de2335012ced5ea --- /dev/null +++ b/drivers/extcon/extcon-adc-jack.c @@ -0,0 +1,198 @@ +/* + * drivers/extcon/extcon-adc-jack.c + * + * Analog Jack extcon driver with ADC-based detection capability. + * + * Copyright (C) 2012 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * Modified for calling to IIO to get adc by <anish.singh@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/iio/consumer.h> +#include <linux/extcon/extcon-adc-jack.h> +#include <linux/extcon.h> + +/** + * struct adc_jack_data - internal data for adc_jack device driver + * @edev - extcon device. + * @cable_names - list of supported cables. + * @num_cables - size of cable_names. + * @adc_conditions - list of adc value conditions. + * @num_conditions - size of adc_conditions. + * @irq - irq number of attach/detach event (0 if not exist). + * @handling_delay - interrupt handler will schedule extcon event + * handling at handling_delay jiffies. + * @handler - extcon event handler called by interrupt handler. + * @chan - iio channel being queried. + */ +struct adc_jack_data { + struct extcon_dev edev; + + const char **cable_names; + int num_cables; + struct adc_jack_cond *adc_conditions; + int num_conditions; + + int irq; + unsigned long handling_delay; /* in jiffies */ + struct delayed_work handler; + + struct iio_channel *chan; +}; + +static void adc_jack_handler(struct work_struct *work) +{ + struct adc_jack_data *data = container_of(to_delayed_work(work), + struct adc_jack_data, + handler); + u32 state = 0; + int ret, adc_val; + int i; + + ret = iio_read_channel_raw(data->chan, &adc_val); + if (ret < 0) { + dev_err(data->edev.dev, "read channel() error: %d\n", ret); + return; + } + + /* Get state from adc value with adc_conditions */ + for (i = 0; i < data->num_conditions; i++) { + struct adc_jack_cond *def = &data->adc_conditions[i]; + if (!def->state) + break; + if (def->min_adc <= adc_val && def->max_adc >= adc_val) { + state = def->state; + break; + } + } + /* if no def has met, it means state = 0 (no cables attached) */ + + extcon_set_state(&data->edev, state); +} + +static irqreturn_t adc_jack_irq_thread(int irq, void *_data) +{ + struct adc_jack_data *data = _data; + + schedule_delayed_work(&data->handler, data->handling_delay); + return IRQ_HANDLED; +} + +static int __devinit adc_jack_probe(struct platform_device *pdev) +{ + struct adc_jack_data *data; + struct adc_jack_pdata *pdata = pdev->dev.platform_data; + int i, err = 0; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->edev.name = pdata->name; + + if (!pdata->cable_names) { + err = -EINVAL; + dev_err(&pdev->dev, "error: cable_names not defined.\n"); + goto out; + } + + data->edev.supported_cable = pdata->cable_names; + + /* Check the length of array and set num_cables */ + for (i = 0; data->edev.supported_cable[i]; i++) + ; + if (i == 0 || i > SUPPORTED_CABLE_MAX) { + err = -EINVAL; + dev_err(&pdev->dev, "error: pdata->cable_names size = %d\n", + i - 1); + goto out; + } + data->num_cables = i; + + if (!pdata->adc_conditions || + !pdata->adc_conditions[0].state) { + err = -EINVAL; + dev_err(&pdev->dev, "error: adc_conditions not defined.\n"); + goto out; + } + data->adc_conditions = pdata->adc_conditions; + + /* Check the length of array and set num_conditions */ + for (i = 0; data->adc_conditions[i].state; i++) + ; + data->num_conditions = i; + + data->chan = iio_channel_get(dev_name(&pdev->dev), + pdata->consumer_channel); + if (IS_ERR(data->chan)) { + err = PTR_ERR(data->chan); + goto out; + } + + data->handling_delay = msecs_to_jiffies(pdata->handling_delay_ms); + + INIT_DELAYED_WORK_DEFERRABLE(&data->handler, adc_jack_handler); + + platform_set_drvdata(pdev, data); + + err = extcon_dev_register(&data->edev, &pdev->dev); + if (err) + goto out; + + data->irq = platform_get_irq(pdev, 0); + if (!data->irq) { + dev_err(&pdev->dev, "platform_get_irq failed\n"); + err = -ENODEV; + goto err_irq; + } + + err = request_any_context_irq(data->irq, adc_jack_irq_thread, + pdata->irq_flags, pdata->name, data); + + if (err) { + dev_err(&pdev->dev, "error: irq %d\n", data->irq); + err = -EINVAL; + goto err_irq; + } + + goto out; + +err_irq: + extcon_dev_unregister(&data->edev); +out: + return err; +} + +static int __devexit adc_jack_remove(struct platform_device *pdev) +{ + struct adc_jack_data *data = platform_get_drvdata(pdev); + + free_irq(data->irq, data); + cancel_work_sync(&data->handler.work); + extcon_dev_unregister(&data->edev); + + return 0; +} + +static struct platform_driver adc_jack_driver = { + .probe = adc_jack_probe, + .remove = __devexit_p(adc_jack_remove), + .driver = { + .name = "adc-jack", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(adc_jack_driver); diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 6c19833ed2d06033e9b683ad602dd6c62905bac6..cdab9e598297b8102fcb18df0984e26b49140685 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -21,6 +21,7 @@ #include <linux/interrupt.h> #include <linux/err.h> #include <linux/gpio.h> +#include <linux/input.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> @@ -30,11 +31,14 @@ #include <linux/mfd/arizona/pdata.h> #include <linux/mfd/arizona/registers.h> +#define ARIZONA_NUM_BUTTONS 6 + struct arizona_extcon_info { struct device *dev; struct arizona *arizona; struct mutex lock; struct regulator *micvdd; + struct input_dev *input; int micd_mode; const struct arizona_micd_config *micd_modes; @@ -54,6 +58,18 @@ static const struct arizona_micd_config micd_default_modes[] = { { 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 }, }; +static struct { + u16 status; + int report; +} arizona_lvl_to_key[ARIZONA_NUM_BUTTONS] = { + { 0x1, BTN_0 }, + { 0x2, BTN_1 }, + { 0x4, BTN_2 }, + { 0x8, BTN_3 }, + { 0x10, BTN_4 }, + { 0x20, BTN_5 }, +}; + #define ARIZONA_CABLE_MECHANICAL 0 #define ARIZONA_CABLE_MICROPHONE 1 #define ARIZONA_CABLE_HEADPHONE 2 @@ -133,6 +149,7 @@ static void arizona_stop_mic(struct arizona_extcon_info *info) if (change) { regulator_disable(info->micvdd); + pm_runtime_mark_last_busy(info->dev); pm_runtime_put_autosuspend(info->dev); } } @@ -141,8 +158,8 @@ static irqreturn_t arizona_micdet(int irq, void *data) { struct arizona_extcon_info *info = data; struct arizona *arizona = info->arizona; - unsigned int val; - int ret; + unsigned int val, lvl; + int ret, i; mutex_lock(&info->lock); @@ -219,13 +236,22 @@ static irqreturn_t arizona_micdet(int irq, void *data) /* * If we're still detecting and we detect a short then we've - * got a headphone. Otherwise it's a button press, the - * button reporting is stubbed out for now. + * got a headphone. Otherwise it's a button press. */ if (val & 0x3fc) { if (info->mic) { dev_dbg(arizona->dev, "Mic button detected\n"); + lvl = val & ARIZONA_MICD_LVL_MASK; + lvl >>= ARIZONA_MICD_LVL_SHIFT; + + for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) + if (lvl & arizona_lvl_to_key[i].status) + input_report_key(info->input, + arizona_lvl_to_key[i].report, + 1); + input_sync(info->input); + } else if (info->detecting) { dev_dbg(arizona->dev, "Headphone detected\n"); info->detecting = false; @@ -244,6 +270,10 @@ static irqreturn_t arizona_micdet(int irq, void *data) } } else { dev_dbg(arizona->dev, "Mic button released\n"); + for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) + input_report_key(info->input, + arizona_lvl_to_key[i].report, 0); + input_sync(info->input); } handled: @@ -258,7 +288,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) struct arizona_extcon_info *info = data; struct arizona *arizona = info->arizona; unsigned int val; - int ret; + int ret, i; pm_runtime_get_sync(info->dev); @@ -288,6 +318,11 @@ static irqreturn_t arizona_jackdet(int irq, void *data) arizona_stop_mic(info); + for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) + input_report_key(info->input, + arizona_lvl_to_key[i].report, 0); + input_sync(info->input); + ret = extcon_update_state(&info->edev, 0xffffffff, 0); if (ret != 0) dev_err(arizona->dev, "Removal report failed: %d\n", @@ -307,13 +342,13 @@ static int __devinit arizona_extcon_probe(struct platform_device *pdev) struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); struct arizona_pdata *pdata; struct arizona_extcon_info *info; - int ret, mode; + int ret, mode, i; pdata = dev_get_platdata(arizona->dev); info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); if (!info) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + dev_err(&pdev->dev, "Failed to allocate memory\n"); ret = -ENOMEM; goto err; } @@ -350,7 +385,7 @@ static int __devinit arizona_extcon_probe(struct platform_device *pdev) ret = extcon_dev_register(&info->edev, arizona->dev); if (ret < 0) { - dev_err(arizona->dev, "extcon_dev_regster() failed: %d\n", + dev_err(arizona->dev, "extcon_dev_register() failed: %d\n", ret); goto err; } @@ -382,6 +417,20 @@ static int __devinit arizona_extcon_probe(struct platform_device *pdev) arizona_extcon_set_mode(info, 0); + info->input = input_allocate_device(); + if (!info->input) { + dev_err(arizona->dev, "Can't allocate input dev\n"); + ret = -ENOMEM; + goto err_register; + } + + for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) + input_set_capability(info->input, EV_KEY, + arizona_lvl_to_key[i].report); + info->input->name = "Headset"; + info->input->phys = "arizona/extcon"; + info->input->dev.parent = &pdev->dev; + pm_runtime_enable(&pdev->dev); pm_runtime_idle(&pdev->dev); pm_runtime_get_sync(&pdev->dev); @@ -391,7 +440,7 @@ static int __devinit arizona_extcon_probe(struct platform_device *pdev) if (ret != 0) { dev_err(&pdev->dev, "Failed to get JACKDET rise IRQ: %d\n", ret); - goto err_register; + goto err_input; } ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 1); @@ -441,8 +490,16 @@ static int __devinit arizona_extcon_probe(struct platform_device *pdev) pm_runtime_put(&pdev->dev); + ret = input_register_device(info->input); + if (ret) { + dev_err(&pdev->dev, "Can't register input device: %d\n", ret); + goto err_micdet; + } + return 0; +err_micdet: + arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); err_fall_wake: arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0); err_fall: @@ -451,6 +508,8 @@ err_rise_wake: arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0); err_rise: arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info); +err_input: + input_free_device(info->input); err_register: pm_runtime_disable(&pdev->dev); extcon_dev_unregister(&info->edev); @@ -473,6 +532,7 @@ static int __devexit arizona_extcon_remove(struct platform_device *pdev) regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, ARIZONA_JD1_ENA, 0); arizona_clk32k_disable(arizona); + input_unregister_device(info->input); extcon_dev_unregister(&info->edev); return 0; diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon-class.c similarity index 98% rename from drivers/extcon/extcon_class.c rename to drivers/extcon/extcon-class.c index f6419f9db76c103c29a981129abdfa08be8110b8..946a3188b2b78a4014f84e93588e8423c41501c0 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon-class.c @@ -30,6 +30,7 @@ #include <linux/err.h> #include <linux/extcon.h> #include <linux/slab.h> +#include <linux/sysfs.h> /* * extcon_cable_name suggests the standard cable names for commonly used @@ -442,7 +443,7 @@ static int _call_per_cable(struct notifier_block *nb, unsigned long val, /** * extcon_register_interest() - Register a notifier for a state change of a - * specific cable, not a entier set of cables of a + * specific cable, not an entier set of cables of a * extcon device. * @obj: an empty extcon_specific_cable_nb object to be returned. * @extcon_name: the name of extcon device. @@ -498,7 +499,7 @@ int extcon_unregister_interest(struct extcon_specific_cable_nb *obj) } /** - * extcon_register_notifier() - Register a notifee to get notified by + * extcon_register_notifier() - Register a notifiee to get notified by * any attach status changes from the extcon. * @edev: the extcon device. * @nb: a notifier block to be registered. @@ -515,7 +516,7 @@ int extcon_register_notifier(struct extcon_dev *edev, EXPORT_SYMBOL_GPL(extcon_register_notifier); /** - * extcon_unregister_notifier() - Unregister a notifee from the extcon device. + * extcon_unregister_notifier() - Unregister a notifiee from the extcon device. * @edev: the extcon device. * @nb: a registered notifier block to be unregistered. */ @@ -673,10 +674,12 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) cable->attr_g.name = str; cable->attr_g.attrs = cable->attrs; + sysfs_attr_init(&cable->attr_name.attr); cable->attr_name.attr.name = "name"; cable->attr_name.attr.mode = 0444; cable->attr_name.show = cable_name_show; + sysfs_attr_init(&cable->attr_state.attr); cable->attr_state.attr.name = "state"; cable->attr_state.attr.mode = 0644; cable->attr_state.show = cable_state_show; @@ -722,6 +725,7 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) goto err_muex; } strcpy(name, buf); + sysfs_attr_init(&edev->d_attrs_muex[index].attr); edev->d_attrs_muex[index].attr.name = name; edev->d_attrs_muex[index].attr.mode = 0000; edev->attrs_muex[index] = &edev->d_attrs_muex[index] @@ -802,7 +806,7 @@ EXPORT_SYMBOL_GPL(extcon_dev_register); /** * extcon_dev_unregister() - Unregister the extcon device. - * @edev: the extcon device instance to be unregitered. + * @edev: the extcon device instance to be unregistered. * * Note that this does not call kfree(edev) because edev was not allocated * by this class. diff --git a/drivers/extcon/extcon_gpio.c b/drivers/extcon/extcon-gpio.c similarity index 100% rename from drivers/extcon/extcon_gpio.c rename to drivers/extcon/extcon-gpio.c diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 38f9e52f358b17596b8b41177d6af493d509f84b..e21387e2da5c25d2c9fdd19dae94108f9e9455da 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -356,7 +356,7 @@ static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info, extcon_set_cable_state(info->edev, "MHL", attached); break; default: - dev_err(info->dev, "faild to detect %s accessory\n", + dev_err(info->dev, "failed to detect %s accessory\n", attached ? "attached" : "detached"); dev_err(info->dev, "- adc:0x%x, adclow:0x%x, adc1k:0x%x\n", adc, adclow, adc1k); @@ -548,7 +548,7 @@ static void max77693_muic_irq_work(struct work_struct *work) curr_adc = info->status[0] & STATUS1_ADC_MASK; curr_adc >>= STATUS1_ADC_SHIFT; - /* Check accossory state which is either detached or attached */ + /* Check accessory state which is either detached or attached */ if (curr_adc == MAX77693_MUIC_ADC_OPEN) attached = false; @@ -564,7 +564,7 @@ static void max77693_muic_irq_work(struct work_struct *work) curr_chg_type = info->status[1] & STATUS2_CHGTYP_MASK; curr_chg_type >>= STATUS2_CHGTYP_SHIFT; - /* Check charger accossory state which + /* Check charger accessory state which is either detached or attached */ if (curr_chg_type == MAX77693_CHARGER_TYPE_NONE) attached = false; @@ -699,7 +699,7 @@ static int __devinit max77693_muic_probe(struct platform_device *pdev) ret = request_threaded_irq(virq, NULL, max77693_muic_irq_handler, - 0, muic_irq->name, info); + IRQF_ONESHOT, muic_irq->name, info); if (ret) { dev_err(&pdev->dev, "failed: irq request (IRQ: %d," diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 47408e802ab6effa670f8b3ba0637a3a75a14b36..d10c9873dd9a8684d7137e49245afc9b40d13d60 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -435,12 +435,23 @@ efivar_attr_read(struct efivar_entry *entry, char *buf) if (status != EFI_SUCCESS) return -EIO; - if (var->Attributes & 0x1) + if (var->Attributes & EFI_VARIABLE_NON_VOLATILE) str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n"); - if (var->Attributes & 0x2) + if (var->Attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS) str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n"); - if (var->Attributes & 0x4) + if (var->Attributes & EFI_VARIABLE_RUNTIME_ACCESS) str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n"); + if (var->Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD) + str += sprintf(str, "EFI_VARIABLE_HARDWARE_ERROR_RECORD\n"); + if (var->Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) + str += sprintf(str, + "EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS\n"); + if (var->Attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) + str += sprintf(str, + "EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS\n"); + if (var->Attributes & EFI_VARIABLE_APPEND_WRITE) + str += sprintf(str, "EFI_VARIABLE_APPEND_WRITE\n"); return str - buf; } diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 86f8885aeb4574fcb8a78c6b56446d5430124e3d..3648f8f0f36886282da6abf3bb2df05a355d3001 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -26,6 +26,7 @@ #include <linux/slab.h> #include <linux/vmalloc.h> #include <linux/hyperv.h> +#include <linux/version.h> #include <asm/hyperv.h> #include "hyperv_vmbus.h" @@ -37,28 +38,6 @@ struct hv_context hv_context = { .signal_event_buffer = NULL, }; -/* - * query_hypervisor_presence - * - Query the cpuid for presence of windows hypervisor - */ -static int query_hypervisor_presence(void) -{ - unsigned int eax; - unsigned int ebx; - unsigned int ecx; - unsigned int edx; - unsigned int op; - - eax = 0; - ebx = 0; - ecx = 0; - edx = 0; - op = HVCPUID_VERSION_FEATURES; - cpuid(op, &eax, &ebx, &ecx, &edx); - - return ecx & HV_PRESENT_BIT; -} - /* * query_hypervisor_info - Get version info of the windows hypervisor */ @@ -159,14 +138,13 @@ int hv_init(void) memset(hv_context.synic_message_page, 0, sizeof(void *) * NR_CPUS); - if (!query_hypervisor_presence()) - goto cleanup; - max_leaf = query_hypervisor_info(); - /* Write our OS info */ - wrmsrl(HV_X64_MSR_GUEST_OS_ID, HV_LINUX_GUEST_ID); - hv_context.guestid = HV_LINUX_GUEST_ID; + /* + * Write our OS ID. + */ + hv_context.guestid = generate_guest_id(0, LINUX_VERSION_CODE, 0); + wrmsrl(HV_X64_MSR_GUEST_OS_ID, hv_context.guestid); /* See if the hypercall page is already set */ rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64); diff --git a/drivers/hv/hv_kvp.c b/drivers/hv/hv_kvp.c index 0012eed6d872b0b01dd313d18107879d99b8e142..ed50e9e83c61a8ec8690c757876a0300096f59d4 100644 --- a/drivers/hv/hv_kvp.c +++ b/drivers/hv/hv_kvp.c @@ -48,13 +48,24 @@ static struct { void *kvp_context; /* for the channel callback */ } kvp_transaction; +/* + * Before we can accept KVP messages from the host, we need + * to handshake with the user level daemon. This state tracks + * if we are in the handshake phase. + */ +static bool in_hand_shake = true; + +/* + * This state maintains the version number registered by the daemon. + */ +static int dm_reg_value; + static void kvp_send_key(struct work_struct *dummy); -#define TIMEOUT_FIRED 1 -static void kvp_respond_to_host(char *key, char *value, int error); +static void kvp_respond_to_host(struct hv_kvp_msg *msg, int error); static void kvp_work_func(struct work_struct *dummy); -static void kvp_register(void); +static void kvp_register(int); static DECLARE_DELAYED_WORK(kvp_work, kvp_work_func); static DECLARE_WORK(kvp_sendkey_work, kvp_send_key); @@ -68,7 +79,7 @@ static u8 *recv_buffer; */ static void -kvp_register(void) +kvp_register(int reg_value) { struct cn_msg *msg; @@ -83,7 +94,7 @@ kvp_register(void) msg->id.idx = CN_KVP_IDX; msg->id.val = CN_KVP_VAL; - kvp_msg->kvp_hdr.operation = KVP_OP_REGISTER; + kvp_msg->kvp_hdr.operation = reg_value; strcpy(version, HV_DRV_VERSION); msg->len = sizeof(struct hv_kvp_msg); cn_netlink_send(msg, 0, GFP_ATOMIC); @@ -97,9 +108,43 @@ kvp_work_func(struct work_struct *dummy) * If the timer fires, the user-mode component has not responded; * process the pending transaction. */ - kvp_respond_to_host("Unknown key", "Guest timed out", TIMEOUT_FIRED); + kvp_respond_to_host(NULL, HV_E_FAIL); +} + +static int kvp_handle_handshake(struct hv_kvp_msg *msg) +{ + int ret = 1; + + switch (msg->kvp_hdr.operation) { + case KVP_OP_REGISTER: + dm_reg_value = KVP_OP_REGISTER; + pr_info("KVP: IP injection functionality not available\n"); + pr_info("KVP: Upgrade the KVP daemon\n"); + break; + case KVP_OP_REGISTER1: + dm_reg_value = KVP_OP_REGISTER1; + break; + default: + pr_info("KVP: incompatible daemon\n"); + pr_info("KVP: KVP version: %d, Daemon version: %d\n", + KVP_OP_REGISTER1, msg->kvp_hdr.operation); + ret = 0; + } + + if (ret) { + /* + * We have a compatible daemon; complete the handshake. + */ + pr_info("KVP: user-mode registering done.\n"); + kvp_register(dm_reg_value); + kvp_transaction.active = false; + if (kvp_transaction.kvp_context) + hv_kvp_onchannelcallback(kvp_transaction.kvp_context); + } + return ret; } + /* * Callback when data is received from user mode. */ @@ -109,29 +154,165 @@ kvp_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) { struct hv_kvp_msg *message; struct hv_kvp_msg_enumerate *data; + int error = 0; message = (struct hv_kvp_msg *)msg->data; - switch (message->kvp_hdr.operation) { + + /* + * If we are negotiating the version information + * with the daemon; handle that first. + */ + + if (in_hand_shake) { + if (kvp_handle_handshake(message)) + in_hand_shake = false; + return; + } + + /* + * Based on the version of the daemon, we propagate errors from the + * daemon differently. + */ + + data = &message->body.kvp_enum_data; + + switch (dm_reg_value) { case KVP_OP_REGISTER: - pr_info("KVP: user-mode registering done.\n"); - kvp_register(); - kvp_transaction.active = false; - hv_kvp_onchannelcallback(kvp_transaction.kvp_context); + /* + * Null string is used to pass back error condition. + */ + if (data->data.key[0] == 0) + error = HV_S_CONT; break; - default: - data = &message->body.kvp_enum_data; + case KVP_OP_REGISTER1: /* - * Complete the transaction by forwarding the key value - * to the host. But first, cancel the timeout. + * We use the message header information from + * the user level daemon to transmit errors. */ - if (cancel_delayed_work_sync(&kvp_work)) - kvp_respond_to_host(data->data.key, - data->data.value, - !strlen(data->data.key)); + error = message->error; + break; + } + + /* + * Complete the transaction by forwarding the key value + * to the host. But first, cancel the timeout. + */ + if (cancel_delayed_work_sync(&kvp_work)) + kvp_respond_to_host(message, error); +} + + +static int process_ob_ipinfo(void *in_msg, void *out_msg, int op) +{ + struct hv_kvp_msg *in = in_msg; + struct hv_kvp_ip_msg *out = out_msg; + int len; + + switch (op) { + case KVP_OP_GET_IP_INFO: + /* + * Transform all parameters into utf16 encoding. + */ + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.ip_addr, + strlen((char *)in->body.kvp_ip_val.ip_addr), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.ip_addr, + MAX_IP_ADDR_SIZE); + if (len < 0) + return len; + + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.sub_net, + strlen((char *)in->body.kvp_ip_val.sub_net), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.sub_net, + MAX_IP_ADDR_SIZE); + if (len < 0) + return len; + + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.gate_way, + strlen((char *)in->body.kvp_ip_val.gate_way), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.gate_way, + MAX_GATEWAY_SIZE); + if (len < 0) + return len; + + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.dns_addr, + strlen((char *)in->body.kvp_ip_val.dns_addr), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.dns_addr, + MAX_IP_ADDR_SIZE); + if (len < 0) + return len; + + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.adapter_id, + strlen((char *)in->body.kvp_ip_val.adapter_id), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.adapter_id, + MAX_IP_ADDR_SIZE); + if (len < 0) + return len; + + out->kvp_ip_val.dhcp_enabled = + in->body.kvp_ip_val.dhcp_enabled; + out->kvp_ip_val.addr_family = + in->body.kvp_ip_val.addr_family; + } + + return 0; +} + +static void process_ib_ipinfo(void *in_msg, void *out_msg, int op) +{ + struct hv_kvp_ip_msg *in = in_msg; + struct hv_kvp_msg *out = out_msg; + + switch (op) { + case KVP_OP_SET_IP_INFO: + /* + * Transform all parameters into utf8 encoding. + */ + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.ip_addr, + MAX_IP_ADDR_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.ip_addr, + MAX_IP_ADDR_SIZE); + + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.sub_net, + MAX_IP_ADDR_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.sub_net, + MAX_IP_ADDR_SIZE); + + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.gate_way, + MAX_GATEWAY_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.gate_way, + MAX_GATEWAY_SIZE); + + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.dns_addr, + MAX_IP_ADDR_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.dns_addr, + MAX_IP_ADDR_SIZE); + + out->body.kvp_ip_val.dhcp_enabled = in->kvp_ip_val.dhcp_enabled; + + default: + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.adapter_id, + MAX_ADAPTER_ID_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.adapter_id, + MAX_ADAPTER_ID_SIZE); + + out->body.kvp_ip_val.addr_family = in->kvp_ip_val.addr_family; } } + + + static void kvp_send_key(struct work_struct *dummy) { @@ -167,6 +348,12 @@ kvp_send_key(struct work_struct *dummy) */ switch (message->kvp_hdr.operation) { + case KVP_OP_SET_IP_INFO: + process_ib_ipinfo(in_msg, message, KVP_OP_SET_IP_INFO); + break; + case KVP_OP_GET_IP_INFO: + process_ib_ipinfo(in_msg, message, KVP_OP_GET_IP_INFO); + break; case KVP_OP_SET: switch (in_msg->body.kvp_set.data.value_type) { case REG_SZ: @@ -243,17 +430,19 @@ kvp_send_key(struct work_struct *dummy) */ static void -kvp_respond_to_host(char *key, char *value, int error) +kvp_respond_to_host(struct hv_kvp_msg *msg_to_host, int error) { struct hv_kvp_msg *kvp_msg; struct hv_kvp_exchg_msg_value *kvp_data; char *key_name; + char *value; struct icmsg_hdr *icmsghdrp; int keylen = 0; int valuelen = 0; u32 buf_len; struct vmbus_channel *channel; u64 req_id; + int ret; /* * If a transaction is not active; log and return. @@ -287,6 +476,7 @@ kvp_respond_to_host(char *key, char *value, int error) */ return; + icmsghdrp->status = error; /* * If the error parameter is set, terminate the host's enumeration @@ -294,20 +484,27 @@ kvp_respond_to_host(char *key, char *value, int error) */ if (error) { /* - * Something failed or the we have timedout; - * terminate the current host-side iteration. + * Something failed or we have timedout; + * terminate the current host-side iteration. */ - icmsghdrp->status = HV_S_CONT; goto response_done; } - icmsghdrp->status = HV_S_OK; - kvp_msg = (struct hv_kvp_msg *) &recv_buffer[sizeof(struct vmbuspipe_hdr) + sizeof(struct icmsg_hdr)]; switch (kvp_transaction.kvp_msg->kvp_hdr.operation) { + case KVP_OP_GET_IP_INFO: + ret = process_ob_ipinfo(msg_to_host, + (struct hv_kvp_ip_msg *)kvp_msg, + KVP_OP_GET_IP_INFO); + if (ret < 0) + icmsghdrp->status = HV_E_FAIL; + + goto response_done; + case KVP_OP_SET_IP_INFO: + goto response_done; case KVP_OP_GET: kvp_data = &kvp_msg->body.kvp_get.data; goto copy_value; @@ -321,7 +518,7 @@ kvp_respond_to_host(char *key, char *value, int error) } kvp_data = &kvp_msg->body.kvp_enum_data.data; - key_name = key; + key_name = msg_to_host->body.kvp_enum_data.data.key; /* * The windows host expects the key/value pair to be encoded @@ -335,6 +532,7 @@ kvp_respond_to_host(char *key, char *value, int error) kvp_data->key_size = 2*(keylen + 1); /* utf16 encoding */ copy_value: + value = msg_to_host->body.kvp_enum_data.data.value; valuelen = utf8s_to_utf16s(value, strlen(value), UTF16_HOST_ENDIAN, (wchar_t *) kvp_data->value, (HV_KVP_EXCHANGE_MAX_VALUE_SIZE / 2) - 2); @@ -387,7 +585,8 @@ void hv_kvp_onchannelcallback(void *context) return; } - vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE, &recvlen, &requestid); + vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE * 2, &recvlen, + &requestid); if (recvlen > 0) { icmsghdrp = (struct icmsg_hdr *)&recv_buffer[ diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c index d3ac6a40118b30eb1033809c7f9198a6dd5c9b17..a0667de7a04c85de62a63527255c492f01063db8 100644 --- a/drivers/hv/hv_util.c +++ b/drivers/hv/hv_util.c @@ -263,7 +263,7 @@ static int util_probe(struct hv_device *dev, (struct hv_util_service *)dev_id->driver_data; int ret; - srv->recv_buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); + srv->recv_buffer = kmalloc(PAGE_SIZE * 2, GFP_KERNEL); if (!srv->recv_buffer) return -ENOMEM; if (srv->util_init) { @@ -274,7 +274,7 @@ static int util_probe(struct hv_device *dev, } } - ret = vmbus_open(dev->channel, 2 * PAGE_SIZE, 2 * PAGE_SIZE, NULL, 0, + ret = vmbus_open(dev->channel, 4 * PAGE_SIZE, 4 * PAGE_SIZE, NULL, 0, srv->util_cb, dev->channel); if (ret) goto error; diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 0614ff3a7d7e2ece52e0a4e487b60f96c9bc521d..d8d1fadb398aa75e11375fffe8826dbd8781f1fa 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -410,10 +410,49 @@ enum { #define HV_PRESENT_BIT 0x80000000 -#define HV_LINUX_GUEST_ID_LO 0x00000000 -#define HV_LINUX_GUEST_ID_HI 2976579765 -#define HV_LINUX_GUEST_ID (((u64)HV_LINUX_GUEST_ID_HI << 32) | \ - HV_LINUX_GUEST_ID_LO) +/* + * The guest OS needs to register the guest ID with the hypervisor. + * The guest ID is a 64 bit entity and the structure of this ID is + * specified in the Hyper-V specification: + * + * http://msdn.microsoft.com/en-us/library/windows/hardware/ff542653%28v=vs.85%29.aspx + * + * While the current guideline does not specify how Linux guest ID(s) + * need to be generated, our plan is to publish the guidelines for + * Linux and other guest operating systems that currently are hosted + * on Hyper-V. The implementation here conforms to this yet + * unpublished guidelines. + * + * + * Bit(s) + * 63 - Indicates if the OS is Open Source or not; 1 is Open Source + * 62:56 - Os Type; Linux is 0x100 + * 55:48 - Distro specific identification + * 47:16 - Linux kernel version number + * 15:0 - Distro specific identification + * + * + */ + +#define HV_LINUX_VENDOR_ID 0x8100 + +/* + * Generate the guest ID based on the guideline described above. + */ + +static inline __u64 generate_guest_id(__u8 d_info1, __u32 kernel_version, + __u16 d_info2) +{ + __u64 guest_id = 0; + + guest_id = (((__u64)HV_LINUX_VENDOR_ID) << 48); + guest_id |= (((__u64)(d_info1)) << 48); + guest_id |= (((__u64)(kernel_version)) << 16); + guest_id |= ((__u64)(d_info2)); + + return guest_id; +} + #define HV_CPU_POWER_MANAGEMENT (1 << 0) #define HV_RECOMMENDATIONS_MAX 4 diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 4748086eaaf26ad41bd3b2fb0c7e40be4a34e054..8e1a9ec5300328eee7bb22a71c47c42f31ad5aa9 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -34,6 +34,7 @@ #include <linux/completion.h> #include <linux/hyperv.h> #include <asm/hyperv.h> +#include <asm/hypervisor.h> #include "hyperv_vmbus.h" @@ -146,43 +147,9 @@ static ssize_t vmbus_show_device_attr(struct device *dev, get_channel_info(hv_dev, device_info); if (!strcmp(dev_attr->attr.name, "class_id")) { - ret = sprintf(buf, "{%02x%02x%02x%02x-%02x%02x-%02x%02x-" - "%02x%02x%02x%02x%02x%02x%02x%02x}\n", - device_info->chn_type.b[3], - device_info->chn_type.b[2], - device_info->chn_type.b[1], - device_info->chn_type.b[0], - device_info->chn_type.b[5], - device_info->chn_type.b[4], - device_info->chn_type.b[7], - device_info->chn_type.b[6], - device_info->chn_type.b[8], - device_info->chn_type.b[9], - device_info->chn_type.b[10], - device_info->chn_type.b[11], - device_info->chn_type.b[12], - device_info->chn_type.b[13], - device_info->chn_type.b[14], - device_info->chn_type.b[15]); + ret = sprintf(buf, "{%pUl}\n", device_info->chn_type.b); } else if (!strcmp(dev_attr->attr.name, "device_id")) { - ret = sprintf(buf, "{%02x%02x%02x%02x-%02x%02x-%02x%02x-" - "%02x%02x%02x%02x%02x%02x%02x%02x}\n", - device_info->chn_instance.b[3], - device_info->chn_instance.b[2], - device_info->chn_instance.b[1], - device_info->chn_instance.b[0], - device_info->chn_instance.b[5], - device_info->chn_instance.b[4], - device_info->chn_instance.b[7], - device_info->chn_instance.b[6], - device_info->chn_instance.b[8], - device_info->chn_instance.b[9], - device_info->chn_instance.b[10], - device_info->chn_instance.b[11], - device_info->chn_instance.b[12], - device_info->chn_instance.b[13], - device_info->chn_instance.b[14], - device_info->chn_instance.b[15]); + ret = sprintf(buf, "{%pUl}\n", device_info->chn_instance.b); } else if (!strcmp(dev_attr->attr.name, "modalias")) { print_alias_name(hv_dev, alias_name); ret = sprintf(buf, "vmbus:%s\n", alias_name); @@ -757,6 +724,9 @@ static int __init hv_acpi_init(void) { int ret, t; + if (x86_hyper != &x86_hyper_ms_hyperv) + return -ENODEV; + init_completion(&probe_event); /* diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile index 42b3ce9d80fc5352590e0648866f114e9a1a6661..9cce5d70ed5218503f5e58b2310cde9570ae6de7 100644 --- a/drivers/memory/Makefile +++ b/drivers/memory/Makefile @@ -2,6 +2,9 @@ # Makefile for memory devices # +ifeq ($(CONFIG_DDR),y) +obj-$(CONFIG_OF) += of_memory.o +endif obj-$(CONFIG_TI_EMIF) += emif.o obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o diff --git a/drivers/memory/emif.c b/drivers/memory/emif.c index 33a4396b24cbd5d6e3bf9cd18b78814e46fc268e..06d31c99e6ac22e166027a5a3666ca810931581b 100644 --- a/drivers/memory/emif.c +++ b/drivers/memory/emif.c @@ -18,6 +18,7 @@ #include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/slab.h> +#include <linux/of.h> #include <linux/debugfs.h> #include <linux/seq_file.h> #include <linux/module.h> @@ -25,6 +26,7 @@ #include <linux/spinlock.h> #include <memory/jedec_ddr.h> #include "emif.h" +#include "of_memory.h" /** * struct emif_data - Per device static data for driver's use @@ -49,6 +51,7 @@ * frequency in effect at the moment) * @plat_data: Pointer to saved platform data. * @debugfs_root: dentry to the root folder for EMIF in debugfs + * @np_ddr: Pointer to ddr device tree node */ struct emif_data { u8 duplicate; @@ -63,6 +66,7 @@ struct emif_data { struct emif_regs *curr_regs; struct emif_platform_data *plat_data; struct dentry *debugfs_root; + struct device_node *np_ddr; }; static struct emif_data *emif1; @@ -71,6 +75,7 @@ static unsigned long irq_state; static u32 t_ck; /* DDR clock period in ps */ static LIST_HEAD(device_list); +#ifdef CONFIG_DEBUG_FS static void do_emif_regdump_show(struct seq_file *s, struct emif_data *emif, struct emif_regs *regs) { @@ -162,23 +167,23 @@ static int __init_or_module emif_debugfs_init(struct emif_data *emif) int ret; dentry = debugfs_create_dir(dev_name(emif->dev), NULL); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); + if (!dentry) { + ret = -ENOMEM; goto err0; } emif->debugfs_root = dentry; dentry = debugfs_create_file("regcache_dump", S_IRUGO, emif->debugfs_root, emif, &emif_regdump_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); + if (!dentry) { + ret = -ENOMEM; goto err1; } dentry = debugfs_create_file("mr4", S_IRUGO, emif->debugfs_root, emif, &emif_mr4_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); + if (!dentry) { + ret = -ENOMEM; goto err1; } @@ -194,6 +199,16 @@ static void __exit emif_debugfs_exit(struct emif_data *emif) debugfs_remove_recursive(emif->debugfs_root); emif->debugfs_root = NULL; } +#else +static inline int __init_or_module emif_debugfs_init(struct emif_data *emif) +{ + return 0; +} + +static inline void __exit emif_debugfs_exit(struct emif_data *emif) +{ +} +#endif /* * Calculate the period of DDR clock from frequency value @@ -1148,6 +1163,168 @@ static int is_custom_config_valid(struct emif_custom_configs *cust_cfgs, return valid; } +#if defined(CONFIG_OF) +static void __init_or_module of_get_custom_configs(struct device_node *np_emif, + struct emif_data *emif) +{ + struct emif_custom_configs *cust_cfgs = NULL; + int len; + const int *lpmode, *poll_intvl; + + lpmode = of_get_property(np_emif, "low-power-mode", &len); + poll_intvl = of_get_property(np_emif, "temp-alert-poll-interval", &len); + + if (lpmode || poll_intvl) + cust_cfgs = devm_kzalloc(emif->dev, sizeof(*cust_cfgs), + GFP_KERNEL); + + if (!cust_cfgs) + return; + + if (lpmode) { + cust_cfgs->mask |= EMIF_CUSTOM_CONFIG_LPMODE; + cust_cfgs->lpmode = *lpmode; + of_property_read_u32(np_emif, + "low-power-mode-timeout-performance", + &cust_cfgs->lpmode_timeout_performance); + of_property_read_u32(np_emif, + "low-power-mode-timeout-power", + &cust_cfgs->lpmode_timeout_power); + of_property_read_u32(np_emif, + "low-power-mode-freq-threshold", + &cust_cfgs->lpmode_freq_threshold); + } + + if (poll_intvl) { + cust_cfgs->mask |= + EMIF_CUSTOM_CONFIG_TEMP_ALERT_POLL_INTERVAL; + cust_cfgs->temp_alert_poll_interval_ms = *poll_intvl; + } + + if (!is_custom_config_valid(cust_cfgs, emif->dev)) { + devm_kfree(emif->dev, cust_cfgs); + return; + } + + emif->plat_data->custom_configs = cust_cfgs; +} + +static void __init_or_module of_get_ddr_info(struct device_node *np_emif, + struct device_node *np_ddr, + struct ddr_device_info *dev_info) +{ + u32 density = 0, io_width = 0; + int len; + + if (of_find_property(np_emif, "cs1-used", &len)) + dev_info->cs1_used = true; + + if (of_find_property(np_emif, "cal-resistor-per-cs", &len)) + dev_info->cal_resistors_per_cs = true; + + if (of_device_is_compatible(np_ddr , "jedec,lpddr2-s4")) + dev_info->type = DDR_TYPE_LPDDR2_S4; + else if (of_device_is_compatible(np_ddr , "jedec,lpddr2-s2")) + dev_info->type = DDR_TYPE_LPDDR2_S2; + + of_property_read_u32(np_ddr, "density", &density); + of_property_read_u32(np_ddr, "io-width", &io_width); + + /* Convert from density in Mb to the density encoding in jedc_ddr.h */ + if (density & (density - 1)) + dev_info->density = 0; + else + dev_info->density = __fls(density) - 5; + + /* Convert from io_width in bits to io_width encoding in jedc_ddr.h */ + if (io_width & (io_width - 1)) + dev_info->io_width = 0; + else + dev_info->io_width = __fls(io_width) - 1; +} + +static struct emif_data * __init_or_module of_get_memory_device_details( + struct device_node *np_emif, struct device *dev) +{ + struct emif_data *emif = NULL; + struct ddr_device_info *dev_info = NULL; + struct emif_platform_data *pd = NULL; + struct device_node *np_ddr; + int len; + + np_ddr = of_parse_phandle(np_emif, "device-handle", 0); + if (!np_ddr) + goto error; + emif = devm_kzalloc(dev, sizeof(struct emif_data), GFP_KERNEL); + pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); + dev_info = devm_kzalloc(dev, sizeof(*dev_info), GFP_KERNEL); + + if (!emif || !pd || !dev_info) { + dev_err(dev, "%s: Out of memory!!\n", + __func__); + goto error; + } + + emif->plat_data = pd; + pd->device_info = dev_info; + emif->dev = dev; + emif->np_ddr = np_ddr; + emif->temperature_level = SDRAM_TEMP_NOMINAL; + + if (of_device_is_compatible(np_emif, "ti,emif-4d")) + emif->plat_data->ip_rev = EMIF_4D; + else if (of_device_is_compatible(np_emif, "ti,emif-4d5")) + emif->plat_data->ip_rev = EMIF_4D5; + + of_property_read_u32(np_emif, "phy-type", &pd->phy_type); + + if (of_find_property(np_emif, "hw-caps-ll-interface", &len)) + pd->hw_caps |= EMIF_HW_CAPS_LL_INTERFACE; + + of_get_ddr_info(np_emif, np_ddr, dev_info); + if (!is_dev_data_valid(pd->device_info->type, pd->device_info->density, + pd->device_info->io_width, pd->phy_type, pd->ip_rev, + emif->dev)) { + dev_err(dev, "%s: invalid device data!!\n", __func__); + goto error; + } + /* + * For EMIF instances other than EMIF1 see if the devices connected + * are exactly same as on EMIF1(which is typically the case). If so, + * mark it as a duplicate of EMIF1. This will save some memory and + * computation. + */ + if (emif1 && emif1->np_ddr == np_ddr) { + emif->duplicate = true; + goto out; + } else if (emif1) { + dev_warn(emif->dev, "%s: Non-symmetric DDR geometry\n", + __func__); + } + + of_get_custom_configs(np_emif, emif); + emif->plat_data->timings = of_get_ddr_timings(np_ddr, emif->dev, + emif->plat_data->device_info->type, + &emif->plat_data->timings_arr_size); + + emif->plat_data->min_tck = of_get_min_tck(np_ddr, emif->dev); + goto out; + +error: + return NULL; +out: + return emif; +} + +#else + +static struct emif_data * __init_or_module of_get_memory_device_details( + struct device_node *np_emif, struct device *dev) +{ + return NULL; +} +#endif + static struct emif_data *__init_or_module get_device_details( struct platform_device *pdev) { @@ -1267,7 +1444,11 @@ static int __init_or_module emif_probe(struct platform_device *pdev) struct resource *res; int irq; - emif = get_device_details(pdev); + if (pdev->dev.of_node) + emif = of_get_memory_device_details(pdev->dev.of_node, &pdev->dev); + else + emif = get_device_details(pdev); + if (!emif) { pr_err("%s: error getting device data\n", __func__); goto error; @@ -1644,11 +1825,21 @@ static void __attribute__((unused)) freq_post_notify_handling(void) spin_unlock_irqrestore(&emif_lock, irq_state); } +#if defined(CONFIG_OF) +static const struct of_device_id emif_of_match[] = { + { .compatible = "ti,emif-4d" }, + { .compatible = "ti,emif-4d5" }, + {}, +}; +MODULE_DEVICE_TABLE(of, emif_of_match); +#endif + static struct platform_driver emif_driver = { .remove = __exit_p(emif_remove), .shutdown = emif_shutdown, .driver = { .name = "emif", + .of_match_table = of_match_ptr(emif_of_match), }, }; diff --git a/drivers/memory/of_memory.c b/drivers/memory/of_memory.c new file mode 100644 index 0000000000000000000000000000000000000000..60074351f17ecfe8ef0806299deff2fdb5fa70b3 --- /dev/null +++ b/drivers/memory/of_memory.c @@ -0,0 +1,153 @@ +/* + * OpenFirmware helpers for memory drivers + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/of.h> +#include <linux/gfp.h> +#include <memory/jedec_ddr.h> +#include <linux/export.h> + +/** + * of_get_min_tck() - extract min timing values for ddr + * @np: pointer to ddr device tree node + * @device: device requesting for min timing values + * + * Populates the lpddr2_min_tck structure by extracting data + * from device tree node. Returns a pointer to the populated + * structure. If any error in populating the structure, returns + * default min timings provided by JEDEC. + */ +const struct lpddr2_min_tck *of_get_min_tck(struct device_node *np, + struct device *dev) +{ + int ret = 0; + struct lpddr2_min_tck *min; + + min = devm_kzalloc(dev, sizeof(*min), GFP_KERNEL); + if (!min) + goto default_min_tck; + + ret |= of_property_read_u32(np, "tRPab-min-tck", &min->tRPab); + ret |= of_property_read_u32(np, "tRCD-min-tck", &min->tRCD); + ret |= of_property_read_u32(np, "tWR-min-tck", &min->tWR); + ret |= of_property_read_u32(np, "tRASmin-min-tck", &min->tRASmin); + ret |= of_property_read_u32(np, "tRRD-min-tck", &min->tRRD); + ret |= of_property_read_u32(np, "tWTR-min-tck", &min->tWTR); + ret |= of_property_read_u32(np, "tXP-min-tck", &min->tXP); + ret |= of_property_read_u32(np, "tRTP-min-tck", &min->tRTP); + ret |= of_property_read_u32(np, "tCKE-min-tck", &min->tCKE); + ret |= of_property_read_u32(np, "tCKESR-min-tck", &min->tCKESR); + ret |= of_property_read_u32(np, "tFAW-min-tck", &min->tFAW); + + if (ret) { + devm_kfree(dev, min); + goto default_min_tck; + } + + return min; + +default_min_tck: + dev_warn(dev, "%s: using default min-tck values\n", __func__); + return &lpddr2_jedec_min_tck; +} +EXPORT_SYMBOL(of_get_min_tck); + +static int of_do_get_timings(struct device_node *np, + struct lpddr2_timings *tim) +{ + int ret; + + ret = of_property_read_u32(np, "max-freq", &tim->max_freq); + ret |= of_property_read_u32(np, "min-freq", &tim->min_freq); + ret |= of_property_read_u32(np, "tRPab", &tim->tRPab); + ret |= of_property_read_u32(np, "tRCD", &tim->tRCD); + ret |= of_property_read_u32(np, "tWR", &tim->tWR); + ret |= of_property_read_u32(np, "tRAS-min", &tim->tRAS_min); + ret |= of_property_read_u32(np, "tRRD", &tim->tRRD); + ret |= of_property_read_u32(np, "tWTR", &tim->tWTR); + ret |= of_property_read_u32(np, "tXP", &tim->tXP); + ret |= of_property_read_u32(np, "tRTP", &tim->tRTP); + ret |= of_property_read_u32(np, "tCKESR", &tim->tCKESR); + ret |= of_property_read_u32(np, "tDQSCK-max", &tim->tDQSCK_max); + ret |= of_property_read_u32(np, "tFAW", &tim->tFAW); + ret |= of_property_read_u32(np, "tZQCS", &tim->tZQCS); + ret |= of_property_read_u32(np, "tZQCL", &tim->tZQCL); + ret |= of_property_read_u32(np, "tZQinit", &tim->tZQinit); + ret |= of_property_read_u32(np, "tRAS-max-ns", &tim->tRAS_max_ns); + ret |= of_property_read_u32(np, "tDQSCK-max-derated", + &tim->tDQSCK_max_derated); + + return ret; +} + +/** + * of_get_ddr_timings() - extracts the ddr timings and updates no of + * frequencies available. + * @np_ddr: Pointer to ddr device tree node + * @dev: Device requesting for ddr timings + * @device_type: Type of ddr(LPDDR2 S2/S4) + * @nr_frequencies: No of frequencies available for ddr + * (updated by this function) + * + * Populates lpddr2_timings structure by extracting data from device + * tree node. Returns pointer to populated structure. If any error + * while populating, returns default timings provided by JEDEC. + */ +const struct lpddr2_timings *of_get_ddr_timings(struct device_node *np_ddr, + struct device *dev, u32 device_type, u32 *nr_frequencies) +{ + struct lpddr2_timings *timings = NULL; + u32 arr_sz = 0, i = 0; + struct device_node *np_tim; + char *tim_compat; + + switch (device_type) { + case DDR_TYPE_LPDDR2_S2: + case DDR_TYPE_LPDDR2_S4: + tim_compat = "jedec,lpddr2-timings"; + break; + default: + dev_warn(dev, "%s: un-supported memory type\n", __func__); + } + + for_each_child_of_node(np_ddr, np_tim) + if (of_device_is_compatible(np_tim, tim_compat)) + arr_sz++; + + if (arr_sz) + timings = devm_kzalloc(dev, sizeof(*timings) * arr_sz, + GFP_KERNEL); + + if (!timings) + goto default_timings; + + for_each_child_of_node(np_ddr, np_tim) { + if (of_device_is_compatible(np_tim, tim_compat)) { + if (of_do_get_timings(np_tim, &timings[i])) { + devm_kfree(dev, timings); + goto default_timings; + } + i++; + } + } + + *nr_frequencies = arr_sz; + + return timings; + +default_timings: + dev_warn(dev, "%s: using default timings\n", __func__); + *nr_frequencies = ARRAY_SIZE(lpddr2_jedec_timings); + return lpddr2_jedec_timings; +} +EXPORT_SYMBOL(of_get_ddr_timings); diff --git a/drivers/memory/of_memory.h b/drivers/memory/of_memory.h new file mode 100644 index 0000000000000000000000000000000000000000..ef2514f553d3b3a3e22c8cabfe85fb0d1543fdc8 --- /dev/null +++ b/drivers/memory/of_memory.h @@ -0,0 +1,36 @@ +/* + * OpenFirmware helpers for memory drivers + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __LINUX_MEMORY_OF_REG_H +#define __LINUX_MEMORY_OF_REG_H + +#if defined(CONFIG_OF) && defined(CONFIG_DDR) +extern const struct lpddr2_min_tck *of_get_min_tck(struct device_node *np, + struct device *dev); +extern const struct lpddr2_timings + *of_get_ddr_timings(struct device_node *np_ddr, struct device *dev, + u32 device_type, u32 *nr_frequencies); +#else +static inline const struct lpddr2_min_tck + *of_get_min_tck(struct device_node *np, struct device *dev) +{ + return NULL; +} + +static inline const struct lpddr2_timings + *of_get_ddr_timings(struct device_node *np_ddr, struct device *dev, + u32 device_type, u32 *nr_frequencies) +{ + return NULL; +} +#endif /* CONFIG_OF && CONFIG_DDR */ + +#endif /* __LINUX_MEMORY_OF_REG_ */ diff --git a/drivers/memory/tegra20-mc.c b/drivers/memory/tegra20-mc.c index 3ed49c1c2b91caa5597d0f2dd72698f68ddb4ac8..e6764bb41cb9b5aaf5e46e110d699d8fac3b5047 100644 --- a/drivers/memory/tegra20-mc.c +++ b/drivers/memory/tegra20-mc.c @@ -57,7 +57,7 @@ static inline u32 mc_readl(struct tegra20_mc *mc, u32 offs) if (offs < 0x24) val = readl(mc->regs[0] + offs); - if (offs < 0x400) + else if (offs < 0x400) val = readl(mc->regs[1] + offs - 0x3c); return val; @@ -65,14 +65,10 @@ static inline u32 mc_readl(struct tegra20_mc *mc, u32 offs) static inline void mc_writel(struct tegra20_mc *mc, u32 val, u32 offs) { - if (offs < 0x24) { + if (offs < 0x24) writel(val, mc->regs[0] + offs); - return; - } - if (offs < 0x400) { + else if (offs < 0x400) writel(val, mc->regs[1] + offs - 0x3c); - return; - } } static const char * const tegra20_mc_client[] = { diff --git a/drivers/memory/tegra30-mc.c b/drivers/memory/tegra30-mc.c index e56ff04eb5cc4a10e0ee181c81881bc5c02d09bc..802b9ea431fa41f3831f13a5d6e8c7a95422b54c 100644 --- a/drivers/memory/tegra30-mc.c +++ b/drivers/memory/tegra30-mc.c @@ -95,11 +95,11 @@ static inline u32 mc_readl(struct tegra30_mc *mc, u32 offs) if (offs < 0x10) val = readl(mc->regs[0] + offs); - if (offs < 0x1f0) + else if (offs < 0x1f0) val = readl(mc->regs[1] + offs - 0x3c); - if (offs < 0x228) + else if (offs < 0x228) val = readl(mc->regs[2] + offs - 0x200); - if (offs < 0x400) + else if (offs < 0x400) val = readl(mc->regs[3] + offs - 0x284); return val; @@ -107,22 +107,14 @@ static inline u32 mc_readl(struct tegra30_mc *mc, u32 offs) static inline void mc_writel(struct tegra30_mc *mc, u32 val, u32 offs) { - if (offs < 0x10) { + if (offs < 0x10) writel(val, mc->regs[0] + offs); - return; - } - if (offs < 0x1f0) { + else if (offs < 0x1f0) writel(val, mc->regs[1] + offs - 0x3c); - return; - } - if (offs < 0x228) { + else if (offs < 0x228) writel(val, mc->regs[2] + offs - 0x200); - return; - } - if (offs < 0x400) { + else if (offs < 0x400) writel(val, mc->regs[3] + offs - 0x284); - return; - } } static const char * const tegra30_mc_client[] = { diff --git a/drivers/vme/bridges/vme_ca91cx42.c b/drivers/vme/bridges/vme_ca91cx42.c index e0df92ec44bd09e31b36eac563c0b3b965cde154..1425d22cf956342fe61d522cc0fd27531cb96333 100644 --- a/drivers/vme/bridges/vme_ca91cx42.c +++ b/drivers/vme/bridges/vme_ca91cx42.c @@ -1603,7 +1603,7 @@ static int ca91cx42_probe(struct pci_dev *pdev, const struct pci_device_id *id) { int retval, i; u32 data; - struct list_head *pos = NULL; + struct list_head *pos = NULL, *n; struct vme_bridge *ca91cx42_bridge; struct ca91cx42_driver *ca91cx42_device; struct vme_master_resource *master_image; @@ -1821,28 +1821,28 @@ err_reg: ca91cx42_crcsr_exit(ca91cx42_bridge, pdev); err_lm: /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->lm_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->lm_resources) { lm = list_entry(pos, struct vme_lm_resource, list); list_del(pos); kfree(lm); } err_dma: /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->dma_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->dma_resources) { dma_ctrlr = list_entry(pos, struct vme_dma_resource, list); list_del(pos); kfree(dma_ctrlr); } err_slave: /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->slave_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->slave_resources) { slave_image = list_entry(pos, struct vme_slave_resource, list); list_del(pos); kfree(slave_image); } err_master: /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->master_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->master_resources) { master_image = list_entry(pos, struct vme_master_resource, list); list_del(pos); @@ -1868,7 +1868,7 @@ err_struct: static void ca91cx42_remove(struct pci_dev *pdev) { - struct list_head *pos = NULL; + struct list_head *pos = NULL, *n; struct vme_master_resource *master_image; struct vme_slave_resource *slave_image; struct vme_dma_resource *dma_ctrlr; @@ -1905,28 +1905,28 @@ static void ca91cx42_remove(struct pci_dev *pdev) ca91cx42_crcsr_exit(ca91cx42_bridge, pdev); /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->lm_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->lm_resources) { lm = list_entry(pos, struct vme_lm_resource, list); list_del(pos); kfree(lm); } /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->dma_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->dma_resources) { dma_ctrlr = list_entry(pos, struct vme_dma_resource, list); list_del(pos); kfree(dma_ctrlr); } /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->slave_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->slave_resources) { slave_image = list_entry(pos, struct vme_slave_resource, list); list_del(pos); kfree(slave_image); } /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->master_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->master_resources) { master_image = list_entry(pos, struct vme_master_resource, list); list_del(pos); diff --git a/drivers/vme/bridges/vme_tsi148.c b/drivers/vme/bridges/vme_tsi148.c index 880d9242e3490ed03eb467028c72b5570d3f1cda..5fbd08ffb9c2dfe5365e0fc8b370907b46cb1268 100644 --- a/drivers/vme/bridges/vme_tsi148.c +++ b/drivers/vme/bridges/vme_tsi148.c @@ -2350,7 +2350,7 @@ static int tsi148_probe(struct pci_dev *pdev, const struct pci_device_id *id) { int retval, i, master_num; u32 data; - struct list_head *pos = NULL; + struct list_head *pos = NULL, *n; struct vme_bridge *tsi148_bridge; struct tsi148_driver *tsi148_device; struct vme_master_resource *master_image; @@ -2615,28 +2615,28 @@ err_reg: err_crcsr: err_lm: /* resources are stored in link list */ - list_for_each(pos, &tsi148_bridge->lm_resources) { + list_for_each_safe(pos, n, &tsi148_bridge->lm_resources) { lm = list_entry(pos, struct vme_lm_resource, list); list_del(pos); kfree(lm); } err_dma: /* resources are stored in link list */ - list_for_each(pos, &tsi148_bridge->dma_resources) { + list_for_each_safe(pos, n, &tsi148_bridge->dma_resources) { dma_ctrlr = list_entry(pos, struct vme_dma_resource, list); list_del(pos); kfree(dma_ctrlr); } err_slave: /* resources are stored in link list */ - list_for_each(pos, &tsi148_bridge->slave_resources) { + list_for_each_safe(pos, n, &tsi148_bridge->slave_resources) { slave_image = list_entry(pos, struct vme_slave_resource, list); list_del(pos); kfree(slave_image); } err_master: /* resources are stored in link list */ - list_for_each(pos, &tsi148_bridge->master_resources) { + list_for_each_safe(pos, n, &tsi148_bridge->master_resources) { master_image = list_entry(pos, struct vme_master_resource, list); list_del(pos); diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index 4733eab34a237faa58f57ea14bce96c9659cb2d7..6393fd61d5c4dedc19574cdee6eb136d1340fddb 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -28,7 +28,7 @@ #include <linux/magic.h> #include <linux/slab.h> -#define DEBUGFS_DEFAULT_MODE 0755 +#define DEBUGFS_DEFAULT_MODE 0700 static struct vfsmount *debugfs_mount; static int debugfs_mount_count; @@ -291,9 +291,9 @@ static struct file_system_type debug_fs_type = { .kill_sb = kill_litter_super, }; -struct dentry *__create_file(const char *name, umode_t mode, - struct dentry *parent, void *data, - const struct file_operations *fops) +static struct dentry *__create_file(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops) { struct dentry *dentry = NULL; int error; diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c index a7ac78f8e67a4b458793d13f67f3263e58328b6b..3c9eb5624f5e195d6e2b06908f3b21396ccc8723 100644 --- a/fs/sysfs/symlink.c +++ b/fs/sysfs/symlink.c @@ -113,7 +113,7 @@ int sysfs_create_link(struct kobject *kobj, struct kobject *target, * @target: object we're pointing to. * @name: name of the symlink. * - * This function does the same as sysf_create_link(), but it + * This function does the same as sysfs_create_link(), but it * doesn't warn if the link already exists. */ int sysfs_create_link_nowarn(struct kobject *kobj, struct kobject *target, diff --git a/include/linux/device.h b/include/linux/device.h index 52a5f15a2223ecb916391138ca9cb44f5d5d108a..af92883bb4a6cf3ed01cc4389a54295cc6cc190d 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -536,6 +536,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, @@ -891,12 +895,15 @@ extern const char *dev_driver_string(const struct device *dev); #ifdef CONFIG_PRINTK -extern int __dev_printk(const char *level, const struct device *dev, - struct va_format *vaf); +extern __printf(3, 0) +int dev_vprintk_emit(int level, const struct device *dev, + const char *fmt, va_list args); +extern __printf(3, 4) +int dev_printk_emit(int level, const struct device *dev, const char *fmt, ...); + extern __printf(3, 4) int dev_printk(const char *level, const struct device *dev, - const char *fmt, ...) - ; + const char *fmt, ...); extern __printf(2, 3) int dev_emerg(const struct device *dev, const char *fmt, ...); extern __printf(2, 3) @@ -914,6 +921,14 @@ int _dev_info(const struct device *dev, const char *fmt, ...); #else +static inline __printf(3, 0) +int dev_vprintk_emit(int level, const struct device *dev, + const char *fmt, va_list args) +{ return 0; } +static inline __printf(3, 4) +int dev_printk_emit(int level, const struct device *dev, const char *fmt, ...) +{ return 0; } + static inline int __dev_printk(const char *level, const struct device *dev, struct va_format *vaf) { return 0; } @@ -946,6 +961,32 @@ int _dev_info(const struct device *dev, const char *fmt, ...) #endif +/* + * Stupid hackaround for existing uses of non-printk uses dev_info + * + * Note that the definition of dev_info below is actually _dev_info + * and a macro is used to avoid redefining dev_info + */ + +#define dev_info(dev, fmt, arg...) _dev_info(dev, fmt, ##arg) + +#if defined(CONFIG_DYNAMIC_DEBUG) +#define dev_dbg(dev, format, ...) \ +do { \ + dynamic_dev_dbg(dev, format, ##__VA_ARGS__); \ +} while (0) +#elif defined(DEBUG) +#define dev_dbg(dev, format, arg...) \ + dev_printk(KERN_DEBUG, dev, format, ##arg) +#else +#define dev_dbg(dev, format, arg...) \ +({ \ + if (0) \ + dev_printk(KERN_DEBUG, dev, format, ##arg); \ + 0; \ +}) +#endif + #define dev_level_ratelimited(dev_level, dev, fmt, ...) \ do { \ static DEFINE_RATELIMIT_STATE(_rs, \ @@ -969,33 +1010,21 @@ do { \ dev_level_ratelimited(dev_notice, dev, fmt, ##__VA_ARGS__) #define dev_info_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_info, dev, fmt, ##__VA_ARGS__) +#if defined(CONFIG_DYNAMIC_DEBUG) || defined(DEBUG) #define dev_dbg_ratelimited(dev, fmt, ...) \ - dev_level_ratelimited(dev_dbg, dev, fmt, ##__VA_ARGS__) - -/* - * Stupid hackaround for existing uses of non-printk uses dev_info - * - * Note that the definition of dev_info below is actually _dev_info - * and a macro is used to avoid redefining dev_info - */ - -#define dev_info(dev, fmt, arg...) _dev_info(dev, fmt, ##arg) - -#if defined(CONFIG_DYNAMIC_DEBUG) -#define dev_dbg(dev, format, ...) \ -do { \ - dynamic_dev_dbg(dev, format, ##__VA_ARGS__); \ +do { \ + static DEFINE_RATELIMIT_STATE(_rs, \ + DEFAULT_RATELIMIT_INTERVAL, \ + DEFAULT_RATELIMIT_BURST); \ + DEFINE_DYNAMIC_DEBUG_METADATA(descriptor, fmt); \ + if (unlikely(descriptor.flags & _DPRINTK_FLAGS_PRINT) && \ + __ratelimit(&_rs)) \ + __dynamic_pr_debug(&descriptor, pr_fmt(fmt), \ + ##__VA_ARGS__); \ } while (0) -#elif defined(DEBUG) -#define dev_dbg(dev, format, arg...) \ - dev_printk(KERN_DEBUG, dev, format, ##arg) #else -#define dev_dbg(dev, format, arg...) \ -({ \ - if (0) \ - dev_printk(KERN_DEBUG, dev, format, ##arg); \ - 0; \ -}) +#define dev_dbg_ratelimited(dev, fmt, ...) \ + no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #endif #ifdef VERBOSE_DEBUG diff --git a/include/linux/extcon.h b/include/linux/extcon.h index cdd4014776566bbe395bd635ef8360b2c073cedc..7443a560c9d059d106e7ce18dcf8813b18502c65 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -30,19 +30,19 @@ /* * The standard cable name is to help support general notifier - * and notifee device drivers to share the common names. + * and notifiee device drivers to share the common names. * Please use standard cable names unless your notifier device has * a very unique and abnormal cable or * the cable type is supposed to be used with only one unique - * pair of notifier/notifee devices. + * pair of notifier/notifiee devices. * * Please add any other "standard" cables used with extcon dev. * * You may add a dot and number to specify version or specification * of the specific cable if it is required. (e.g., "Fast-charger.18" * and "Fast-charger.10" for 1.8A and 1.0A chargers) - * However, the notifee and notifier should be able to handle such - * string and if the notifee can negotiate the protocol or idenify, + * However, the notifiee and notifier should be able to handle such + * string and if the notifiee can negotiate the protocol or identify, * you don't need such convention. This convention is helpful when * notifier can distinguish but notifiee cannot. */ @@ -76,7 +76,7 @@ struct extcon_cable; * struct extcon_dev - An extcon device represents one external connector. * @name The name of this extcon device. Parent device name is used * if NULL. - * @supported_cable Array of supported cable name ending with NULL. + * @supported_cable Array of supported cable names ending with NULL. * If supported_cable is NULL, cable name related APIs * are disabled. * @mutually_exclusive Array of mutually exclusive set of cables that cannot @@ -95,7 +95,7 @@ struct extcon_cable; * @state Attach/detach state of this extcon. Do not provide at * register-time * @nh Notifier for the state change events from this extcon - * @entry To support list of extcon devices so that uses can search + * @entry To support list of extcon devices so that users can search * for extcon devices based on the extcon name. * @lock * @max_supported Internal value to store the number of cables. @@ -199,7 +199,7 @@ extern int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state); /* * get/set_cable_state access each bit of the 32b encoded state value. * They are used to access the status of each cable based on the cable_name - * or cable_index, which is retrived by extcon_find_cable_index + * or cable_index, which is retrieved by extcon_find_cable_index */ extern int extcon_find_cable_index(struct extcon_dev *sdev, const char *cable_name); @@ -226,9 +226,9 @@ extern int extcon_unregister_interest(struct extcon_specific_cable_nb *nb); /* * Following APIs are to monitor every action of a notifier. - * Registerer gets notified for every external port of a connection device. + * Registrar gets notified for every external port of a connection device. * Probably this could be used to debug an action of notifier; however, - * we do not recommend to use this at normal 'notifiee' device drivers who + * we do not recommend to use this for normal 'notifiee' device drivers who * want to be notified by a specific external port of the notifier. */ extern int extcon_register_notifier(struct extcon_dev *edev, diff --git a/include/linux/extcon/extcon-adc-jack.h b/include/linux/extcon/extcon-adc-jack.h new file mode 100644 index 0000000000000000000000000000000000000000..20e9eef25d4c2d30e33fe4986e6eeb6276647bae --- /dev/null +++ b/include/linux/extcon/extcon-adc-jack.h @@ -0,0 +1,71 @@ +/* + * include/linux/extcon/extcon-adc-jack.h + * + * Analog Jack extcon driver with ADC-based detection capability. + * + * Copyright (C) 2012 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _EXTCON_ADC_JACK_H_ +#define _EXTCON_ADC_JACK_H_ __FILE__ + +#include <linux/module.h> +#include <linux/extcon.h> + +/** + * struct adc_jack_cond - condition to use an extcon state + * @state - the corresponding extcon state (if 0, this struct denotes + * the last adc_jack_cond element among the array) + * @min_adc - min adc value for this condition + * @max_adc - max adc value for this condition + * + * For example, if { .state = 0x3, .min_adc = 100, .max_adc = 200}, it means + * that if ADC value is between (inclusive) 100 and 200, than the cable 0 and + * 1 are attached (1<<0 | 1<<1 == 0x3) + * + * Note that you don't need to describe condition for "no cable attached" + * because when no adc_jack_cond is met, state = 0 is automatically chosen. + */ +struct adc_jack_cond { + u32 state; /* extcon state value. 0 if invalid */ + u32 min_adc; + u32 max_adc; +}; + +/** + * struct adc_jack_pdata - platform data for adc jack device. + * @name - name of the extcon device. If null, "adc-jack" is used. + * @consumer_channel - Unique name to identify the channel on the consumer + * side. This typically describes the channels used within + * the consumer. E.g. 'battery_voltage' + * @cable_names - array of cable names ending with null. + * @adc_contitions - array of struct adc_jack_cond conditions ending + * with .state = 0 entry. This describes how to decode + * adc values into extcon state. + * @irq_flags - irq flags used for the @irq + * @handling_delay_ms - in some devices, we need to read ADC value some + * milli-seconds after the interrupt occurs. You may + * describe such delays with @handling_delay_ms, which + * is rounded-off by jiffies. + */ +struct adc_jack_pdata { + const char *name; + const char *consumer_channel; + /* + * The last entry should be NULL + */ + const char **cable_names; + /* The last entry's state should be 0 */ + struct adc_jack_cond *adc_conditions; + + unsigned long irq_flags; + unsigned long handling_delay_ms; /* in ms */ +}; + +#endif /* _EXTCON_ADC_JACK_H */ diff --git a/include/linux/firmware.h b/include/linux/firmware.h index 1e7c01189fa698980e4ae1bbb1dbaa75ac8fbafa..e4279fedb93a305023059882cca49da35ca7f420 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/hyperv.h b/include/linux/hyperv.h index 68ed7f7e1fc9182bed0df74e55f065f1e53b4994..e73b852156b11f3c5f145de201f2ecc38fa50937 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -122,12 +122,53 @@ #define REG_U32 4 #define REG_U64 8 +/* + * As we look at expanding the KVP functionality to include + * IP injection functionality, we need to maintain binary + * compatibility with older daemons. + * + * The KVP opcodes are defined by the host and it was unfortunate + * that I chose to treat the registration operation as part of the + * KVP operations defined by the host. + * Here is the level of compatibility + * (between the user level daemon and the kernel KVP driver) that we + * will implement: + * + * An older daemon will always be supported on a newer driver. + * A given user level daemon will require a minimal version of the + * kernel driver. + * If we cannot handle the version differences, we will fail gracefully + * (this can happen when we have a user level daemon that is more + * advanced than the KVP driver. + * + * We will use values used in this handshake for determining if we have + * workable user level daemon and the kernel driver. We begin by taking the + * registration opcode out of the KVP opcode namespace. We will however, + * maintain compatibility with the existing user-level daemon code. + */ + +/* + * Daemon code not supporting IP injection (legacy daemon). + */ + +#define KVP_OP_REGISTER 4 + +/* + * Daemon code supporting IP injection. + * The KVP opcode field is used to communicate the + * registration information; so define a namespace that + * will be distinct from the host defined KVP opcode. + */ + +#define KVP_OP_REGISTER1 100 + enum hv_kvp_exchg_op { KVP_OP_GET = 0, KVP_OP_SET, KVP_OP_DELETE, KVP_OP_ENUMERATE, - KVP_OP_REGISTER, + KVP_OP_GET_IP_INFO, + KVP_OP_SET_IP_INFO, KVP_OP_COUNT /* Number of operations, must be last. */ }; @@ -140,6 +181,39 @@ enum hv_kvp_exchg_pool { KVP_POOL_COUNT /* Number of pools, must be last. */ }; +/* + * Some Hyper-V status codes. + */ + +#define HV_S_OK 0x00000000 +#define HV_E_FAIL 0x80004005 +#define HV_S_CONT 0x80070103 +#define HV_ERROR_NOT_SUPPORTED 0x80070032 +#define HV_ERROR_MACHINE_LOCKED 0x800704F7 +#define HV_ERROR_DEVICE_NOT_CONNECTED 0x8007048F +#define HV_INVALIDARG 0x80070057 +#define HV_GUID_NOTFOUND 0x80041002 + +#define ADDR_FAMILY_NONE 0x00 +#define ADDR_FAMILY_IPV4 0x01 +#define ADDR_FAMILY_IPV6 0x02 + +#define MAX_ADAPTER_ID_SIZE 128 +#define MAX_IP_ADDR_SIZE 1024 +#define MAX_GATEWAY_SIZE 512 + + +struct hv_kvp_ipaddr_value { + __u16 adapter_id[MAX_ADAPTER_ID_SIZE]; + __u8 addr_family; + __u8 dhcp_enabled; + __u16 ip_addr[MAX_IP_ADDR_SIZE]; + __u16 sub_net[MAX_IP_ADDR_SIZE]; + __u16 gate_way[MAX_GATEWAY_SIZE]; + __u16 dns_addr[MAX_IP_ADDR_SIZE]; +} __attribute__((packed)); + + struct hv_kvp_hdr { __u8 operation; __u8 pool; @@ -181,16 +255,26 @@ struct hv_kvp_register { }; struct hv_kvp_msg { - struct hv_kvp_hdr kvp_hdr; + union { + struct hv_kvp_hdr kvp_hdr; + int error; + }; union { struct hv_kvp_msg_get kvp_get; struct hv_kvp_msg_set kvp_set; struct hv_kvp_msg_delete kvp_delete; struct hv_kvp_msg_enumerate kvp_enum_data; + struct hv_kvp_ipaddr_value kvp_ip_val; struct hv_kvp_register kvp_register; } body; } __attribute__((packed)); +struct hv_kvp_ip_msg { + __u8 operation; + __u8 pool; + struct hv_kvp_ipaddr_value kvp_ip_val; +} __attribute__((packed)); + #ifdef __KERNEL__ #include <linux/scatterlist.h> #include <linux/list.h> @@ -405,7 +489,7 @@ struct vmtransfer_page_range { struct vmtransfer_page_packet_header { struct vmpacket_descriptor d; u16 xfer_pageset_id; - bool sender_owns_set; + u8 sender_owns_set; u8 reserved; u32 range_cnt; struct vmtransfer_page_range ranges[1]; @@ -559,7 +643,7 @@ struct vmbus_channel_query_vmbus_version { /* VMBus Version Supported parameters */ struct vmbus_channel_version_supported { struct vmbus_channel_message_header header; - bool version_supported; + u8 version_supported; } __packed; /* Offer Channel parameters */ @@ -568,7 +652,7 @@ struct vmbus_channel_offer_channel { struct vmbus_channel_offer offer; u32 child_relid; u8 monitorid; - bool monitor_allocated; + u8 monitor_allocated; } __packed; /* Rescind Offer parameters */ @@ -704,7 +788,7 @@ struct vmbus_channel_initiate_contact { struct vmbus_channel_version_response { struct vmbus_channel_message_header header; - bool version_supported; + u8 version_supported; } __packed; enum vmbus_channel_state { @@ -977,11 +1061,6 @@ void vmbus_driver_unregister(struct hv_driver *hv_driver); #define ICMSGHDRFLAG_REQUEST 2 #define ICMSGHDRFLAG_RESPONSE 4 -#define HV_S_OK 0x00000000 -#define HV_E_FAIL 0x80004005 -#define HV_S_CONT 0x80070103 -#define HV_ERROR_NOT_SUPPORTED 0x80070032 -#define HV_ERROR_MACHINE_LOCKED 0x800704F7 /* * While we want to handle util services as regular devices, diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 59dc05f382475d67d5d4326c852f3b7d49332e3b..5f49cc0a107e2f75eafbedffd152f03565e16a15 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -2720,9 +2720,6 @@ static inline const char *netdev_name(const struct net_device *dev) return dev->name; } -extern int __netdev_printk(const char *level, const struct net_device *dev, - struct va_format *vaf); - extern __printf(3, 4) int netdev_printk(const char *level, const struct net_device *dev, const char *format, ...); diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h index 60e9994ef4053ef120ff65def32de3c2940a2d9f..5711e9525a2a80b3c405438632ea95b17a5836c9 100644 --- a/include/linux/platform_device.h +++ b/include/linux/platform_device.h @@ -14,11 +14,15 @@ #include <linux/device.h> #include <linux/mod_devicetable.h> +#define PLATFORM_DEVID_NONE (-1) +#define PLATFORM_DEVID_AUTO (-2) + struct mfd_cell; struct platform_device { const char * name; int id; + bool id_auto; struct device dev; u32 num_resources; struct resource * resource; diff --git a/include/linux/pm.h b/include/linux/pm.h index f067e60a38322fa3a935249cc75c185a8bf522af..88f034a23f2c6a04d56c9cc07743d1e7148824b5 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 diff --git a/init/Kconfig b/init/Kconfig index 495e6e9632db33bffaa0767f2eee38a483e0976e..73e4adfa91dca4783e53772c27a3b29e77ef704f 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1265,13 +1265,7 @@ config KALLSYMS_ALL Say N unless you really need all symbols. config HOTPLUG - bool "Support for hot-pluggable devices" if EXPERT - default y - help - This option is provided for the case where no hotplug or uevent - capabilities is wanted by the kernel. You should only consider - disabling this option for embedded systems that do not use modules, a - dynamic /dev tree, or dynamic device discovery. Just say Y. + def_bool y config PRINTK default y diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c index 7ca29a0a3019685291e33a8b3df86669f9378dea..e7f7d993357a57985169ecbfc36ca793798c1a60 100644 --- a/lib/dynamic_debug.c +++ b/lib/dynamic_debug.c @@ -521,25 +521,25 @@ static char *dynamic_emit_prefix(const struct _ddebug *desc, char *buf) int pos_after_tid; int pos = 0; - pos += snprintf(buf + pos, remaining(pos), "%s", KERN_DEBUG); + *buf = '\0'; + if (desc->flags & _DPRINTK_FLAGS_INCL_TID) { if (in_interrupt()) - pos += snprintf(buf + pos, remaining(pos), "%s ", - "<intr>"); + pos += snprintf(buf + pos, remaining(pos), "<intr> "); else pos += snprintf(buf + pos, remaining(pos), "[%d] ", - task_pid_vnr(current)); + task_pid_vnr(current)); } pos_after_tid = pos; if (desc->flags & _DPRINTK_FLAGS_INCL_MODNAME) pos += snprintf(buf + pos, remaining(pos), "%s:", - desc->modname); + desc->modname); if (desc->flags & _DPRINTK_FLAGS_INCL_FUNCNAME) pos += snprintf(buf + pos, remaining(pos), "%s:", - desc->function); + desc->function); if (desc->flags & _DPRINTK_FLAGS_INCL_LINENO) pos += snprintf(buf + pos, remaining(pos), "%d:", - desc->lineno); + desc->lineno); if (pos - pos_after_tid) pos += snprintf(buf + pos, remaining(pos), " "); if (pos >= PREFIX_SIZE) @@ -559,9 +559,13 @@ int __dynamic_pr_debug(struct _ddebug *descriptor, const char *fmt, ...) BUG_ON(!fmt); va_start(args, fmt); + vaf.fmt = fmt; vaf.va = &args; - res = printk("%s%pV", dynamic_emit_prefix(descriptor, buf), &vaf); + + res = printk(KERN_DEBUG "%s%pV", + dynamic_emit_prefix(descriptor, buf), &vaf); + va_end(args); return res; @@ -574,15 +578,26 @@ int __dynamic_dev_dbg(struct _ddebug *descriptor, struct va_format vaf; va_list args; int res; - char buf[PREFIX_SIZE]; BUG_ON(!descriptor); BUG_ON(!fmt); va_start(args, fmt); + vaf.fmt = fmt; vaf.va = &args; - res = __dev_printk(dynamic_emit_prefix(descriptor, buf), dev, &vaf); + + if (!dev) { + res = printk(KERN_DEBUG "(NULL device *): %pV", &vaf); + } else { + char buf[PREFIX_SIZE]; + + res = dev_printk_emit(7, dev, "%s%s %s: %pV", + dynamic_emit_prefix(descriptor, buf), + dev_driver_string(dev), dev_name(dev), + &vaf); + } + va_end(args); return res; @@ -592,20 +607,35 @@ EXPORT_SYMBOL(__dynamic_dev_dbg); #ifdef CONFIG_NET int __dynamic_netdev_dbg(struct _ddebug *descriptor, - const struct net_device *dev, const char *fmt, ...) + const struct net_device *dev, const char *fmt, ...) { struct va_format vaf; va_list args; int res; - char buf[PREFIX_SIZE]; BUG_ON(!descriptor); BUG_ON(!fmt); va_start(args, fmt); + vaf.fmt = fmt; vaf.va = &args; - res = __netdev_printk(dynamic_emit_prefix(descriptor, buf), dev, &vaf); + + if (dev && dev->dev.parent) { + char buf[PREFIX_SIZE]; + + res = dev_printk_emit(7, dev->dev.parent, + "%s%s %s %s: %pV", + dynamic_emit_prefix(descriptor, buf), + dev_driver_string(dev->dev.parent), + dev_name(dev->dev.parent), + netdev_name(dev), &vaf); + } else if (dev) { + res = printk(KERN_DEBUG "%s: %pV", netdev_name(dev), &vaf); + } else { + res = printk(KERN_DEBUG "(NULL net_device): %pV", &vaf); + } + va_end(args); return res; diff --git a/net/core/dev.c b/net/core/dev.c index 89e33a5d4d932c54af74a08cb080809affc7dd67..36c4a0cdb6c128bb0174bbf4302e0e65e9309cbc 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -6424,22 +6424,26 @@ const char *netdev_drivername(const struct net_device *dev) return empty; } -int __netdev_printk(const char *level, const struct net_device *dev, +static int __netdev_printk(const char *level, const struct net_device *dev, struct va_format *vaf) { int r; - if (dev && dev->dev.parent) - r = dev_printk(level, dev->dev.parent, "%s: %pV", - netdev_name(dev), vaf); - else if (dev) + if (dev && dev->dev.parent) { + r = dev_printk_emit(level[1] - '0', + dev->dev.parent, + "%s %s %s: %pV", + dev_driver_string(dev->dev.parent), + dev_name(dev->dev.parent), + netdev_name(dev), vaf); + } else if (dev) { r = printk("%s%s: %pV", level, netdev_name(dev), vaf); - else + } else { r = printk("%s(NULL net_device): %pV", level, vaf); + } return r; } -EXPORT_SYMBOL(__netdev_printk); int netdev_printk(const char *level, const struct net_device *dev, const char *format, ...) @@ -6454,6 +6458,7 @@ int netdev_printk(const char *level, const struct net_device *dev, vaf.va = &args; r = __netdev_printk(level, dev, &vaf); + va_end(args); return r; @@ -6473,6 +6478,7 @@ int func(const struct net_device *dev, const char *fmt, ...) \ vaf.va = &args; \ \ r = __netdev_printk(level, dev, &vaf); \ + \ va_end(args); \ \ return r; \ diff --git a/tools/hv/hv_get_dhcp_info.sh b/tools/hv/hv_get_dhcp_info.sh new file mode 100755 index 0000000000000000000000000000000000000000..ccd3e9532764e5370ccf4f0b5b1ee4a294638c5e --- /dev/null +++ b/tools/hv/hv_get_dhcp_info.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# This example script retrieves the DHCP state of a given interface. +# In the interest of keeping the KVP daemon code free of distro specific +# information; the kvp daemon code invokes this external script to gather +# DHCP setting for the specific interface. +# +# Input: Name of the interface +# +# Output: The script prints the string "Enabled" to stdout to indicate +# that DHCP is enabled on the interface. If DHCP is not enabled, +# the script prints the string "Disabled" to stdout. +# +# Each Distro is expected to implement this script in a distro specific +# fashion. For instance on Distros that ship with Network Manager enabled, +# this script can be based on the Network Manager APIs for retrieving DHCP +# information. + +if_file="/etc/sysconfig/network-scripts/ifcfg-"$1 + +dhcp=$(grep "dhcp" $if_file 2>/dev/null) + +if [ "$dhcp" != "" ]; +then +echo "Enabled" +else +echo "Disabled" +fi diff --git a/tools/hv/hv_get_dns_info.sh b/tools/hv/hv_get_dns_info.sh new file mode 100755 index 0000000000000000000000000000000000000000..058c17b46ffcddbd921e52bb1f6c661f467b9d0a --- /dev/null +++ b/tools/hv/hv_get_dns_info.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# This example script parses /etc/resolv.conf to retrive DNS information. +# In the interest of keeping the KVP daemon code free of distro specific +# information; the kvp daemon code invokes this external script to gather +# DNS information. +# This script is expected to print the nameserver values to stdout. +# Each Distro is expected to implement this script in a distro specific +# fashion. For instance on Distros that ship with Network Manager enabled, +# this script can be based on the Network Manager APIs for retrieving DNS +# entries. + +cat /etc/resolv.conf 2>/dev/null | awk '/^nameserver/ { print $2 }' diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c index d9834b36294373f88d29731350ccc9d384b41788..5959affd882076d73338b0dff7d0cffb496636c7 100644 --- a/tools/hv/hv_kvp_daemon.c +++ b/tools/hv/hv_kvp_daemon.c @@ -31,6 +31,7 @@ #include <stdlib.h> #include <unistd.h> #include <string.h> +#include <ctype.h> #include <errno.h> #include <arpa/inet.h> #include <linux/connector.h> @@ -41,6 +42,7 @@ #include <syslog.h> #include <sys/stat.h> #include <fcntl.h> +#include <dirent.h> /* * KVP protocol: The user mode component first registers with the @@ -68,25 +70,39 @@ enum key_index { ProcessorArchitecture }; + +enum { + IPADDR = 0, + NETMASK, + GATEWAY, + DNS +}; + static char kvp_send_buffer[4096]; -static char kvp_recv_buffer[4096]; +static char kvp_recv_buffer[4096 * 2]; static struct sockaddr_nl addr; +static int in_hand_shake = 1; static char *os_name = ""; static char *os_major = ""; static char *os_minor = ""; static char *processor_arch; static char *os_build; -static char *lic_version; +static char *lic_version = "Unknown version"; static struct utsname uts_buf; +/* + * The location of the interface configuration file. + */ + +#define KVP_CONFIG_LOC "/var/opt/" #define MAX_FILE_NAME 100 #define ENTRIES_PER_BLOCK 50 struct kvp_record { - __u8 key[HV_KVP_EXCHANGE_MAX_KEY_SIZE]; - __u8 value[HV_KVP_EXCHANGE_MAX_VALUE_SIZE]; + char key[HV_KVP_EXCHANGE_MAX_KEY_SIZE]; + char value[HV_KVP_EXCHANGE_MAX_VALUE_SIZE]; }; struct kvp_file_state { @@ -94,7 +110,7 @@ struct kvp_file_state { int num_blocks; struct kvp_record *records; int num_records; - __u8 fname[MAX_FILE_NAME]; + char fname[MAX_FILE_NAME]; }; static struct kvp_file_state kvp_file_info[KVP_POOL_COUNT]; @@ -106,7 +122,7 @@ static void kvp_acquire_lock(int pool) if (fcntl(kvp_file_info[pool].fd, F_SETLKW, &fl) == -1) { syslog(LOG_ERR, "Failed to acquire the lock pool: %d", pool); - exit(-1); + exit(EXIT_FAILURE); } } @@ -118,7 +134,7 @@ static void kvp_release_lock(int pool) if (fcntl(kvp_file_info[pool].fd, F_SETLK, &fl) == -1) { perror("fcntl"); syslog(LOG_ERR, "Failed to release the lock pool: %d", pool); - exit(-1); + exit(EXIT_FAILURE); } } @@ -137,14 +153,19 @@ static void kvp_update_file(int pool) if (!filep) { kvp_release_lock(pool); syslog(LOG_ERR, "Failed to open file, pool: %d", pool); - exit(-1); + exit(EXIT_FAILURE); } bytes_written = fwrite(kvp_file_info[pool].records, sizeof(struct kvp_record), kvp_file_info[pool].num_records, filep); - fflush(filep); + if (ferror(filep) || fclose(filep)) { + kvp_release_lock(pool); + syslog(LOG_ERR, "Failed to write file, pool: %d", pool); + exit(EXIT_FAILURE); + } + kvp_release_lock(pool); } @@ -163,14 +184,19 @@ static void kvp_update_mem_state(int pool) if (!filep) { kvp_release_lock(pool); syslog(LOG_ERR, "Failed to open file, pool: %d", pool); - exit(-1); + exit(EXIT_FAILURE); } - while (!feof(filep)) { + for (;;) { readp = &record[records_read]; records_read += fread(readp, sizeof(struct kvp_record), ENTRIES_PER_BLOCK * num_blocks, filep); + if (ferror(filep)) { + syslog(LOG_ERR, "Failed to read file, pool: %d", pool); + exit(EXIT_FAILURE); + } + if (!feof(filep)) { /* * We have more data to read. @@ -180,7 +206,7 @@ static void kvp_update_mem_state(int pool) if (record == NULL) { syslog(LOG_ERR, "malloc failed"); - exit(-1); + exit(EXIT_FAILURE); } continue; } @@ -191,14 +217,15 @@ static void kvp_update_mem_state(int pool) kvp_file_info[pool].records = record; kvp_file_info[pool].num_records = records_read; + fclose(filep); kvp_release_lock(pool); } static int kvp_file_init(void) { - int ret, fd; + int fd; FILE *filep; size_t records_read; - __u8 *fname; + char *fname; struct kvp_record *record; struct kvp_record *readp; int num_blocks; @@ -208,7 +235,7 @@ static int kvp_file_init(void) if (access("/var/opt/hyperv", F_OK)) { if (mkdir("/var/opt/hyperv", S_IRUSR | S_IWUSR | S_IROTH)) { syslog(LOG_ERR, " Failed to create /var/opt/hyperv"); - exit(-1); + exit(EXIT_FAILURE); } } @@ -232,12 +259,18 @@ static int kvp_file_init(void) fclose(filep); return 1; } - while (!feof(filep)) { + for (;;) { readp = &record[records_read]; records_read += fread(readp, sizeof(struct kvp_record), ENTRIES_PER_BLOCK, filep); + if (ferror(filep)) { + syslog(LOG_ERR, "Failed to read file, pool: %d", + i); + exit(EXIT_FAILURE); + } + if (!feof(filep)) { /* * We have more data to read. @@ -311,7 +344,6 @@ static int kvp_key_add_or_modify(int pool, __u8 *key, int key_size, __u8 *value, int value_size) { int i; - int j, k; int num_records; struct kvp_record *record; int num_blocks; @@ -394,7 +426,7 @@ static int kvp_get_value(int pool, __u8 *key, int key_size, __u8 *value, return 1; } -static void kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size, +static int kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size, __u8 *value, int value_size) { struct kvp_record *record; @@ -406,16 +438,12 @@ static void kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size, record = kvp_file_info[pool].records; if (index >= kvp_file_info[pool].num_records) { - /* - * This is an invalid index; terminate enumeration; - * - a NULL value will do the trick. - */ - strcpy(value, ""); - return; + return 1; } memcpy(key, record[index].key, key_size); memcpy(value, record[index].value, value_size); + return 0; } @@ -426,6 +454,7 @@ void kvp_get_os_info(void) uname(&uts_buf); os_build = uts_buf.release; + os_name = uts_buf.sysname; processor_arch = uts_buf.machine; /* @@ -437,20 +466,70 @@ void kvp_get_os_info(void) if (p) *p = '\0'; + /* + * Parse the /etc/os-release file if present: + * http://www.freedesktop.org/software/systemd/man/os-release.html + */ + file = fopen("/etc/os-release", "r"); + if (file != NULL) { + while (fgets(buf, sizeof(buf), file)) { + char *value, *q; + + /* Ignore comments */ + if (buf[0] == '#') + continue; + + /* Split into name=value */ + p = strchr(buf, '='); + if (!p) + continue; + *p++ = 0; + + /* Remove quotes and newline; un-escape */ + value = p; + q = p; + while (*p) { + if (*p == '\\') { + ++p; + if (!*p) + break; + *q++ = *p++; + } else if (*p == '\'' || *p == '"' || + *p == '\n') { + ++p; + } else { + *q++ = *p++; + } + } + *q = 0; + + if (!strcmp(buf, "NAME")) { + p = strdup(value); + if (!p) + break; + os_name = p; + } else if (!strcmp(buf, "VERSION_ID")) { + p = strdup(value); + if (!p) + break; + os_major = p; + } + } + fclose(file); + return; + } + + /* Fallback for older RH/SUSE releases */ file = fopen("/etc/SuSE-release", "r"); if (file != NULL) goto kvp_osinfo_found; file = fopen("/etc/redhat-release", "r"); if (file != NULL) goto kvp_osinfo_found; - /* - * Add code for other supported platforms. - */ /* * We don't have information about the os. */ - os_name = uts_buf.sysname; return; kvp_osinfo_found: @@ -494,82 +573,458 @@ done: return; } + + +/* + * Retrieve an interface name corresponding to the specified guid. + * If there is a match, the function returns a pointer + * to the interface name and if not, a NULL is returned. + * If a match is found, the caller is responsible for + * freeing the memory. + */ + +static char *kvp_get_if_name(char *guid) +{ + DIR *dir; + struct dirent *entry; + FILE *file; + char *p, *q, *x; + char *if_name = NULL; + char buf[256]; + char *kvp_net_dir = "/sys/class/net/"; + char dev_id[256]; + + dir = opendir(kvp_net_dir); + if (dir == NULL) + return NULL; + + snprintf(dev_id, sizeof(dev_id), "%s", kvp_net_dir); + q = dev_id + strlen(kvp_net_dir); + + while ((entry = readdir(dir)) != NULL) { + /* + * Set the state for the next pass. + */ + *q = '\0'; + strcat(dev_id, entry->d_name); + strcat(dev_id, "/device/device_id"); + + file = fopen(dev_id, "r"); + if (file == NULL) + continue; + + p = fgets(buf, sizeof(buf), file); + if (p) { + x = strchr(p, '\n'); + if (x) + *x = '\0'; + + if (!strcmp(p, guid)) { + /* + * Found the guid match; return the interface + * name. The caller will free the memory. + */ + if_name = strdup(entry->d_name); + fclose(file); + break; + } + } + fclose(file); + } + + closedir(dir); + return if_name; +} + +/* + * Retrieve the MAC address given the interface name. + */ + +static char *kvp_if_name_to_mac(char *if_name) +{ + FILE *file; + char *p, *x; + char buf[256]; + char addr_file[256]; + int i; + char *mac_addr = NULL; + + snprintf(addr_file, sizeof(addr_file), "%s%s%s", "/sys/class/net/", + if_name, "/address"); + + file = fopen(addr_file, "r"); + if (file == NULL) + return NULL; + + p = fgets(buf, sizeof(buf), file); + if (p) { + x = strchr(p, '\n'); + if (x) + *x = '\0'; + for (i = 0; i < strlen(p); i++) + p[i] = toupper(p[i]); + mac_addr = strdup(p); + } + + fclose(file); + return mac_addr; +} + + +/* + * Retrieve the interface name given tha MAC address. + */ + +static char *kvp_mac_to_if_name(char *mac) +{ + DIR *dir; + struct dirent *entry; + FILE *file; + char *p, *q, *x; + char *if_name = NULL; + char buf[256]; + char *kvp_net_dir = "/sys/class/net/"; + char dev_id[256]; + int i; + + dir = opendir(kvp_net_dir); + if (dir == NULL) + return NULL; + + snprintf(dev_id, sizeof(dev_id), kvp_net_dir); + q = dev_id + strlen(kvp_net_dir); + + while ((entry = readdir(dir)) != NULL) { + /* + * Set the state for the next pass. + */ + *q = '\0'; + + strcat(dev_id, entry->d_name); + strcat(dev_id, "/address"); + + file = fopen(dev_id, "r"); + if (file == NULL) + continue; + + p = fgets(buf, sizeof(buf), file); + if (p) { + x = strchr(p, '\n'); + if (x) + *x = '\0'; + + for (i = 0; i < strlen(p); i++) + p[i] = toupper(p[i]); + + if (!strcmp(p, mac)) { + /* + * Found the MAC match; return the interface + * name. The caller will free the memory. + */ + if_name = strdup(entry->d_name); + fclose(file); + break; + } + } + fclose(file); + } + + closedir(dir); + return if_name; +} + + +static void kvp_process_ipconfig_file(char *cmd, + char *config_buf, int len, + int element_size, int offset) +{ + char buf[256]; + char *p; + char *x; + FILE *file; + + /* + * First execute the command. + */ + file = popen(cmd, "r"); + if (file == NULL) + return; + + if (offset == 0) + memset(config_buf, 0, len); + while ((p = fgets(buf, sizeof(buf), file)) != NULL) { + if ((len - strlen(config_buf)) < (element_size + 1)) + break; + + x = strchr(p, '\n'); + *x = '\0'; + strcat(config_buf, p); + strcat(config_buf, ";"); + } + pclose(file); +} + +static void kvp_get_ipconfig_info(char *if_name, + struct hv_kvp_ipaddr_value *buffer) +{ + char cmd[512]; + char dhcp_info[128]; + char *p; + FILE *file; + + /* + * Get the address of default gateway (ipv4). + */ + sprintf(cmd, "%s %s", "ip route show dev", if_name); + strcat(cmd, " | awk '/default/ {print $3 }'"); + + /* + * Execute the command to gather gateway info. + */ + kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way, + (MAX_GATEWAY_SIZE * 2), INET_ADDRSTRLEN, 0); + + /* + * Get the address of default gateway (ipv6). + */ + sprintf(cmd, "%s %s", "ip -f inet6 route show dev", if_name); + strcat(cmd, " | awk '/default/ {print $3 }'"); + + /* + * Execute the command to gather gateway info (ipv6). + */ + kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way, + (MAX_GATEWAY_SIZE * 2), INET6_ADDRSTRLEN, 1); + + + /* + * Gather the DNS state. + * Since there is no standard way to get this information + * across various distributions of interest; we just invoke + * an external script that needs to be ported across distros + * of interest. + * + * Following is the expected format of the information from the script: + * + * ipaddr1 (nameserver1) + * ipaddr2 (nameserver2) + * . + * . + */ + + sprintf(cmd, "%s", "hv_get_dns_info"); + + /* + * Execute the command to gather DNS info. + */ + kvp_process_ipconfig_file(cmd, (char *)buffer->dns_addr, + (MAX_IP_ADDR_SIZE * 2), INET_ADDRSTRLEN, 0); + + /* + * Gather the DHCP state. + * We will gather this state by invoking an external script. + * The parameter to the script is the interface name. + * Here is the expected output: + * + * Enabled: DHCP enabled. + */ + + sprintf(cmd, "%s %s", "hv_get_dhcp_info", if_name); + + file = popen(cmd, "r"); + if (file == NULL) + return; + + p = fgets(dhcp_info, sizeof(dhcp_info), file); + if (p == NULL) { + pclose(file); + return; + } + + if (!strncmp(p, "Enabled", 7)) + buffer->dhcp_enabled = 1; + else + buffer->dhcp_enabled = 0; + + pclose(file); +} + + +static unsigned int hweight32(unsigned int *w) +{ + unsigned int res = *w - ((*w >> 1) & 0x55555555); + res = (res & 0x33333333) + ((res >> 2) & 0x33333333); + res = (res + (res >> 4)) & 0x0F0F0F0F; + res = res + (res >> 8); + return (res + (res >> 16)) & 0x000000FF; +} + +static int kvp_process_ip_address(void *addrp, + int family, char *buffer, + int length, int *offset) +{ + struct sockaddr_in *addr; + struct sockaddr_in6 *addr6; + int addr_length; + char tmp[50]; + const char *str; + + if (family == AF_INET) { + addr = (struct sockaddr_in *)addrp; + str = inet_ntop(family, &addr->sin_addr, tmp, 50); + addr_length = INET_ADDRSTRLEN; + } else { + addr6 = (struct sockaddr_in6 *)addrp; + str = inet_ntop(family, &addr6->sin6_addr.s6_addr, tmp, 50); + addr_length = INET6_ADDRSTRLEN; + } + + if ((length - *offset) < addr_length + 1) + return HV_E_FAIL; + if (str == NULL) { + strcpy(buffer, "inet_ntop failed\n"); + return HV_E_FAIL; + } + if (*offset == 0) + strcpy(buffer, tmp); + else + strcat(buffer, tmp); + strcat(buffer, ";"); + + *offset += strlen(str) + 1; + return 0; +} + static int -kvp_get_ip_address(int family, char *buffer, int length) +kvp_get_ip_info(int family, char *if_name, int op, + void *out_buffer, int length) { struct ifaddrs *ifap; struct ifaddrs *curp; - int ipv4_len = strlen("255.255.255.255") + 1; - int ipv6_len = strlen("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")+1; int offset = 0; - const char *str; - char tmp[50]; + int sn_offset = 0; int error = 0; - + char *buffer; + struct hv_kvp_ipaddr_value *ip_buffer; + char cidr_mask[5]; /* /xyz */ + int weight; + int i; + unsigned int *w; + char *sn_str; + struct sockaddr_in6 *addr6; + + if (op == KVP_OP_ENUMERATE) { + buffer = out_buffer; + } else { + ip_buffer = out_buffer; + buffer = (char *)ip_buffer->ip_addr; + ip_buffer->addr_family = 0; + } /* * On entry into this function, the buffer is capable of holding the - * maximum key value (2048 bytes). + * maximum key value. */ if (getifaddrs(&ifap)) { strcpy(buffer, "getifaddrs failed\n"); - return 1; + return HV_E_FAIL; } curp = ifap; while (curp != NULL) { - if ((curp->ifa_addr != NULL) && - (curp->ifa_addr->sa_family == family)) { - if (family == AF_INET) { - struct sockaddr_in *addr = - (struct sockaddr_in *) curp->ifa_addr; - - str = inet_ntop(family, &addr->sin_addr, - tmp, 50); - if (str == NULL) { - strcpy(buffer, "inet_ntop failed\n"); - error = 1; - goto getaddr_done; - } - if (offset == 0) - strcpy(buffer, tmp); - else - strcat(buffer, tmp); - strcat(buffer, ";"); + if (curp->ifa_addr == NULL) { + curp = curp->ifa_next; + continue; + } - offset += strlen(str) + 1; - if ((length - offset) < (ipv4_len + 1)) - goto getaddr_done; + if ((if_name != NULL) && + (strncmp(curp->ifa_name, if_name, strlen(if_name)))) { + /* + * We want info about a specific interface; + * just continue. + */ + curp = curp->ifa_next; + continue; + } - } else { + /* + * We only support two address families: AF_INET and AF_INET6. + * If a family value of 0 is specified, we collect both + * supported address families; if not we gather info on + * the specified address family. + */ + if ((family != 0) && (curp->ifa_addr->sa_family != family)) { + curp = curp->ifa_next; + continue; + } + if ((curp->ifa_addr->sa_family != AF_INET) && + (curp->ifa_addr->sa_family != AF_INET6)) { + curp = curp->ifa_next; + continue; + } + if (op == KVP_OP_GET_IP_INFO) { /* - * We only support AF_INET and AF_INET6 - * and the list of addresses is separated by a ";". + * Gather info other than the IP address. + * IP address info will be gathered later. */ - struct sockaddr_in6 *addr = - (struct sockaddr_in6 *) curp->ifa_addr; - - str = inet_ntop(family, - &addr->sin6_addr.s6_addr, - tmp, 50); - if (str == NULL) { - strcpy(buffer, "inet_ntop failed\n"); - error = 1; - goto getaddr_done; - } - if (offset == 0) - strcpy(buffer, tmp); - else - strcat(buffer, tmp); - strcat(buffer, ";"); - offset += strlen(str) + 1; - if ((length - offset) < (ipv6_len + 1)) - goto getaddr_done; + if (curp->ifa_addr->sa_family == AF_INET) { + ip_buffer->addr_family |= ADDR_FAMILY_IPV4; + /* + * Get subnet info. + */ + error = kvp_process_ip_address( + curp->ifa_netmask, + AF_INET, + (char *) + ip_buffer->sub_net, + length, + &sn_offset); + if (error) + goto gather_ipaddr; + } else { + ip_buffer->addr_family |= ADDR_FAMILY_IPV6; + /* + * Get subnet info in CIDR format. + */ + weight = 0; + sn_str = (char *)ip_buffer->sub_net; + addr6 = (struct sockaddr_in6 *) + curp->ifa_netmask; + w = addr6->sin6_addr.s6_addr32; + + for (i = 0; i < 4; i++) + weight += hweight32(&w[i]); + + sprintf(cidr_mask, "/%d", weight); + if ((length - sn_offset) < + (strlen(cidr_mask) + 1)) + goto gather_ipaddr; + + if (sn_offset == 0) + strcpy(sn_str, cidr_mask); + else + strcat(sn_str, cidr_mask); + strcat((char *)ip_buffer->sub_net, ";"); + sn_offset += strlen(sn_str) + 1; } + /* + * Collect other ip related configuration info. + */ + + kvp_get_ipconfig_info(if_name, ip_buffer); } + +gather_ipaddr: + error = kvp_process_ip_address(curp->ifa_addr, + curp->ifa_addr->sa_family, + buffer, + length, &offset); + if (error) + goto getaddr_done; + curp = curp->ifa_next; } @@ -579,6 +1034,315 @@ getaddr_done: } +static int expand_ipv6(char *addr, int type) +{ + int ret; + struct in6_addr v6_addr; + + ret = inet_pton(AF_INET6, addr, &v6_addr); + + if (ret != 1) { + if (type == NETMASK) + return 1; + return 0; + } + + sprintf(addr, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:%02x%02x", + (int)v6_addr.s6_addr[0], (int)v6_addr.s6_addr[1], + (int)v6_addr.s6_addr[2], (int)v6_addr.s6_addr[3], + (int)v6_addr.s6_addr[4], (int)v6_addr.s6_addr[5], + (int)v6_addr.s6_addr[6], (int)v6_addr.s6_addr[7], + (int)v6_addr.s6_addr[8], (int)v6_addr.s6_addr[9], + (int)v6_addr.s6_addr[10], (int)v6_addr.s6_addr[11], + (int)v6_addr.s6_addr[12], (int)v6_addr.s6_addr[13], + (int)v6_addr.s6_addr[14], (int)v6_addr.s6_addr[15]); + + return 1; + +} + +static int is_ipv4(char *addr) +{ + int ret; + struct in_addr ipv4_addr; + + ret = inet_pton(AF_INET, addr, &ipv4_addr); + + if (ret == 1) + return 1; + return 0; +} + +static int parse_ip_val_buffer(char *in_buf, int *offset, + char *out_buf, int out_len) +{ + char *x; + char *start; + + /* + * in_buf has sequence of characters that are seperated by + * the character ';'. The last sequence does not have the + * terminating ";" character. + */ + start = in_buf + *offset; + + x = strchr(start, ';'); + if (x) + *x = 0; + else + x = start + strlen(start); + + if (strlen(start) != 0) { + int i = 0; + /* + * Get rid of leading spaces. + */ + while (start[i] == ' ') + i++; + + if ((x - start) <= out_len) { + strcpy(out_buf, (start + i)); + *offset += (x - start) + 1; + return 1; + } + } + return 0; +} + +static int kvp_write_file(FILE *f, char *s1, char *s2, char *s3) +{ + int ret; + + ret = fprintf(f, "%s%s%s%s\n", s1, s2, "=", s3); + + if (ret < 0) + return HV_E_FAIL; + + return 0; +} + + +static int process_ip_string(FILE *f, char *ip_string, int type) +{ + int error = 0; + char addr[INET6_ADDRSTRLEN]; + int i = 0; + int j = 0; + char str[256]; + char sub_str[10]; + int offset = 0; + + memset(addr, 0, sizeof(addr)); + + while (parse_ip_val_buffer(ip_string, &offset, addr, + (MAX_IP_ADDR_SIZE * 2))) { + + sub_str[0] = 0; + if (is_ipv4(addr)) { + switch (type) { + case IPADDR: + snprintf(str, sizeof(str), "%s", "IPADDR"); + break; + case NETMASK: + snprintf(str, sizeof(str), "%s", "NETMASK"); + break; + case GATEWAY: + snprintf(str, sizeof(str), "%s", "GATEWAY"); + break; + case DNS: + snprintf(str, sizeof(str), "%s", "DNS"); + break; + } + if (i != 0) { + if (type != DNS) { + snprintf(sub_str, sizeof(sub_str), + "_%d", i++); + } else { + snprintf(sub_str, sizeof(sub_str), + "%d", ++i); + } + } else if (type == DNS) { + snprintf(sub_str, sizeof(sub_str), "%d", ++i); + } + + + } else if (expand_ipv6(addr, type)) { + switch (type) { + case IPADDR: + snprintf(str, sizeof(str), "%s", "IPV6ADDR"); + break; + case NETMASK: + snprintf(str, sizeof(str), "%s", "IPV6NETMASK"); + break; + case GATEWAY: + snprintf(str, sizeof(str), "%s", + "IPV6_DEFAULTGW"); + break; + case DNS: + snprintf(str, sizeof(str), "%s", "DNS"); + break; + } + if ((j != 0) || (type == DNS)) { + if (type != DNS) { + snprintf(sub_str, sizeof(sub_str), + "_%d", j++); + } else { + snprintf(sub_str, sizeof(sub_str), + "%d", ++i); + } + } else if (type == DNS) { + snprintf(sub_str, sizeof(sub_str), + "%d", ++i); + } + } else { + return HV_INVALIDARG; + } + + error = kvp_write_file(f, str, sub_str, addr); + if (error) + return error; + memset(addr, 0, sizeof(addr)); + } + + return 0; +} + +static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) +{ + int error = 0; + char if_file[128]; + FILE *file; + char cmd[512]; + char *mac_addr; + + /* + * Set the configuration for the specified interface with + * the information provided. Since there is no standard + * way to configure an interface, we will have an external + * script that does the job of configuring the interface and + * flushing the configuration. + * + * The parameters passed to this external script are: + * 1. A configuration file that has the specified configuration. + * + * We will embed the name of the interface in the configuration + * file: ifcfg-ethx (where ethx is the interface name). + * + * The information provided here may be more than what is needed + * in a given distro to configure the interface and so are free + * ignore information that may not be relevant. + * + * Here is the format of the ip configuration file: + * + * HWADDR=macaddr + * IF_NAME=interface name + * DHCP=yes (This is optional; if yes, DHCP is configured) + * + * IPADDR=ipaddr1 + * IPADDR_1=ipaddr2 + * IPADDR_x=ipaddry (where y = x + 1) + * + * NETMASK=netmask1 + * NETMASK_x=netmasky (where y = x + 1) + * + * GATEWAY=ipaddr1 + * GATEWAY_x=ipaddry (where y = x + 1) + * + * DNSx=ipaddrx (where first DNS address is tagged as DNS1 etc) + * + * IPV6 addresses will be tagged as IPV6ADDR, IPV6 gateway will be + * tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as + * IPV6NETMASK. + * + * The host can specify multiple ipv4 and ipv6 addresses to be + * configured for the interface. Furthermore, the configuration + * needs to be persistent. A subsequent GET call on the interface + * is expected to return the configuration that is set via the SET + * call. + */ + + snprintf(if_file, sizeof(if_file), "%s%s%s", KVP_CONFIG_LOC, + "hyperv/ifcfg-", if_name); + + file = fopen(if_file, "w"); + + if (file == NULL) { + syslog(LOG_ERR, "Failed to open config file"); + return HV_E_FAIL; + } + + /* + * First write out the MAC address. + */ + + mac_addr = kvp_if_name_to_mac(if_name); + if (mac_addr == NULL) { + error = HV_E_FAIL; + goto setval_error; + } + + error = kvp_write_file(file, "HWADDR", "", mac_addr); + if (error) + goto setval_error; + + error = kvp_write_file(file, "IF_NAME", "", if_name); + if (error) + goto setval_error; + + if (new_val->dhcp_enabled) { + error = kvp_write_file(file, "DHCP", "", "yes"); + if (error) + goto setval_error; + + /* + * We are done!. + */ + goto setval_done; + } + + /* + * Write the configuration for ipaddress, netmask, gateway and + * name servers. + */ + + error = process_ip_string(file, (char *)new_val->ip_addr, IPADDR); + if (error) + goto setval_error; + + error = process_ip_string(file, (char *)new_val->sub_net, NETMASK); + if (error) + goto setval_error; + + error = process_ip_string(file, (char *)new_val->gate_way, GATEWAY); + if (error) + goto setval_error; + + error = process_ip_string(file, (char *)new_val->dns_addr, DNS); + if (error) + goto setval_error; + +setval_done: + free(mac_addr); + fclose(file); + + /* + * Now that we have populated the configuration file, + * invoke the external script to do its magic. + */ + + snprintf(cmd, sizeof(cmd), "%s %s", "hv_set_ifconfig", if_file); + system(cmd); + return 0; + +setval_error: + syslog(LOG_ERR, "Failed to write config file"); + free(mac_addr); + fclose(file); + return error; +} + + static int kvp_get_domain_name(char *buffer, int length) { @@ -646,6 +1410,10 @@ int main(void) char *p; char *key_value; char *key_name; + int op; + int pool; + char *if_name; + struct hv_kvp_ipaddr_value *kvp_ip_val; daemon(1, 0); openlog("KVP", 0, LOG_USER); @@ -657,13 +1425,13 @@ int main(void) if (kvp_file_init()) { syslog(LOG_ERR, "Failed to initialize the pools"); - exit(-1); + exit(EXIT_FAILURE); } fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR); if (fd < 0) { syslog(LOG_ERR, "netlink socket creation failed; error:%d", fd); - exit(-1); + exit(EXIT_FAILURE); } addr.nl_family = AF_NETLINK; addr.nl_pad = 0; @@ -675,7 +1443,7 @@ int main(void) if (error < 0) { syslog(LOG_ERR, "bind failed; error:%d", error); close(fd); - exit(-1); + exit(EXIT_FAILURE); } sock_opt = addr.nl_groups; setsockopt(fd, 270, 1, &sock_opt, sizeof(sock_opt)); @@ -687,7 +1455,7 @@ int main(void) message->id.val = CN_KVP_VAL; hv_msg = (struct hv_kvp_msg *)message->data; - hv_msg->kvp_hdr.operation = KVP_OP_REGISTER; + hv_msg->kvp_hdr.operation = KVP_OP_REGISTER1; message->ack = 0; message->len = sizeof(struct hv_kvp_msg); @@ -695,7 +1463,7 @@ int main(void) if (len < 0) { syslog(LOG_ERR, "netlink_send failed; error:%d", len); close(fd); - exit(-1); + exit(EXIT_FAILURE); } pfd.fd = fd; @@ -721,12 +1489,21 @@ int main(void) incoming_cn_msg = (struct cn_msg *)NLMSG_DATA(incoming_msg); hv_msg = (struct hv_kvp_msg *)incoming_cn_msg->data; - switch (hv_msg->kvp_hdr.operation) { - case KVP_OP_REGISTER: + /* + * We will use the KVP header information to pass back + * the error from this daemon. So, first copy the state + * and set the error code to success. + */ + op = hv_msg->kvp_hdr.operation; + pool = hv_msg->kvp_hdr.pool; + hv_msg->error = HV_S_OK; + + if ((in_hand_shake) && (op == KVP_OP_REGISTER1)) { /* * Driver is registering with us; stash away the version * information. */ + in_hand_shake = 0; p = (char *)hv_msg->body.kvp_register.version; lic_version = malloc(strlen(p) + 1); if (lic_version) { @@ -737,44 +1514,82 @@ int main(void) syslog(LOG_ERR, "malloc failed"); } continue; + } - /* - * The current protocol with the kernel component uses a - * NULL key name to pass an error condition. - * For the SET, GET and DELETE operations, - * use the existing protocol to pass back error. - */ + switch (op) { + case KVP_OP_GET_IP_INFO: + kvp_ip_val = &hv_msg->body.kvp_ip_val; + if_name = + kvp_mac_to_if_name((char *)kvp_ip_val->adapter_id); + + if (if_name == NULL) { + /* + * We could not map the mac address to an + * interface name; return error. + */ + hv_msg->error = HV_E_FAIL; + break; + } + error = kvp_get_ip_info( + 0, if_name, KVP_OP_GET_IP_INFO, + kvp_ip_val, + (MAX_IP_ADDR_SIZE * 2)); + + if (error) + hv_msg->error = error; + + free(if_name); + break; + + case KVP_OP_SET_IP_INFO: + kvp_ip_val = &hv_msg->body.kvp_ip_val; + if_name = kvp_get_if_name( + (char *)kvp_ip_val->adapter_id); + if (if_name == NULL) { + /* + * We could not map the guid to an + * interface name; return error. + */ + hv_msg->error = HV_GUID_NOTFOUND; + break; + } + error = kvp_set_ip_info(if_name, kvp_ip_val); + if (error) + hv_msg->error = error; + + free(if_name); + break; case KVP_OP_SET: - if (kvp_key_add_or_modify(hv_msg->kvp_hdr.pool, + if (kvp_key_add_or_modify(pool, hv_msg->body.kvp_set.data.key, hv_msg->body.kvp_set.data.key_size, hv_msg->body.kvp_set.data.value, hv_msg->body.kvp_set.data.value_size)) - strcpy(hv_msg->body.kvp_set.data.key, ""); + hv_msg->error = HV_S_CONT; break; case KVP_OP_GET: - if (kvp_get_value(hv_msg->kvp_hdr.pool, + if (kvp_get_value(pool, hv_msg->body.kvp_set.data.key, hv_msg->body.kvp_set.data.key_size, hv_msg->body.kvp_set.data.value, hv_msg->body.kvp_set.data.value_size)) - strcpy(hv_msg->body.kvp_set.data.key, ""); + hv_msg->error = HV_S_CONT; break; case KVP_OP_DELETE: - if (kvp_key_delete(hv_msg->kvp_hdr.pool, + if (kvp_key_delete(pool, hv_msg->body.kvp_delete.key, hv_msg->body.kvp_delete.key_size)) - strcpy(hv_msg->body.kvp_delete.key, ""); + hv_msg->error = HV_S_CONT; break; default: break; } - if (hv_msg->kvp_hdr.operation != KVP_OP_ENUMERATE) + if (op != KVP_OP_ENUMERATE) goto kvp_done; /* @@ -782,13 +1597,14 @@ int main(void) * both the key and the value; if not read from the * appropriate pool. */ - if (hv_msg->kvp_hdr.pool != KVP_POOL_AUTO) { - kvp_pool_enumerate(hv_msg->kvp_hdr.pool, + if (pool != KVP_POOL_AUTO) { + if (kvp_pool_enumerate(pool, hv_msg->body.kvp_enum_data.index, hv_msg->body.kvp_enum_data.data.key, HV_KVP_EXCHANGE_MAX_KEY_SIZE, hv_msg->body.kvp_enum_data.data.value, - HV_KVP_EXCHANGE_MAX_VALUE_SIZE); + HV_KVP_EXCHANGE_MAX_VALUE_SIZE)) + hv_msg->error = HV_S_CONT; goto kvp_done; } @@ -807,13 +1623,13 @@ int main(void) strcpy(key_value, lic_version); break; case NetworkAddressIPv4: - kvp_get_ip_address(AF_INET, key_value, - HV_KVP_EXCHANGE_MAX_VALUE_SIZE); + kvp_get_ip_info(AF_INET, NULL, KVP_OP_ENUMERATE, + key_value, HV_KVP_EXCHANGE_MAX_VALUE_SIZE); strcpy(key_name, "NetworkAddressIPv4"); break; case NetworkAddressIPv6: - kvp_get_ip_address(AF_INET6, key_value, - HV_KVP_EXCHANGE_MAX_VALUE_SIZE); + kvp_get_ip_info(AF_INET6, NULL, KVP_OP_ENUMERATE, + key_value, HV_KVP_EXCHANGE_MAX_VALUE_SIZE); strcpy(key_name, "NetworkAddressIPv6"); break; case OSBuildNumber: @@ -841,11 +1657,7 @@ int main(void) strcpy(key_name, "ProcessorArchitecture"); break; default: - strcpy(key_value, "Unknown Key"); - /* - * We use a null key name to terminate enumeration. - */ - strcpy(key_name, ""); + hv_msg->error = HV_S_CONT; break; } /* @@ -863,7 +1675,7 @@ kvp_done: len = netlink_send(fd, incoming_cn_msg); if (len < 0) { syslog(LOG_ERR, "net_link send failed; error:%d", len); - exit(-1); + exit(EXIT_FAILURE); } } diff --git a/tools/hv/hv_set_ifconfig.sh b/tools/hv/hv_set_ifconfig.sh new file mode 100755 index 0000000000000000000000000000000000000000..3e9427e08d806ae601724efe6423c00e2cb41597 --- /dev/null +++ b/tools/hv/hv_set_ifconfig.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# This example script activates an interface based on the specified +# configuration. +# +# In the interest of keeping the KVP daemon code free of distro specific +# information; the kvp daemon code invokes this external script to configure +# the interface. +# +# The only argument to this script is the configuration file that is to +# be used to configure the interface. +# +# Each Distro is expected to implement this script in a distro specific +# fashion. For instance on Distros that ship with Network Manager enabled, +# this script can be based on the Network Manager APIs for configuring the +# interface. +# +# This example script is based on a RHEL environment. +# +# Here is the format of the ip configuration file: +# +# HWADDR=macaddr +# IF_NAME=interface name +# DHCP=yes (This is optional; if yes, DHCP is configured) +# +# IPADDR=ipaddr1 +# IPADDR_1=ipaddr2 +# IPADDR_x=ipaddry (where y = x + 1) +# +# NETMASK=netmask1 +# NETMASK_x=netmasky (where y = x + 1) +# +# GATEWAY=ipaddr1 +# GATEWAY_x=ipaddry (where y = x + 1) +# +# DNSx=ipaddrx (where first DNS address is tagged as DNS1 etc) +# +# IPV6 addresses will be tagged as IPV6ADDR, IPV6 gateway will be +# tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as +# IPV6NETMASK. +# +# The host can specify multiple ipv4 and ipv6 addresses to be +# configured for the interface. Furthermore, the configuration +# needs to be persistent. A subsequent GET call on the interface +# is expected to return the configuration that is set via the SET +# call. +# + + + +echo "IPV6INIT=yes" >> $1 +echo "NM_CONTROLLED=no" >> $1 +echo "PEERDNS=yes" >> $1 +echo "ONBOOT=yes" >> $1 + +dhcp=$(grep "DHCP" $1 2>/dev/null) +if [ "$dhcp" != "" ]; +then +echo "BOOTPROTO=dhcp" >> $1; +fi + +cp $1 /etc/sysconfig/network-scripts/ + + +interface=$(echo $1 | awk -F - '{ print $2 }') + +/sbin/ifdown $interface 2>/dev/null +/sbin/ifup $interfac 2>/dev/null