[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 }
相关文章:
[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_se…...
2023_Spark_实验十:Centos_Spark Local模式部署
参考这篇博客:【Centos8_配置单节点伪分布式Spark环境】_centos8伪分布式环境搭建-CSDN博客...
pyecharts-快速入门
pyecharts文档:渲染图表 - pyecharts - A Python Echarts Plotting Library built with love. pyecharts-gallery文档:中文简介 - Document (pyecharts.org) 一、快速入门案例 from pyecharts.charts import Barbar Bar() bar.add_xaxis(["衬衫…...
vue3打包疯狂报错
打包的时候报错很多Cannot find name ‘xxx‘ 。 但是npm run dev 是运行正常的。 解决方法:package.json中的vue-tsc --noEmit 删掉就可以了。 例如: 这是原来的 {"scripts": {"dev": "vite","build": &quo…...
STM32 软件触发ADC采集
0.91寸OLED屏幕大小的音频频谱,炫酷! STM32另一个很少人知道的的功能——时钟监测 晶振与软件的关系(深度理解) STM32单片机一种另类的IO初始化方法 ADC是一个十分重要的功能,几乎任何一款单片机都会包含这个功能&a…...
Android SystemUI组件(08)睡眠灭屏 锁屏处理流程
该系列文章总纲链接:专题分纲目录 Android SystemUI组件 本章关键点总结 & 说明: 说明:本章节持续迭代之前章节的思维导图,主要关注左侧上方锁屏分析部分 睡眠灭屏 即可。 Power按键的处理逻辑最终是由PhoneWindowManager来完…...
C# 表达式与运算符
本课要点: 1、表达式的基本概念 2、常用的几种运算符 3、运算符的优先级 4、常见问题 一 表达式 表达式是由运算符和操作数组成的。、-、*和/等都是运算符,操作数包括文本、常量、变量和表达式等。 二 算术运算符 2.1 算术运算符的使用 三 常见错误 …...
SpringBoot--最大连接数和最大并发数
原文网址:SpringBoot--最大连接数和最大并发数-CSDN博客 简介 本文介绍SpringBoot的最大连接数和最大并发数。 配置 SpringBoot默认使用tomcat处理请求。tomcat可以指定连接数、线程数等配置。 server:tomcat:# 请求处理线程都在使用中时,新连接请求…...
CF687D Dividing Kingdom II 题解
Description 给定一个 n n n 个点、 m m m 条边的图,有 q q q 次询问,每次询问一个 [ l , r ] [l,r] [l,r] 的区间,求将 n n n 个点分为两个部分后,编号在 [ l , r ] [l,r] [l,r] 内的边中,两端点属于同一部分的…...
高空抛物AI检测算法:精准防控,技术革新守护城市安全
近年来,随着城市化进程的加速,高楼大厦如雨后春笋般涌现,但随之而来的高空抛物问题却成为城市管理的一大难题。高空抛物不仅严重威胁行人的安全,还可能引发法律纠纷和社会问题。为了有效预防和减少高空抛物事件的发生,…...
html+css+js实现Collapse 折叠面板
实现效果: HTML部分 <div class"collapse"><ul><li><div class"header"><h4>一致性 Consistency</h4><span class"iconfont icon-jiantou"></span></div><div class"…...
RM服务器研究(一)
客户端默认端口是10100: MultiPort.dll BOOL sub_10001070() { UINT v0; // esi BOOL result; // eax CHAR KeyName; // [espCh] [ebp-10Ch] DWORD flOldProtect; // [esp10h] [ebp-108h] CHAR Buffer; // [esp14h] [ebp-104h] char v5; // [esp15h] [e…...
云岚到家xxl job 配置
调度中心: 负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码; 主要职责为执行器管理、任务管理、监控运维、日志管理等 任务执行器: 负责接收调度请求并执行任务逻辑; 主要职责是执行任…...
国内动态短效sk5
HTTP爬虫代理,软件测试, 动态转发IP方案,全高匿名,私密IP,固定网关将您每次请求的HTTP重定向到不同的后端IP,支持API;指路小熊IP https://www.xiaoxiongip.com?fromqkJWgD可测...
【路径规划】路径平滑算法,A星算法拐点的圆弧化处理
摘要 A算法广泛应用于路径规划中,但其生成的路径通常在拐点处呈现不平滑的折线。为了提升路径的平滑性,本文提出了一种基于圆弧的平滑处理方法,用于对A算法产生的路径拐点进行优化。通过在MATLAB中进行仿真验证,该方法能够有效减…...
【寻找one piece的算法之路】——双指针算法!他与她是否会相遇呢?
💐个人主页:初晴~ 📚相关专栏:寻找one piece的刷题之路 什么是双指针算法 双指针算法是一种常用的编程技巧,尤其在处理数组和字符串问题时非常有效。这种方法的核心思想是使用两个指针来遍历数据结构,这两…...
UFS 3.1架构简介
整个UFS协议栈可以分为三层:应用层(UFS Application Layer(UAP)),传输层(UFS Transport Layer(UTP)),链路层(UIC InterConnect Layer(UIC))。应用层发出SCSI命令(UFS没有自己的命令使用的是简化的SCSI命令),在传输层将SCSI分装为UPIU,再经过链路层将命令发送给Devices。下…...
注册安全分析报告:科研诚信查询平台无验证方式导致安全隐患
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 1. 暴力破解密码,造成用户信息泄露 2. 短信盗刷的安全问题,影响业务及导致用户投诉 3. 带来经济损失,尤其是后付费客户,风险巨大,造…...
04.useTitle
在 React 应用中,动态更新页面标题是提升用户体验的一个重要方面。它可以让用户更清楚地知道当前页面的内容或状态,特别是在单页应用(SPA)中。useTitle 钩子提供了一种简单而有效的方式来管理文档标题。以下是如何实现和使用这个自定义钩子: const useTitle = title =>…...
ROS2中的srv、action、发布订阅三种方式
ROS2中的srv、action、发布订阅三种方式 以下是ROS2中srv、action、发布订阅三种方式的差异和使用场景的表格形式呈现: 特性/方式srv(服务)action(动作)发布订阅(Publish-Subscribe)通信模式请…...
HarmonyOS/OpenHarmony 自定义弹窗页面级层级控制解决方案
关键词:CuntomDialog自定义弹窗、SubWindow子窗口、页面级、弹窗层级控制、鸿蒙、弹窗展示层级异常 问题存在API版本:API10 - API12(该问题已反馈,期望后续官方能增加页面级控制能力) 在正常的鸿蒙app开发过程中&…...
C/C++进阶(一)--内存管理
更多精彩内容..... 🎉❤️播主の主页✨😘 Stark、-CSDN博客 本文所在专栏: 学习专栏C语言_Stark、的博客-CSDN博客 其它专栏: 数据结构与算法_Stark、的博客-CSDN博客 项目实战C系列_Stark、的博客-CSDN博客 座右铭&a…...
docker-compose 快速部署clickhouse集群
在本教程中,我们将学习如何使用 Docker Compose 部署一个带有三节点的 ClickHouse 集群,并使用 ZooKeeper 作为分布式协调服务。 前提条件 注意事项: 镜像版本号注意保持一致 [zookeeper:3.7, clickhouse/clickhouse-server:22.5.4]config…...
闯关训练三:Git 基础知识
任务1: 破冰活动:自我介绍 点击Fork目标项目,创建一个新的Fork 获取仓库链接 在连接好开发机的vscode终端中逐行执行以下代码: git clone https://github.com/KelvinIII/Tutorial.git # 修改为自己frok的仓库 cd Tutorial/ git branch -a g…...
Java--IO基本流
IO流 概述 生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了ctrls ,可能文件就白白编辑了。当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢?键盘…...
结合大语言模型的机械臂抓取操作简单介绍
一、大语言模型与机械臂抓取的基本操作 1. 大语言模型简介 大语言模型是基于深度学习技术构建的自然语言处理模型,能够生成、理解和处理文本信息。这些模型通过训练大量的文本数据,学习语法、上下文和常识,能够执行多种任务,如文…...
Vivado - BD(差分时钟、简单分频、RESET、KEY)
目录 1. 简介 1.1 要点 1.2 buffer 介绍 2. vivado 工程 2.1 Block Design 2.2 IBUFDS 2.3 BUFGCE_DIV 2.4 Processor System Reset 2.5 key_mod 2.6 led_drv 3. 编译与调试 3.1 XDC 3.2 Debug 4. 总结 1. 简介 1.1 要点 了解 Utility Buffer v2.2 中的 Buffer…...
7--苍穹外卖-SpringBoot项目中套餐管理 详解(一)
前言 目录 新增套餐 需求分析和设计 代码开发 根据分类id查询菜品 Controller层 Service层 ServiceImpl层 Mapper层 DishMapper.xml 新增套餐 实体类 mapper层 Service层 ServiceImpl层 Mapper层 SetmealMapper.xml setmealDishMapper.xml 套餐分页查询 需求分…...
【尚硅谷】RocketMQ 消息队列学习笔记
RocketMQ 和 Kafka 消息队列概念比较? 好的!RocketMQ 和 Kafka 都是分布式消息队列系统,它们的核心概念有很多相似之处,但在具体实现和命名上有所不同。下面我通过一个表格来对比 RocketMQ 和 Kafka 中的五个概念:消息…...
C题(三)芝麻开门 --- strcmp函数应用
场景一:“芝麻开门 ”是通往C语言的大门的暗号,现在你需要说对暗号,大门才会打开。 【分解目标1】字符串的输入 char arr[20] { 0 }; //字符的集合---字符串(数组表示)//20为预定的数组的大小scanf("%s", a…...
焦作做网站公司/整站优化代理
我们在进行网络编程的时候,经常需要获取本地主机,网卡的信息,我们代码实现如下。#include "../common/initsock.h"#include <windows.h> #include <stdio.h> #include <Iphlpapi.h>#include "protoutils.h&…...
个人网站备案icp/长春seo排名收费
最全的BAT大厂面试题整理 版权声明:本文为博主原创文章,未经博主允许不得转载。https://www.jianshu.com/p/c70989bd5f29 转载请标明出处: https://www.jianshu.com/p/c70989bd5f29 本文出自 AWeiLoveAndroid的博客 临近年关,又…...
网站做产品的审核工作内容/优化标题关键词技巧
提示:这是MyBatis的第六篇,有兴趣的话可以看下: 一: MyBatis复习笔记整理 二: MyBatis的resultMap标签 自定义封装返回值类型 三: MyBatis动态SQL官方文档 四: MyBatis动态SQL学习笔记 五&…...
企业门户网站需求分析/百度网盘登录入口官网
Sublime Text 2报“Decode error - output not utf-8”错误的解决办法 作者:chszs,转载需注明。作者博客主页:http://blog.csdn.net/chszs 正如我在 上一篇博客《Sublime Text 2搭建Java开发环境》中所述,在Sublime Text 2上搭建J…...
忻州 建网站/如何制定会员营销方案
单行函数: 是为了完成某些特定操作的功能支持,方便数据的相关开发; 语法; funcation_name(列|表达式[参数1,参数2]) 单行函数: 字符函数:接受数据返回具体的字符信息; 数值函数…...
学生模拟网站开发项目/锦州seo推广
本文系阅读阅读原章节后总结概括得出。由于需要我进行一定的概括提炼,如有不当之处欢迎读者斧正。如果你对内容有任何疑问,欢迎共同交流讨论。 关于什么时候应该用强制类型解封,网上有各种的答案,有人说永远别用,有人说…...