pinctrl子系统学习笔记
一、背景
cpu的gpio引脚可以复用成多个功能,如可以配置成I2C或者普通GPIO模式。配置方式一般是通过写引脚复用的配置寄存器,但是不同芯片厂商配置寄存器格式内容各不相同,设置引脚复用无法做到通用且自由的配置,只能在启动初始化时候在soc驱动初始化时对每个引脚配置好。 linux 3.0之后内核中抽象出了pinctrl子系统,每个soc注册设置引脚复用的方法以及将soc的每个引脚可选的复用功能。
二、pinctrl实现
2.1 API
# 用于soc向pinctrl系统中注册设置引脚复用方法以及设置引脚复用状态
# driver_data用于soc驱动私有数据,一般用来保存pin 引脚的可选复用功能列表。
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,struct device *dev, void *driver_data)# 设置引脚复用状态,用来设置引脚复用状态
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)# 用于具体设备驱动(i2c等)关联pinctrl节点,以及设置默认复用模式,设备树解析时调用
int pinctrl_bind_pins(struct device *dev)
2.2 数据结构
数据结构关系图:
这里主要有几个对象:
- pin_group 和function : 引脚复用一般是多个引脚组成一个group,一起设置复用状态的。多个引脚复用为某个功能,成为function。
- pin_conf :设置引脚的上下拉电阻等电气参数
- pin_mux:设置引脚复用
pinctrl_desc
核心数据结构是pinctrl_desc,pinctrl的全局描述。
struct pinctrl_desc {const char *name;/*系统种有多少个pinctrl*/const struct pinctrl_pin_desc *pins;unsigned int npins;/*引脚控制的操作集,不同芯片方案不同*/const struct pinctrl_ops *pctlops;const struct pinmux_ops *pmxops;const struct pinconf_ops *confops;struct module *owner;
};
pinctrl_pin_desc
描述系统中的所有引脚,drv_data保存驱动私有数据。
struct pinctrl_pin_desc {unsigned number;const char *name;void *drv_data;
};
pinctrl_ops
回调函数,获取pin group的操作,以及解析dts。
struct pinctrl_ops {/*获取系统中有多少个pin groups*/int (*get_groups_count) (struct pinctrl_dev *pctldev);/*获取指定group(由索引selector指定)的名称,由select确认*/const char *(*get_group_name) (struct pinctrl_dev *pctldev,unsigned selector);/*获取指定group的所用pin信息*/int (*get_group_pins) (struct pinctrl_dev *pctldev,unsigned selector,const unsigned **pins,unsigned *num_pins);void (*pin_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s,unsigned offset);/*将对应驱动转换成pin map*/int (*dt_node_to_map) (struct pinctrl_dev *pctldev,struct device_node *np_config,struct pinctrl_map **map, unsigned *num_maps);void (*dt_free_map) (struct pinctrl_dev *pctldev,struct pinctrl_map *map, unsigned num_maps);
};
pinconf_ops
配置管脚状态:pinctrl也提供管脚状态如(上下拉、开漏等)的接口。
struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONFbool is_generic;
#endif/*获取pin脚的当前状态*/int (*pin_config_get) (struct pinctrl_dev *pctldev,unsigned pin,unsigned long *config);/*设置pin脚状态*/int (*pin_config_set) (struct pinctrl_dev *pctldev,unsigned pin,unsigned long *configs,unsigned num_configs);/*获取或者设置指定pin group的配置项*/int (*pin_config_group_get) (struct pinctrl_dev *pctldev,unsigned selector,unsigned long *config);int (*pin_config_group_set) (struct pinctrl_dev *pctldev,unsigned selector,unsigned long *configs,unsigned num_configs);int (*pin_config_dbg_parse_modify) (struct pinctrl_dev *pctldev,const char *arg,unsigned long *config);void (*pin_config_dbg_show) (struct pinctrl_dev *pctldev,struct seq_file *s,unsigned offset);void (*pin_config_group_dbg_show) (struct pinctrl_dev *pctldev,struct seq_file *s,unsigned selector);void (*pin_config_config_dbg_show) (struct pinctrl_dev *pctldev,struct seq_file *s,unsigned long config);
};
pinmux_ops
设置或获取引脚复用情况,对应硬件是iomux。设置某个pin为某个function,通过set_mux设置function selecter。 一个group设置复用状态。
struct pinmux_ops {int (*request) (struct pinctrl_dev *pctldev, unsigned offset);int (*free) (struct pinctrl_dev *pctldev, unsigned offset);int (*get_functions_count) (struct pinctrl_dev *pctldev);const char *(*get_function_name) (struct pinctrl_dev *pctldev,unsigned selector);int (*get_function_groups) (struct pinctrl_dev *pctldev,unsigned selector,const char * const **groups,unsigned *num_groups);/*将指定的pin group(group_selector)设置为指定的function(func_selector)*/int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector,unsigned group_selector);int (*gpio_request_enable) (struct pinctrl_dev *pctldev,struct pinctrl_gpio_range *range,unsigned offset);void (*gpio_disable_free) (struct pinctrl_dev *pctldev,struct pinctrl_gpio_range *range,unsigned offset);int (*gpio_set_direction) (struct pinctrl_dev *pctldev,struct pinctrl_gpio_range *range,unsigned offset,bool input);bool strict;
};
pinctrl_map
pinctrl_map 某个gpio具体的引脚配置和复用状态,由dts中定义,dt_node_to_map函数解析,和具体设备驱动关联。
struct pinctrl_map {//device的名称const char *dev_name;//pin state的名称const char *name;//该map的类型enum pinctrl_map_type type;//pin controller device的名字const char *ctrl_dev_name;union {struct pinctrl_map_mux mux;struct pinctrl_map_configs configs;} data;
};enum pinctrl_map_type {PIN_MAP_TYPE_INVALID,//不需要任何配置,仅仅为了表示state的存在PIN_MAP_TYPE_DUMMY_STATE,//配置管脚复用PIN_MAP_TYPE_MUX_GROUP,//配置pinPIN_MAP_TYPE_CONFIGS_PIN,//配置pin groupPIN_MAP_TYPE_CONFIGS_GROUP,
};struct pinctrl_map_mux {//group的名字const char *group;//function的名字const char *function;
};struct pinctrl_map_configs {//该pin或者pin group的名字const char *group_or_pin;//configuration数组unsigned long *configs;//配置项的个数unsigned num_configs;
};
三、实现流程
linux6.1.11为例,pinctrl系统融入内核设备树解析以及内核设备驱动模型中支持。以zynq为例从dts配置解析入手,了解pinctrl的工作原理。dts中主要关注:
1)pinctrl 子系统:描述系统多少引脚,每个引脚有多少复用情况。
2)I2C、GPIO、SPI等driver默认配置的pinctrl复用设置
《zynq-7000.dtsi》中,如列出i2C和uart的复用配置,其中只是选择了i2c0_10_grp这个pin_group,并且配置为i2c0的复用功能,具体引脚的引脚号等信息是在代码中定义的。
mux 和 conf的作用?
pinctrl0: pinctrl@700 {compatible = "xlnx,pinctrl-zynq";reg = <0x700 0x200>;syscon = <&slcr>;/*设置引脚的复用状态*/pinctrl_i2c0_default: i2c0-default {mux {/*pin_group*/groups = "i2c0_10_grp";/*复用功能为i2c0*/function = "i2c0";};conf {groups = "i2c0_10_grp";bias-pull-up; #上拉电阻slew-rate = <0>; #引脚转换速率io-standard = <1>;};};pinctrl_uart1_default: uart1-default {mux {groups = "uart1_10_grp";function = "uart1";};conf {groups = "uart1_10_grp";slew-rate = <0>;io-standard = <1>;};conf-rx {pins = "MIO49";bias-high-impedance;};conf-tx {pins = "MIO48";bias-disable;};};}
- 其他驱动如何关联pinctrl以及配置默认引脚复用?
对应驱动中的dts新增如下两个属性,在设备驱动device关联driver时,会解析如下字段,关联pinctrl驱动,并设置引脚复用状态,这里是选择pinctrl_i2c0_default。
&i2c0 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_i2c0_default>;
3.1 驱动注册
《linux-6.1.11\drivers\pinctrl\pinctrl-zynq.c》
构造一个pinctrl_desc结构,然后注册到pinctrl系统中。
static struct pinctrl_desc zynq_desc = {.name = "zynq_pinctrl",.pins = zynq_pins,.npins = ARRAY_SIZE(zynq_pins),.pctlops = &zynq_pctrl_ops,.pmxops = &zynq_pinmux_ops,.confops = &zynq_pinconf_ops,.num_custom_params = ARRAY_SIZE(zynq_dt_params),.custom_params = zynq_dt_params,
#ifdef CONFIG_DEBUG_FS.custom_conf_items = zynq_conf_items,
#endif.owner = THIS_MODULE,
};static int zynq_pinctrl_probe(struct platform_device *pdev){pctrl->pctrl = devm_pinctrl_register(&pdev->dev, &zynq_desc, pctrl);
}
pin引脚定义:
/*定义系统中引脚*/
static const struct pinctrl_pin_desc zynq_pins[] = {PINCTRL_PIN(0, "MIO0"),PINCTRL_PIN(1, "MIO1"),...
}/*引脚可选复用功能*/
enum zynq_pinmux_functions {ZYNQ_PMUX_can0,ZYNQ_PMUX_can1,ZYNQ_PMUX_ethernet0,ZYNQ_PMUX_ethernet1,
}/* pin groups 组,每个引脚功能对应的pin脚定义*/
static const unsigned int ethernet0_0_pins[] = {16, 17, 18, 19, 20, 21, 22, 23,24, 25, 26, 27};
static const unsigned int ethernet1_0_pins[] = {28, 29, 30, 31, 32, 33, 34, 35,36, 37, 38, 39};
static const unsigned int mdio0_0_pins[] = {52, 53};
static const unsigned int mdio1_0_pins[] = {52, 53};/*zynq维护的私有数据结构pin_group组信息,关联上述pin脚*/
static const struct zynq_pctrl_group zynq_pctrl_groups[] = {DEFINE_ZYNQ_PINCTRL_GRP(ethernet0_0),DEFINE_ZYNQ_PINCTRL_GRP(ethernet1_0),DEFINE_ZYNQ_PINCTRL_GRP(mdio0_0),DEFINE_ZYNQ_PINCTRL_GRP(mdio1_0),DEFINE_ZYNQ_PINCTRL_GRP(qspi0_0),DEFINE_ZYNQ_PINCTRL_GRP(qspi1_0),DEFINE_ZYNQ_PINCTRL_GRP(qspi_fbclk),DEFINE_ZYNQ_PINCTRL_GRP(qspi_cs1),DEFINE_ZYNQ_PINCTRL_GRP(spi0_0),
}
对应的ops实现这里不展开,具体写寄存器信息。
3.2 具体驱动关联pinctrl
具体i2c、spi等驱动初始化时如何设置引脚复用情况? dts中定义i2c选择pinctrl_i2c0_default的引脚复用。 pinctrl-names用于表示pinctrl_state,有init、default,init是在驱动初始化的状态,default是默认状态。 pinctrl-0 选择复用情况,这两个属性是pinctrl的标准属性,设备树解析框架中实现,在设备驱动匹配时关联。
&i2c0 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_i2c0_default>;
时机:设备驱动匹配时关联,以及设置默认复用状态。
关键函数:pinctrl_bind_pins
《linux-6.1.11\drivers\base\dd.c》
__driver_probe_device -> really_probe -> pinctrl_bind_pins
- 解析dts,关联pinctrl_map
调用pinconf_generic_dt_node_to_map_all解析dts。关联pinctrl_i2c0_default的pinctrl_map。
pinctrl_bind_pins -> create_pinctrl -> pinctrl_dt_to_map -> pinconf_generic_dt_node_to_map_all
- 设置引脚复用
pinctrl_bind_pins -> pinctrl_select_state (选择default state) -> pinctrl_commit_state-> pinmux_disable_setting (禁用旧的复用)-> pinmux_enable_setting (设置新复用)-> pinconf_apply_setting (设置引脚状态)
/*** pinctrl_bind_pins() - called by the device core before probe* @dev: the device that is just about to probe*/
int pinctrl_bind_pins(struct device *dev)
{int ret;if (dev->of_node_reused)return 0;dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);if (!dev->pins)return -ENOMEM;dev->pins->p = devm_pinctrl_get(dev);if (IS_ERR(dev->pins->p)) {dev_dbg(dev, "no pinctrl handle\n");ret = PTR_ERR(dev->pins->p);goto cleanup_alloc;}dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,PINCTRL_STATE_DEFAULT);if (IS_ERR(dev->pins->default_state)) {dev_dbg(dev, "no default pinctrl state\n");ret = 0;goto cleanup_get;}dev->pins->init_state = pinctrl_lookup_state(dev->pins->p,PINCTRL_STATE_INIT);if (IS_ERR(dev->pins->init_state)) {/* Not supplying this state is perfectly legal */dev_dbg(dev, "no init pinctrl state\n");ret = pinctrl_select_state(dev->pins->p,dev->pins->default_state);} else {ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);}if (ret) {dev_dbg(dev, "failed to activate initial pinctrl state\n");goto cleanup_get;}#ifdef CONFIG_PM/** If power management is enabled, we also look for the optional* sleep and idle pin states, with semantics as defined in* <linux/pinctrl/pinctrl-state.h>*/dev->pins->sleep_state = pinctrl_lookup_state(dev->pins->p,PINCTRL_STATE_SLEEP);if (IS_ERR(dev->pins->sleep_state))/* Not supplying this state is perfectly legal */dev_dbg(dev, "no sleep pinctrl state\n");dev->pins->idle_state = pinctrl_lookup_state(dev->pins->p,PINCTRL_STATE_IDLE);if (IS_ERR(dev->pins->idle_state))/* Not supplying this state is perfectly legal */dev_dbg(dev, "no idle pinctrl state\n");
#endifreturn 0;/** If no pinctrl handle or default state was found for this device,* let's explicitly free the pin container in the device, there is* no point in keeping it around.*/
cleanup_get:devm_pinctrl_put(dev->pins->p);
cleanup_alloc:devm_kfree(dev, dev->pins);dev->pins = NULL;/* Return deferrals */if (ret == -EPROBE_DEFER)return ret;/* Return serious errors */if (ret == -EINVAL)return ret;/* We ignore errors like -ENOENT meaning no pinctrl state */return 0;
}
相关文章:

pinctrl子系统学习笔记
一、背景 cpu的gpio引脚可以复用成多个功能,如可以配置成I2C或者普通GPIO模式。配置方式一般是通过写引脚复用的配置寄存器,但是不同芯片厂商配置寄存器格式内容各不相同,设置引脚复用无法做到通用且自由的配置,只能在启动初始化…...
使用vue-element 的计数器inputNumber,传第三个参数
使用vue-element 的计数器inputNumber。 其中的change 事件中,默认自带两个参数,currentValue和oldValue,分别代表改变后的数和改变前的数, 如果想要传第三个参数, change"(currentValue, oldValue) > numCha…...

如何从0构建一个flask项目,直接上实操!!!
项目结构 首先,创建一个项目目录,结构如下: flask_app/ │ ├── app.py # Flask 应用代码 ├── static/ # 存放静态文件(如CSS、JS、图片等) │ └── style.css # 示例…...
Mongoose连接数据库操作实践
文章目录 介绍特点:Mongoose 使用:创建项目并安装:连接到 MongoDB:定义 Schema:创建模型并操作数据库:创建文档:查询文档:更新文档:删除文档:使用钩子&#x…...
centos 7.9 freeswitch1.10.9环境搭建
亲测版本centos 7.9系统–》 freeswitch1.10.9 一、下载插件 yum install -y git alsa-lib-devel autoconf automake bison broadvoice-devel bzip2 curl-devel libdb4-devel e2fsprogs-devel erlang flite-devel g722_1-devel gcc-c++ gdbm-devel gnutls-devel ilbc2...

Gitlab服务管理和仓库项目权限管理
Gitlab服务管理 gitlab-ctl start # 启动所有 gitlab 组件; gitlab-ctl stop # 停止所有 gitlab 组件; gitlab-ctl restart # 重启所有 gitlab 组件; gitlab-ctl status …...
LLMs之Llama-3:Llama-3.3的简介、安装和使用方法、案例应用之详细攻略
LLMs之Llama-3:Llama-3.3的简介、安装和使用方法、案例应用之详细攻略 目录 相关文章 LLMs之LLaMA:LLaMA的简介、安装和使用方法、案例应用之详细攻略 LLMs之LLaMA-2:LLaMA 2的简介(技术细节)、安装、使用方法(开源-免费用于研究和商业用途…...

OpenCV函数及其应用
1. 梯度处理的Sobel算子函数 功能 Sobel算子是一种用于边缘检测的离散微分算子,它结合了高斯平滑和微分求导,用于计算图像亮度的空间梯度。 参数 src:输入图像。 dst:输出图像。 ddepth:输出图像的深度。 dxÿ…...

vulnhub靶场【DriftingBlues】之3
前言 靶机:DriftingBlues-3,IP地址192.168.1.60 攻击:kali,IP地址192.168.1.16 都采用虚拟机,网卡为桥接模式 主机发现 使用arp-scan -l或netdiscover -r 192.168.1.1/24 信息收集 使用nmap扫描端口 网站探测 访…...

文件上传—阿里云OSS对象存储
目录 一、OSS简介 二、OSS基本使用 1. 注册账号 2. 基本配置 (1) 开通OSS (2) 创建存储空间 (3) 修改权限 (4) 配置完成,上传一张图片,检验是否成功。 (5) 创建AccessKey 三、Java项目集成OSS 1. 导入依赖 2. Result.java代码: …...

mybatis-plus超详细讲解
mybatis-plus (简化代码神器) 地址:https://mp.baomidou.com/ 目录 mybatis-plus 简介 特性 支持数据库 参与贡献 快速指南 1、创建数据库 mybatis_plus 2、导入相关的依赖 3、创建对应的文件夹 4、编写配置文件 5、编写代码 …...

【Linux】--- 进程的概念
【Linux】--- 进程的概念 一、进程概念二、PCB1.什么是PCB2.什么是task_struct(重点!)3.task_struct包含内容 三、task_struct内容详解1.查看进程(1)通过系统目录查看(2)通过ps命令查看…...

Unity NTPComponent应用, 实现一个无后端高效获取网络时间的组件
无后端高效获取网络时间的组件 废话不多说,直接上源码m_NowSerivceTime 一个基于你发行游戏地区的时间偏移, 比如北京时区就是 8, 巴西就是-3,美国就是-5using Newtonsoft.Json; 如果这里报错, 就说明项目没有 NewtonsoftJson插件…...
go语言使用zlib压缩[]byte
在Go语言中,可以使用compress/flate和compress/zlib包来实现对[]byte数据的Zlib压缩。下面是一个简单的示例,展示如何使用这些包来压缩一个字节切片: go package main import ( "bytes" "compress/zlib" "fmt"…...

Windows 配置 Tomcat环境
Windows配置Tomcat 1. 介绍 Tomcat是一个开源的、轻量级的Java应用服务器,在Java Web开发领域应用广泛。以下是关于它的详细介绍: 一、基本概念与背景 定义:Tomcat是Apache软件基金会(Apache Software Foundation)下…...

【python从入门到精通】-- 第六战:列表和元组
🌈 个人主页:白子寰 🔥 分类专栏:重生之我在学Linux,C打怪之路,python从入门到精通,数据结构,C语言,C语言题集👈 希望得到您的订阅和支持~ 💡 坚持…...

Python | 数据可视化中常见的4种标注及示例
在Python的数据可视化中,标注(Annotation)技术是一种非常有用的工具,它可以帮助用户更准确地解释图表中的数据和模式。在本文中,将带您了解使用Python实现数据可视化时应该了解的4种标注。 常见的标注方式 文本标注箭…...

LearnOpenGL学习(高级OpenGL -> 高级GLSL,几何着色器,实例化)
完整代码见:zaizai77/Cherno-OpenGL: OpenGL 小白学习之路 高级GLSL 内建变量 顶点着色器 gl_PointSoze : float 输出变量,用于控制渲染 GL_POINTS 型图元时,点的大小。可用于粒子系统。将其设置为 gl_Position.z 时,可以使点…...

Scala学习记录
dao --------> 数据访问 mode ------> 模型 service ---->业务逻辑 Main -------> UI:用户直接操作,调用Service 改造UI层:...

vue使用pdfh5.js插件,显示pdf文件白屏
pdfh5,展示文件白屏,无报错 实现效果图解决方法(降版本)排查问题过程发现问题查找问题根源1、代码写错了?2、预览文件流的问题?3、pdfh5插件更新了,我的依赖包没更新?4、真相大白 彩蛋 实现效果图 解决方法…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...

给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...