[linux 驱动]input输入子系统详解与实战

目录

1 描述

2 结构体

2.1 input_class

2.2 input_dev

2.4 input_event

2.4 input_dev_type

3 input接口

3.1 input_allocate_device

3.2 input_free_device

3.3 input_register_device

3.4 input_unregister_device

3.5 input_event

3.6 input_sync

3.7 input_set_capability

4 input.c分析

4.1 注册字符设备

4.2 函数分析

4.2.1 class_register

4.2.2 dev_name

4.2.3 register_chrdev_region

4.2.3 MKDEV

5 示例

5.1 示例 1

5.1.1 代码

5.1.2 操作

5.2 mcu_cec驱动分析

5.2.1 注册输入事件

5.2.2 函数分析

5.2.2.1 devm_input_allocate_device

5.2.3 上报事件


1 描述

        input 子系统分为 input 驱动层、input 核心层、input 事件处理层,最终给用户空间提供可访问的设备节点,input 子系统框架如图

驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。

核心层:承上启下,为驱动层提供输入设备 注册和操作接口。通知事件层对输入事件进行 处理。

事件层:主要和用户空间进行交互。

        input 核心层会向 Linux 内核注册一个字符设备,在 drivers/input/input.c文件中描述

        如何找到input 设备节点文件对应的具体设备?

        方法(1):使用sudo cat 命令打开/dev/input/下的设备节点文件,然后分别操作各个输入设备,如果有输出有乱码,说明此时打开的设备文件就对应于当前的输入设备。

        方法(2):使用sudo hexdump 命令打开/dev/input/下设备文件,然后分别操作各个输入设备,如果有输出16进制数,说明此时打开的设备文件就对应于当前的输入设备。

        方法(3):查看/proc/bus/input/devices文件,里面记载了各个已经安装输入设备的信息。通过文件内容name和handler来查看设备文件与设备的对应关系。

127|rk3399_Android11:/ # cat /proc/bus/input/devices
I: Bus=0019 Vendor=524b Product=0006 Version=0100
N: Name="ff420030.pwm"
P: Phys=gpio-keys/remotectl
S: Sysfs=/devices/platform/ff420030.pwm/input/input0
U: Uniq=
H: Handlers=event0 cpufreq dmcfreq
B: PROP=0
B: EV=3
B: KEY=70010 20000000000000 0 100010002000000 78000004000a800 1e16c000000000 10004ffcI: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="adc_keys"
P: Phys=adc-keys/input0
S: Sysfs=/devices/platform/adc_keys/input/input1
U: Uniq=
H: Handlers=event1 cpufreq dmcfreq
B: PROP=0
B: EV=3
B: KEY=c000000000000 0I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/platform/gpio-keys/input/input2
U: Uniq=
H: Handlers=event2 cpufreq dmcfreq
B: PROP=0
B: EV=100003
B: KEY=10000000000000 0I: Bus=0000 Vendor=0001 Product=0001 Version=0100
N: Name="rk-headset"
P: Phys=
S: Sysfs=/devices/platform/rk-headset/input/input3
U: Uniq=
H: Handlers=event3
B: PROP=0
B: EV=3
B: KEY=400000000 0 0 0rk3399_Android11:/ #

2 结构体

2.1 input_class

1782 struct class input_class = {
1783         .name           = "input",
1784         .devnode        = input_devnode,
1785 };

2.2 input_dev

        input_dev 结构体是用于描述和管理输入设备的复杂数据结构,涵盖了设备的基本信息、支持的事件、状态管理、操作函数等多个方面,是 Linux 输入子系统的重要组成部分。

130 struct input_dev {
131         const char *name;
132         const char *phys;
133         const char *uniq;
134         struct input_id id;
135 
136         unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
137 
138         unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
139         unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
140         unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
141         unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
142         unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
143         unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
144         unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
145         unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
146         unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
147 
148         unsigned int hint_events_per_packet;
149 
150         unsigned int keycodemax;
151         unsigned int keycodesize;
152         void *keycode;
153 
154         int (*setkeycode)(struct input_dev *dev,
155                           const struct input_keymap_entry *ke,
156                           unsigned int *old_keycode);
157         int (*getkeycode)(struct input_dev *dev,
158                           struct input_keymap_entry *ke);
159 
160         struct ff_device *ff;
161 
162         unsigned int repeat_key;
163         struct timer_list timer;
164 
165         int rep[REP_CNT];
166 
167         struct input_mt *mt;
168 
169         struct input_absinfo *absinfo;
170 
171         unsigned long key[BITS_TO_LONGS(KEY_CNT)];
172         unsigned long led[BITS_TO_LONGS(LED_CNT)];
173         unsigned long snd[BITS_TO_LONGS(SND_CNT)];
174         unsigned long sw[BITS_TO_LONGS(SW_CNT)];
175 
176         int (*open)(struct input_dev *dev);
177         void (*close)(struct input_dev *dev);
178         int (*flush)(struct input_dev *dev, struct file *file);
179         int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
180 
181         struct input_handle __rcu *grab;
182 
183         spinlock_t event_lock;
184         struct mutex mutex;
185 
186         unsigned int users;
187         bool going_away;
188 
189         struct device dev;
190 
191         struct list_head        h_list;
192         struct list_head        node;
193 
194         unsigned int num_vals;
195         unsigned int max_vals;
196         struct input_value *vals;
197 
198         bool devres_managed;
199 
200         ktime_t timestamp[INPUT_CLK_MAX];
201 };

基本信息

const char *name;

const char *phys;

const char *uniq;

name: 输入设备的名称。

phys: 物理路径,通常用于描述设备的实际连接地址。

uniq: 设备的唯一标识符,用于区分不同设备。

设备 ID

struct input_id id;

id: 包含设备类型、制造商和产品 ID 的结构体,用于唯一标识输入设备。

属性位

unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

这些位字段用于表示设备的特性(如支持哪些输入属性)。

evbit: 表示设备支持的事件类型(如按键、鼠标移动等)。

keybit: 表示支持的按键。

relbit: 表示支持的相对坐标变化事件(如鼠标移动)。

absbit: 表示支持的绝对坐标事件。

mscbit: 表示支持的特殊事件类型。

ledbit: 表示支持的 LED 控制。

sndbit: 表示支持的声音控制。

ffbit: 表示支持的力反馈事件。

swbit: 表示支持的开关状态。

键盘设置

unsigned int keycodemax;

unsigned int keycodesize;

void *keycode;

keycodemax: 最大键码数量。

keycodesize: 每个键码的大小。

keycode: 指向键码数组的指针。

键码处理函数

int (*setkeycode)(struct input_dev *dev, const struct input_keymap_entry *ke, unsigned int *old_keycode);

int (*getkeycode)(struct input_dev *dev, struct input_keymap_entry *ke);

setkeycode: 设置键码的回调函数。

getkeycode: 获取键码的回调函数。

其他功能

unsigned int repeat_key;

struct timer_list timer;

repeat_key: 用于设置键重复的时间间隔。

timer: 定时器,用于处理键重复事件。

多点触控和绝对信息

struct input_mt *mt;

struct input_absinfo *absinfo;

mt: 多点触控相关信息。

absinfo: 绝对坐标信息。

状态管理

unsigned long key[BITS_TO_LONGS(KEY_CNT)];

unsigned long led[BITS_TO_LONGS(LED_CNT)];

unsigned long snd[BITS_TO_LONGS(SND_CNT)];

unsigned long sw[BITS_TO_LONGS(SW_CNT)];

用于存储设备当前状态的信息,包括按键状态、LED 状态、声音状态和开关状态。

设备操作函数

int (*open)(struct input_dev *dev);

void (*close)(struct input_dev *dev);

int (*flush)(struct input_dev *dev, struct file *file);

int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

open: 打开设备的回调函数。

close: 关闭设备的回调函数。

flush: 刷新设备状态的回调函数。

event: 处理输入事件的回调函数。

锁和同步

spinlock_t event_lock;

struct mutex mutex;

event_lock: 自旋锁,用于保护事件队列。

mutex: 互斥锁,用于设备操作的同步。

引用计数和状态

unsigned int users;

bool going_away;

users: 当前打开设备的用户数量。

going_away: 表示设备是否正在被移除。

设备结构

struct device dev;

dev: 表示内核设备结构,包含设备的基本信息。

列表和管理

struct list_head h_list;

struct list_head node;

h_list: 用于哈希表中的链表节点。

node: 用于设备列表中的链表节点。

值管理

unsigned int num_vals;

unsigned int max_vals;

struct input_value *vals;

num_vals: 当前值的数量。

max_vals: 最大值数量。

vals: 指向输入值数组的指针。

设备资源管理

bool devres_managed;

devres_managed: 表示设备资源是否由设备管理系统管理。

时间戳

ktime_t timestamp[INPUT_CLK_MAX];

存储输入事件的时间戳。

2.4 input_event

        Linux 内核使用 input_event 这个结构体来表示所有的输入事件

28 struct input_event {29 #if (__BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64)) && !defined(__KERNEL__)30         struct timeval time;31 #define input_event_sec time.tv_sec32 #define input_event_usec time.tv_usec33 #else    34         __kernel_ulong_t __sec;35 #if defined(__sparc__) && defined(__arch64__)36         unsigned int __usec;37         unsigned int __pad;38 #else    39         __kernel_ulong_t __usec;40 #endif41 #define input_event_sec  __sec42 #define input_event_usec __usec43 #endif44         __u16 type;45         __u16 code;46         __s32 value;47 };

type事件类型,比如 EV_KEY,表示此次事件为按键事件,此成员变量为 16 位。

code:事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如:KEY_0、KEY_1等等这些按键。此成员变量为 16 位。

value:值,比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1 的话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了。

2.4 input_dev_type

1768 static const struct device_type input_dev_type = {
1769         .groups         = input_dev_attr_groups,
1770         .release        = input_dev_release,
1771         .uevent         = input_dev_uevent,
1772 #ifdef CONFIG_PM_SLEEP
1773         .pm             = &input_dev_pm_ops,
1774 #endif
1775 };
1519 static const struct attribute_group *input_dev_attr_groups[] = {
1520         &input_dev_attr_group,
1521         &input_dev_id_attr_group,
1522         &input_dev_caps_attr_group,
1523         NULL
1524 };
1526 static void input_dev_release(struct device *device)
1527 {
1528         struct input_dev *dev = to_input_dev(device);
1529 
1530         input_ff_destroy(dev);
1531         input_mt_destroy_slots(dev);
1532         kfree(dev->absinfo);
1533         kfree(dev->vals);
1534         kfree(dev);
1535 
1536         module_put(THIS_MODULE);
1537 }
1759 static const struct dev_pm_ops input_dev_pm_ops = {
1760         .suspend        = input_dev_suspend,
1761         .resume         = input_dev_resume,
1762         .freeze         = input_dev_freeze,
1763         .poweroff       = input_dev_poweroff,
1764         .restore        = input_dev_resume,
1765 };

3 input接口

3.1 input_allocate_device

函数原型

struct input_dev *input_allocate_device(void)

返回值

struct input_dev *

成功:input_dev结构体指针 失败:NULL

功能

申请一个 input_dev

1797 struct input_dev *input_allocate_device(void)
1798 {  
1799         static atomic_t input_no = ATOMIC_INIT(-1);
1800         struct input_dev *dev;
1801    
1802         dev = kzalloc(sizeof(*dev), GFP_KERNEL);
1803         if (dev) {
1804                 dev->dev.type = &input_dev_type;
1805                 dev->dev.class = &input_class;
1806                 device_initialize(&dev->dev);
1807                 mutex_init(&dev->mutex);
1808                 spin_lock_init(&dev->event_lock);
1809                 timer_setup(&dev->timer, NULL, 0);
1810                 INIT_LIST_HEAD(&dev->h_list);
1811                 INIT_LIST_HEAD(&dev->node);
1812    
1813                 dev_set_name(&dev->dev, "input%lu",
1814                              (unsigned long)atomic_inc_return(&input_no));
1815    
1816                 __module_get(THIS_MODULE);
1817         }
1818    
1819         return dev;
1820 }  
1768 static const struct device_type input_dev_type = {
1769         .groups         = input_dev_attr_groups,
1770         .release        = input_dev_release,
1771         .uevent         = input_dev_uevent,
1772 #ifdef CONFIG_PM_SLEEP
1773         .pm             = &input_dev_pm_ops,
1774 #endif
1775 };

3.2 input_free_device

函数原型

void input_free_device(struct input_dev *dev)

参数

struct input_dev *

input_dev结构体指针

功能

释放一个 input_dev

1902 void input_free_device(struct input_dev *dev)
1903 {
1904         if (dev) {
1905                 if (dev->devres_managed)
1906                         WARN_ON(devres_destroy(dev->dev.parent,
1907                                                 devm_input_device_release,
1908                                                 devm_input_device_match,
1909                                                 dev));
1910                 input_put_device(dev);
1911         }
1912 }

3.3 input_register_device

函数原型

int input_register_device(struct input_dev *dev)

参数

struct input_dev *dev

input_dev结构体指针

返回值

int

成功:0 失败:错误码

功能

向 Linux 内核注册 input_dev

2140 int input_register_device(struct input_dev *dev)
2141 {
2142         struct input_devres *devres = NULL;
2143         struct input_handler *handler;
2144         unsigned int packet_size;
2145         const char *path;
2146         int error;
2147 
2148         if (test_bit(EV_ABS, dev->evbit) && !dev->absinfo) {
2149                 dev_err(&dev->dev,
2150                         "Absolute device without dev->absinfo, refusing to register\n");
2151                 return -EINVAL;
2152         }
2153 
2154         if (dev->devres_managed) {
2155                 devres = devres_alloc(devm_input_device_unregister,
2156                                       sizeof(*devres), GFP_KERNEL);
2157                 if (!devres)
2158                         return -ENOMEM;
2159 
2160                 devres->input = dev;
2161         }
2162 
2163         /* Every input device generates EV_SYN/SYN_REPORT events. */
2164         __set_bit(EV_SYN, dev->evbit);
2165 
2166         /* KEY_RESERVED is not supposed to be transmitted to userspace. */
2167         __clear_bit(KEY_RESERVED, dev->keybit);
2168 
2169         /* Make sure that bitmasks not mentioned in dev->evbit are clean. */
2170         input_cleanse_bitmasks(dev);
2171 
2172         packet_size = input_estimate_events_per_packet(dev);
2173         if (dev->hint_events_per_packet < packet_size)
2174                 dev->hint_events_per_packet = packet_size;
2175 
2176         dev->max_vals = dev->hint_events_per_packet + 2;
2177         dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
2178         if (!dev->vals) {
2179                 error = -ENOMEM;
2180                 goto err_devres_free;
2181         }
2182 
2183         /*
2184          * If delay and period are pre-set by the driver, then autorepeating
2185          * is handled by the driver itself and we don't do it in input.c.
2186          */
2187         if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD])
2188                 input_enable_softrepeat(dev, 250, 33);
2189 
2190         if (!dev->getkeycode)
2191                 dev->getkeycode = input_default_getkeycode;
2192 
2193         if (!dev->setkeycode)
2194                 dev->setkeycode = input_default_setkeycode;
2195 
2196         error = device_add(&dev->dev);
2197         if (error)
2198                 goto err_free_vals;
2199 
2200         path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
2201         pr_info("%s as %s\n",
2202                 dev->name ? dev->name : "Unspecified device",
2203                 path ? path : "N/A");
2204         kfree(path);
2205 
2206         error = mutex_lock_interruptible(&input_mutex);
2207         if (error)
2208                 goto err_device_del;
2209 
2210         list_add_tail(&dev->node, &input_dev_list);
2211 
2212         list_for_each_entry(handler, &input_handler_list, node)
2213                 input_attach_handler(dev, handler);
2214 
2215         input_wakeup_procfs_readers();
2216 
2217         mutex_unlock(&input_mutex);
2218 
2219         if (dev->devres_managed) {
2220                 dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",
2221                         __func__, dev_name(&dev->dev));
2222                 devres_add(dev->dev.parent, devres);
2223         }
2224         return 0;
2225 
2226 err_device_del:
2227         device_del(&dev->dev);
2228 err_free_vals:
2229         kfree(dev->vals);
2230         dev->vals = NULL;
2231 err_devres_free:
2232         devres_free(devres);
2233         return error;
2234 }

3.4 input_unregister_device

函数原型

void input_unregister_device(struct input_dev *dev)

参数

struct input_dev *dev

input_dev结构体指针

返回值

功能

向 Linux 内核注销input_dev

2244 void input_unregister_device(struct input_dev *dev)
2245 {
2246         if (dev->devres_managed) {
2247                 WARN_ON(devres_destroy(dev->dev.parent,
2248                                         devm_input_device_unregister,
2249                                         devm_input_device_match,
2250                                         dev));
2251                 __input_unregister_device(dev);
2252                 /*
2253                  * We do not do input_put_device() here because it will be done
2254                  * when 2nd devres fires up.
2255                  */
2256         } else {
2257                 __input_unregister_device(dev);
2258                 input_put_device(dev);
2259         }
2260 }

3.5 input_event

函数原型

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

参数

struct input_dev *dev

需要上报的 input_dev

unsigned int type

上报的事件类型,比如 EV_KEY

unsigned int code

事件码,也就是我们注册的按键值,比如 KEY_0、KEY_1

int value

事件值,比如 1 表示按键按下,0 表示按键松开

返回值

功能

上报指定的事件以及对应的值

436 void input_event(struct input_dev *dev,437                  unsigned int type, unsigned int code, int value)438 {439         unsigned long flags;440 441         if (is_event_supported(type, dev->evbit, EV_MAX)) {442 443                 spin_lock_irqsave(&dev->event_lock, flags);444                 input_handle_event(dev, type, code, value);445                 spin_unlock_irqrestore(&dev->event_lock, flags);446         }447 }

3.6 input_sync

函数原型

static inline void input_sync(struct input_dev *dev)

参数

struct input_dev *dev

需要上报的 input_dev

返回值

功能

告诉 Linux 内核 input 子系统上报结束,input_sync 函数本质是上报一个同步事件

430 static inline void input_sync(struct input_dev *dev)
431 {        
432         input_event(dev, EV_SYN, SYN_REPORT, 0);
433 }    

3.7 input_set_capability

函数原型

void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)

参数

struct input_dev *dev

输入设备结构体指针

unsigned int type

输入事件的类型

unsigned int code

事件的特定代码

返回值

功能

向一个已注册的输入设备添加特定的输入能力

1964 void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
1965 {
1966         switch (type) {
1967         case EV_KEY:
1968                 __set_bit(code, dev->keybit);
1969                 break;
1970 
1971         case EV_REL:
1972                 __set_bit(code, dev->relbit);
1973                 break;
1974 
1975         case EV_ABS:
1976                 input_alloc_absinfo(dev);
1977                 if (!dev->absinfo)
1978                         return;
1979 
1980                 __set_bit(code, dev->absbit);
1981                 break;
1982 
1983         case EV_MSC:
1984                 __set_bit(code, dev->mscbit);
1985                 break;
1986 
1987         case EV_SW:
1988                 __set_bit(code, dev->swbit);
1989                 break;
1990 
1991         case EV_LED:
1992                 __set_bit(code, dev->ledbit);
1993                 break;
1994 
1995         case EV_SND:
1996                 __set_bit(code, dev->sndbit);
1997                 break;
1998 
1999         case EV_FF:
2000                 __set_bit(code, dev->ffbit);
2001                 break;
2002 
2003         case EV_PWR:
2004                 /* do nothing */
2005                 break;
2006 
2007         default:
2008                 pr_err("%s: unknown type %u (code %u)\n", __func__, type, code);
2009                 dump_stack();
2010                 return;
2011         }
2012 
2013         __set_bit(type, dev->evbit);
2014 }

4 input.c分析

4.1 注册字符设备

        input 核心层会向 Linux 内核注册一个字符设备,class_register函数注册一个 input 类,这样系统启动以后就会在/sys/class 目录下有一个 input 子目录

zwzn2064@zwzn2064-CVN-Z690D5-GAMING-PRO:~$ ls /sys/class
ata_device   devfreq-event  i2c-dev   nvme            printer       scsi_generic  vc
ata_link     dma            input     nvme-subsystem  ptp           scsi_host     vfio
ata_port     dmi            iommu     pci_bus         pwm           sound         virtio-ports
backlight    extcon         leds      pci_epc         rapidio_port  spi_master    vtconsole
bdi          firmware       mdio_bus  phy             regulator     spi_slave     wakeup
block        gpio           mem       powercap        remoteproc    thermal       watchdog
bsg          graphics       misc      power_supply    rfkill        tpm           wmi_bus
dax          hidraw         mmc_host  ppdev           rtc           tpmrm
devcoredump  hwmon          nd        ppp             scsi_device   tty
devfreq      i2c-adapter    net       pps             scsi_disk     usbmisc
zwzn2064@zwzn2064-CVN-Z690D5-GAMING-PRO:~$ ls /sys/class/input/
event0   event11  event3  event6  event9  input10  input2  input5  input8
event1   event12  event4  event7  input0  input11  input3  input6  input9
event10  event2   event5  event8  input1  input12  input4  input7  mice
zwzn2064@zwzn2064-CVN-Z690D5-GAMING-PRO:~$ 

代码如下所示

#define INPUT_MAJOR             13
36 #define INPUT_MAX_CHAR_DEVICES          10241777 static char *input_devnode(struct device *dev, umode_t *mode)
1778 {
1779         return kasprintf(GFP_KERNEL, "input/%s", dev_name(dev));
1780 }1782 struct class input_class = {
1783         .name           = "input",
1784         .devnode        = input_devnode,
1785 };2476 static int __init input_init(void)
2477 {
2478         int err;
2479 
2480         err = class_register(&input_class);
2481         if (err) {
2482                 pr_err("unable to register input_dev class\n");
2483                 return err;
2484         }
2485 
2486         err = input_proc_init();
2487         if (err)
2488                 goto fail1;
2489 
2490         err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
2491                                      INPUT_MAX_CHAR_DEVICES, "input");
2492         if (err) {
2493                 pr_err("unable to register char major %d", INPUT_MAJOR);
2494                 goto fail2;
2495         }
2496 
2497         return 0;
2498 
2499  fail2: input_proc_exit();
2500  fail1: class_unregister(&input_class);
2501         return err;
2502 }
2503 
2504 static void __exit input_exit(void)
2505 {
2506         input_proc_exit();
2507         unregister_chrdev_region(MKDEV(INPUT_MAJOR, 0),
2508                                  INPUT_MAX_CHAR_DEVICES);
2509         class_unregister(&input_class);
2510 }
2511 
2512 subsys_initcall(input_init);
2513 module_exit(input_exit);

4.2 函数分析

4.2.1 class_register

函数原型

#define class_register(class) \

({ \

static struct lock_class_key __key; \

__class_register(class, &__key); \

})

int __class_register(struct class *cls, struct lock_class_key *key)

参数

struct class *cls

指向 class 结构体的指针,表示要注册的设备类

struct lock_class_key *key

指向 lock_class_key 结构体的指针,用于锁的调试和分类(通常用于锁的层次化管理)

返回值

int

成功:0 失败:错误码

功能

注册设备类

146 int __class_register(struct class *cls, struct lock_class_key *key)
147 {
148         struct subsys_private *cp;
149         int error;
150 
151         pr_debug("device class '%s': registering\n", cls->name);
152 
153         cp = kzalloc(sizeof(*cp), GFP_KERNEL);
154         if (!cp)
155                 return -ENOMEM;
156         klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put);
157         INIT_LIST_HEAD(&cp->interfaces);
158         kset_init(&cp->glue_dirs);
159         __mutex_init(&cp->mutex, "subsys mutex", key);
160         error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
161         if (error) {
162                 kfree(cp);
163                 return error;
164         }
165 
166         /* set the default /sys/dev directory for devices of this class */
167         if (!cls->dev_kobj)
168                 cls->dev_kobj = sysfs_dev_char_kobj;
169 
170 #if defined(CONFIG_BLOCK)
171         /* let the block class directory show up in the root of sysfs */
172         if (!sysfs_deprecated || cls != &block_class)
173                 cp->subsys.kobj.kset = class_kset;
174 #else    
175         cp->subsys.kobj.kset = class_kset;
176 #endif   
177         cp->subsys.kobj.ktype = &class_ktype;
178         cp->class = cls;
179         cls->p = cp;
180 
181         error = kset_register(&cp->subsys);
182         if (error) {
183                 kfree(cp);
184                 return error;
185         }                                 
186         error = class_add_groups(class_get(cls), cls->class_groups);
187         class_put(cls);
188         return error;
189 }

4.2.2 dev_name

函数原型

static inline const char *dev_name(const struct device *dev)

参数

const struct device *dev

指向 struct device 结构体的指针,该结构体表示一个设备实例

返回值

const char *

函数返回一个指向 const char 的指针,这个指针指向设备名称的字符串

功能

获取设备名称

1132 static inline const char *dev_name(const struct device *dev)
1133 {
1134         /* Use the init name until the kobject becomes available */
1135         if (dev->init_name)
1136                 return dev->init_name;
1137 
1138         return kobject_name(&dev->kobj);
1139 }

4.2.3 register_chrdev_region

函数原型

int register_chrdev_region(dev_t from, unsigned count, const char *name)

参数

dev_t from

dev_t from: 起始设备号,指定从哪个设备号开始分配。

unsigned count

unsigned count: 设备号的数量,指定需要多少个连续的设备号

const char *name

const char *name: 设备的名称,通常用于在系统中标识这个设备

返回值

int

成功:字符设备分配设备号 失败:负值

功能

注册字符设备号

209 int register_chrdev_region(dev_t from, unsigned count, const char *name)
210 {
211         struct char_device_struct *cd;
212         dev_t to = from + count;
213         dev_t n, next;
214                 
215         for (n = from; n < to; n = next) {
216                 next = MKDEV(MAJOR(n)+1, 0);
217                 if (next > to)
218                         next = to;
219                 cd = __register_chrdev_region(MAJOR(n), MINOR(n),
220                                next - n, name);
221                 if (IS_ERR(cd))
222                         goto fail;
223         }
224         return 0;
225 fail:
226         to = n;
227         for (n = from; n < to; n = next) {
228                 next = MKDEV(MAJOR(n)+1, 0);
229                 kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
230         }
231         return PTR_ERR(cd);       
232 }

4.2.3 MKDEV

函数原型

#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

#define MINORBITS 20

参数

ma

主设备号

mi

此设备号

返回值

功能

将主设备号和次设备号组合成一个 dev_t 类型的设备号

5 示例

5.1 示例 1

5.1.1 代码

#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/fs.h>
#include <linux/input.h>struct input_dev *input_dev_s;static int test_init(void){int ret;input_dev_s = input_allocate_device();input_dev_s->name = "input_test";input_dev_s->id.bustype = BUS_HOST;input_dev_s->id.vendor = 0x0001;input_dev_s->id.product = 0x0001;input_dev_s->id.version = 0x0100;ret = input_register_device(input_dev_s);if(ret){printk(KERN_ERR "register input device error\n");input_free_device(input_dev_s);goto failed;}printk("register input device ok\r\n");		return 0;
failed:return ret;}static void test_exit(void){printk("unregister input device\r\n");input_unregister_device(input_dev_s);}module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");

5.1.2 操作

        注册成功后,/dev/input/下多了一个event4 文件节点

console:/data # ls /dev/input/                                                 
event0  event1  event2  event3
console:/data # insmod input_test.ko                                           
[  261.075255] input: input_test as /devices/virtual/input/input5
[  261.077768] register input device ok
console:/data # ls /dev/input/                                                 
event0  event1  event2  event3  event4

        使用 cat /proc/bus/input/devices 命令查看输入设备信息,可以看到 input_test 的输入设备信息。

130|console:/data #  cat /proc/bus/input/devices
I: Bus=0019 Vendor=524b Product=0006 Version=0100
N: Name="ff420030.pwm"
P: Phys=gpio-keys/remotectl
S: Sysfs=/devices/platform/ff420030.pwm/input/input0
U: Uniq=
H: Handlers=event0 cpufreq dmcfreq 
B: PROP=0
B: EV=3
B: KEY=70010 20000000000000 0 100010002000000 78000004000a800 1e16c000000000 10004ffcI: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="adc_keys"
P: Phys=adc-keys/input0
S: Sysfs=/devices/platform/adc_keys/input/input1
U: Uniq=
H: Handlers=event1 cpufreq dmcfreq 
B: PROP=0
B: EV=3
B: KEY=c000000000000 0I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/platform/gpio-keys/input/input2
U: Uniq=
H: Handlers=event2 cpufreq dmcfreq 
B: PROP=0
B: EV=100003
B: KEY=10000000000000 0I: Bus=0000 Vendor=0001 Product=0001 Version=0100
N: Name="rk-headset"
P: Phys=
S: Sysfs=/devices/platform/rk-headset/input/input3
U: Uniq=
H: Handlers=event3 
B: PROP=0
B: EV=3
B: KEY=400000000 0 0 0I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="input_test"
P: Phys=
S: Sysfs=/devices/virtual/input/input5
U: Uniq=
H: Handlers=event4 
B: PROP=0
B: EV=1console:/data # 

5.2 mcu_cec驱动分析

5.2.1 注册输入事件

657 static int  mcu_cec_probe(struct i2c_client *client, const struct i2c_device_id *id)
658 {
659         int ret = 0;
660         int i;
661         struct mcu_cec *mcu_cec;
662         struct input_dev *input;
663         struct device_node *np = client->dev.of_node;
664         printk("%s: probe\n", __FUNCTION__);
665 
666 
667         mcu_cec = devm_kzalloc(&client->dev, sizeof(struct mcu_cec), GFP_KERNEL);
668         if (!mcu_cec)
669                 return -ENOMEM;
705         mcu_cec->mcu_cec_wq = create_singlethread_workqueue("mcu_cec_wq");
706         if (!mcu_cec->mcu_cec_wq){
707                 printk(KERN_ERR"%s: create workqueue failed\n", __func__);
708                 ret = -ENOMEM;
709                 goto failed;
710         }
711 
728         input = devm_input_allocate_device(&client->dev);
729         if (!input) {
730                 ret = -ENOMEM;
731                 goto failed;
732         }
733 
736         input->name = client->name;
737         input->phys = "cec-keys/input0";
738         input->dev.parent = &client->dev;
739 
740         input->id.bustype = BUS_HOST;
741         input->id.vendor = 0x0001;
742         input->id.product = 0x0001;
743         input->id.version = 0x0100;
744 
745         mcu_cec->input_dev = input;
746 
747         for (i=0;i<sizeof(mcu_cec_input_key)/sizeof(struct mcu_cec_key_table);i++){
748                         unsigned int type = EV_KEY;
749 
750                 input_set_capability(input, type, mcu_cec_input_key[i].keyCode);
751         }
752         input_set_capability(input, EV_KEY, KEY_WAKEUP);
753         input_set_capability(input, EV_KEY, KEY_F12);
754         ret = input_register_device(input);
755         if (ret) {
756                 input_free_device(input);
757                 goto failed;
758         }
760         mcu_cec->nb.notifier_call = cec_hotplug_notifier_call;
761         ret = cec_hotplug_reg_notifier(&mcu_cec->nb);
762         if (ret) {
763                 printk("failed to reg notifier: %d\n", ret);
764         }
765 
766         mutex_init(&mcu_cec->m_lock);
767         wake_lock_init(&mcu_cec->w_lock, WAKE_LOCK_SUSPEND, "mcu_lock");
768         wake_lock(&mcu_cec->w_lock);
769         printk("%s: probe ok!!\n", __FUNCTION__);
770     return 0;
771 failed:
772         return ret;
773 }

5.2.2 函数分析

5.2.2.1 devm_input_allocate_device

函数原型

struct input_dev *devm_input_allocate_device(struct device *dev)

参数

struct device *dev

设备结构体指针

返回值

struct input_dev *

输入设备结构体指针

功能

分配并初始化一个输入设备

1862 struct input_dev *devm_input_allocate_device(struct device *dev)
1863 {
1864         struct input_dev *input;
1865         struct input_devres *devres;
1866 
1867         devres = devres_alloc(devm_input_device_release,
1868                               sizeof(*devres), GFP_KERNEL);
1869         if (!devres)
1870                 return NULL;
1871 
1872         input = input_allocate_device();
1873         if (!input) {
1874                 devres_free(devres);
1875                 return NULL;
1876         }
1877 
1878         input->dev.parent = dev;
1879         input->devres_managed = true; 
1880 
1881         devres->input = input;
1882         devres_add(dev, devres);
1883 
1884         return input;
1885 }
1834 static void devm_input_device_release(struct device *dev, void *res)
1835 {
1836         struct input_devres *devres = res;
1837         struct input_dev *input = devres->input;
1838 
1839         dev_dbg(dev, "%s: dropping reference to %s\n",
1840                 __func__, dev_name(&input->dev));
1841         input_put_device(input);
1842 }

devm_input_allocate_device 函数提供了一个方便的方式来分配和初始化 input_dev 设备,并利用设备管理机制自动处理资源释放。这使得设备的创建和销毁变得更加简洁和安全。

devres 机制: devres(设备资源)用于管理与设备相关的资源。devres_add 将资源添加到设备的资源管理列表中,devm_input_device_release 函数会在设备释放时调用,以释放相关资源。

自动资源管理: 通过 devres 机制,确保在设备卸载时,input_dev 设备会被正确释放,避免内存泄漏和资源浪费。

父设备设置: 将 input_dev 的父设备设置为传入的设备 dev,确保设备之间的关系正确设置,有利于设备树的管理和资源的分配。

5.2.3 上报事件

90 static struct mcu_cec_key_table mcu_cec_input_key[] = {91     {0x40, KEY_POWER},     //power off 92     {0x30, KEY_POWER},     //power off 93     {0x01, KEY_UP},94     {0x02, KEY_DOWN},95     {0x03, KEY_LEFT},96     {0x04, KEY_RIGHT},  97     {0x41, KEY_VOLUMEUP},98     {0x42, KEY_VOLUMEDOWN},99     {0x43, KEY_BACK},       //mute
100     {0x2b, KEY_ENTER},
101     {0x20, KEY_0},
102     {0x21, KEY_1},
103     {0x22, KEY_2},
104     {0x23, KEY_3},
105     {0x24, KEY_4},
106     {0x25, KEY_5},
107     {0x26, KEY_6},
108     {0x27, KEY_7},
109     {0x28, KEY_8},
110     {0x29, KEY_9},
111     {0x2a, KEY_DOT},
112     {0xd, KEY_BACK},
113     {0x48, KEY_REWIND},
114     {0x46, KEY_PLAYPAUSE},
115     {0x49, KEY_FASTFORWARD},
116     {0x44, KEY_PLAYPAUSE},
117     {0x45, KEY_STOP},
118     {0x71, KEY_MENU},//KEY_BLUE
119     {0x74, KEY_HOME},//KEY_YELLOW KEY_HOME
120     {0xa, KEY_MENU},//KEY_BLUE
121     {0x09, KEY_HOME},//KEY_YELLOW KEY_HOME
122     {0x4b, KEY_NEXTSONG},
123     {0x4c, KEY_PREVIOUSSONG},
124 };127 static unsigned char cec_get_keycode(unsigned char cecCode)
128 {
129     int i;
130 
131     for (i = 0; i < sizeof(mcu_cec_input_key)/sizeof(struct mcu_cec_key_table); i++){
132         if (mcu_cec_input_key[i].cecCode == cecCode){
133             return mcu_cec_input_key[i].keyCode;
134         }
135     }
136         return 0;
137 }426 static void mcu_cec_report_standby_task(struct work_struct *work)
427 {
428     int ret;
429     unsigned char ceckey;
430     unsigned char keyCode;
431     cec_int_status mcu_value;
432     struct mcu_cec *mcu_cec = container_of(work, struct mcu_cec, work);
433     mutex_lock(&mcu_cec->m_lock);
434     ret = regmap_read(mcu_cec->regmap, CEC_INT_STATUS, (int *)&mcu_value.status);
435     if (ret) {
436           dev_err(&mcu_cec->i2c->dev, "read 0x%x failed\n", CEC_INT_STATUS);
437           goto failed;
438     }
439     if(mcu_value.status_bit.req_standby == 1){
440          mcu_value.status_bit.req_standby = 0;
441          ret = regmap_write(mcu_cec->regmap, CEC_INT_STATUS, (unsigned int)mcu_value.status);
442          if (ret) {
443                 dev_err(&mcu_cec->i2c->dev, "write 0x%x failed\n", CEC_INT_STATUS);
444          }
445          input_event(mcu_cec->input_dev, EV_KEY, KEY_F12, 1);
446          input_sync(mcu_cec->input_dev);
447          input_event(mcu_cec->input_dev, EV_KEY, KEY_F12, 0);//KEY_WAKEUP
448          input_sync(mcu_cec->input_dev);
449          pr_err("report host stanby\n");
450     }else if(mcu_value.status_bit.remote_control == 1){
451          mcu_value.status_bit.remote_control = 0;
452          ret = regmap_write(mcu_cec->regmap, CEC_INT_STATUS, (unsigned int)mcu_value.status);
453          if (ret) {
454                 dev_err(&mcu_cec->i2c->dev, "write 0x%x failed\n", CEC_INT_STATUS);
455          }
456 
457         ret = regmap_read(mcu_cec->regmap, CEC_REMOTE_KEY, (unsigned int *)&ceckey);
458         if (ret) {
459           dev_err(&mcu_cec->i2c->dev, "read 0x%x failed\n", CEC_INT_STATUS);
460           goto failed;
461         }
462 
463         keyCode = cec_get_keycode(ceckey);
464 
465         {
466                 mcu_cec->keycode = keyCode;
467                 if (mcu_cec->keycode == KEY_POWER)
468                 {
469                         if(mcu_cec->input_dev){
470                                 input_event(mcu_cec->input_dev, EV_KEY, KEY_POWER, 1);
471                                 input_sync(mcu_cec->input_dev);
472                                 input_event(mcu_cec->input_dev, EV_KEY, KEY_POWER, 0);//KEY_WAKEUP
473                                 input_sync(mcu_cec->input_dev);
474                         }
475                 }else {
476                         input_event(mcu_cec->input_dev, EV_KEY, mcu_cec->keycode, 1);
477                         input_sync(mcu_cec->input_dev);
478                         input_event(mcu_cec->input_dev, EV_KEY, mcu_cec->keycode, 0);
479                         input_sync(mcu_cec->input_dev);
480                 }
481         }
482 
483 
484         // report_cec_input_keycode(mcu_cec);
485     }
486     enable_irq(mcu_cec->i2c->irq);
487 failed:
488     mutex_unlock(&mcu_cec->m_lock);
489 
490 }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1551254.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

如何做好接口测试?||关于京东平台商品API接口测试

探讨主题&#xff1a;如何做好接口测试&#xff1f; 一、接口测试简介 1、什么是接口测试&#xff1f; 接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换&#xff0c;传递和控制…

Clocking System

文章目录 1. 介绍2. 时钟源2.1 scillator Circuit (OSC)2.1.1 外部时钟输入模式2.1.2 外部晶体/陶瓷谐振器模式2.1.3 振荡器的配置2.1.4 Oscillator Watchdog 2.2 Back-up Clock 3. 锁相环&#xff08;PLL&#xff09;3.1 系统锁相环3.1.1 Features3.1.2 框图 3.2.外设锁相环3.…

普中51单片机

参考&#xff1a;51单片机快速入门教程2022&#xff08;普中51开发板A2新版&#xff09;--绍兴文理学院元培学院《单片机原理与应用》课程建设_哔哩哔哩_bilibili 1.以管理员启动&#xff0c;破解

python-pptx 中 placeholder 和 shape 有什么区别?

在 python-pptx 库中&#xff0c;placeholder 和 shape 是两个核心概念。虽然它们看起来相似&#xff0c;但在功能和作用上存在显著的区别。为了更好地理解这两个概念&#xff0c;我们可以通过它们的定义、使用场景以及实际代码示例来剖析其差异。 Python-pptx 的官网链接&…

KA客户关系管理策略全解析

在当今商业竞争日益激烈的环境中&#xff0c;如何有效管理和维护关键客户关系成为企业制胜的关键。无论是初创企业还是跨国公司&#xff0c;都面临着同样的挑战&#xff0c;那就是如何通过精准的客户关系管理策略&#xff0c;不仅保留现有客户&#xff0c;还能不断拓展新的商业…

成都网页制作中小企业网站建设的成本与预算指南

成都网页制作中小企业网站建设的成本与预算指南 在数字化时代&#xff0c;中小企业越来越重视网络宣传和线上业务的拓展&#xff0c;网站成为了企业形象的重要展示窗口。然而&#xff0c;对于许多中小企业而言&#xff0c;如何合理规划网站建设的成本与预算&#xff0c;往往是一…

java将word转pdf

poi工具转换 <!-- 处理PDF --><dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId>fr.opensagres.poi.xwpf.converter.pdf-gae</artifactId><version>2.0.3</version></dependency>这个工具使用了poi…

Thinkphp/Laravel小型超市进销存管理系统的设计与实现

目录 技术栈和环境说明具体实现截图设计思路关键技术课题的重点和难点&#xff1a;框架介绍数据访问方式PHP核心代码部分展示代码目录结构解析系统测试详细视频演示源码获取 技术栈和环境说明 采用PHP语言开发&#xff0c;开发环境为phpstudy 开发工具notepad并使用MYSQL数据库…

即插即用篇 | DenseNet卷土重来! YOLOv10 引入全新密集连接卷积网络 | ECCV 2024

本改进已同步到YOLO-Magic框架! 本文重新审视了密集连接卷积网络(DenseNets),并揭示了其在主流的ResNet风格架构中被低估的有效性。我们认为,由于未触及的训练方法和传统设计元素没有完全展现其能力,DenseNets的潜力被忽视了。我们的初步研究表明,通过连接实现的密集连接…

墙绘艺术在线交易:SpringBoot技术解析

2 相关技术 2.1 SSM框架介绍 本课题程序开发使用到的框架技术&#xff0c;英文名称缩写是SSM&#xff0c;在JavaWeb开发中使用的流行框架有SSH、SSM、SpringMVC等&#xff0c;作为一个课题程序采用SSH框架也可以&#xff0c;SSM框架也可以&#xff0c;SpringMVC也可以。SSH框架…

使用激光定高需要注意的问题以及效果测试与读取

使用激光定高需要注意的问题 飞控参数要修改EKF2_HGT_REF 需要改成 range sensor 此时&#xff0c;PX4会选择融合来自激光定高模块传输的高度数据&#xff0c;而不再使用其他数据来源如动捕的z数据 (如果用vision的话手动向vision_pose里面发数据也不是不行【狗头】) 效果测…

PCL 均匀下采样

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 均匀下采样 2.1.2 可视化原始点云和下采样后的点云 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff0…

SSL VPN | Easyconnect下载安装使用 (详尽)

EasyConnect是一款远程连接工具&#xff0c;为用户提供简便、快捷的远程访问和控制解决方案。 目录 下载 安装 使用 卸载 下载 通过链接进入官网技术支持板块 深信服技术支持-简单、高效、自助化服务 (sangfor.com.cn)https://support.sangfor.com.cn/ 选择软件下载 在安…

知识产权为什么是企业申报“专精特新”的核心驱动力?

企业要想在激烈的市场竞争中脱颖而出&#xff0c;必须拥有独特的核心竞争力和持续的创新能力。而“专精特新”作为国家重点扶持的企业发展方向&#xff0c;更是对企业在专业化、精细化、特色化、新颖化方面提出了更高要求。在这一背景下&#xff0c;知识产权无疑成为了企业申报…

MIC麦克风工作原理:【图片+公式计算讲解】

Mic一个声电传换装置&#xff0c;广泛应用于电子产品中&#xff0c;最常见的就是手机。我们通常也叫它送话器&#xff0c;今天我们就来好好学习一下Mic的电路和它的工作原理。 1&#xff1a;电路结构图 1&#xff1a;C&#xff1a;Mic的核心部件&#xff0c;是一个可以膜片震动…

数据增强之imgaug的使用

包的导入 path = r"D:\\" # sometimes = lambda aug: iaa.Sometimes(0.5, aug) img = cv2.imread("D:\\photo\\test.jpg") img = cv2.resize(img,(128,128)) # img = cv2.cvtColor(img,cv2.COLOR_RGBA2GRAY) cv2.imwrite(path+"img.jpg",img)随…

上海我店:创新模式引领本地生活新风尚

近年来&#xff0c;一个名为“上海我店”的新兴平台在网络空间中迅速崛起&#xff0c;其公布的业绩令人瞩目——在短短三年内&#xff0c;交易流水已跨越百亿大关&#xff0c;并在最近一个月内迎来了近百万的新增注册用户。这一强劲的增长势头&#xff0c;无疑吸引了众多商家和…

双十一了还在纠结买啥的你,五款好物分享!

​双十一马上要到了&#xff0c;大家也看了各个平台双十一优惠政策了吧&#xff0c;想必还有些朋友没有想要要入手哪些好东西。莫慌&#xff01;作为智能科技博主的我最了解你们了&#xff0c;这就带着双十一必买的好物清单来跟大家见面&#xff01;如果你还不确定双十一到底买…

手机三网状态实时查询分享

我们演示如何使用Python对接手机号在网状态API接口。 以下是详细的接口文档&#xff1a;https://www.tanshuapi.com/market/detail-123 首先&#xff0c;您需要注册并获取API密钥。假设您已经拥有API密钥&#xff0c;接下来是具体的实现步骤。 编写Python代码 以下是一个Py…

地图资源下载工具(geodatatool)下载 亚洲 8 米 DEM数据

本数据集提供的 DEM 镶嵌图是由 DigitalGlobe 卫星的超高分辨率 (VHR) 沿轨和跨轨立体图像生成的。为了生成 DEM 镶嵌图块&#xff0c;超过 4000 个 DEM 条带与加权平均 镶嵌程序合并&#xff0c;以减少错误并消除接缝。镶嵌图块为 100 公里 x 100 公里&#xff0c;8 米处为 …