Linux设备模型(十一) - platform设备

news2024/11/15 17:35:02

一,platform device概述

在Linux2.6以后的设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,

会寻找与之匹配的驱动;相反的,在系统每注册一个设备的时候,会寻找与之匹配的设备,而匹配由总线完成。

一个现实的Linux设备和驱动通常都需要挂接在一种总线上,而对于本身依附于PCI、USB、I2C、SPI等的设备而言,这自然不是问题,

但是在嵌入式系统里面,在SoC系统中集成的独立外设控制器、挂接在SoC内存空间的外设等却不依附于此类总线。基于这一背景,Linux

发明了一种虚拟总线,称为platform总线,相应的设备称为platform_device,而驱动称为platform_driver。

所谓的platform_device并不是与字符设备、块设备和网络设备并存的概念,而是linux系统提供的一种附加手段,例如,我们通常把在

SoC内部集成的I2C、RTC、LCD、看门狗等控制器都归纳为platform_device,而他们本身就是字符设备。这些设备有一个基本的特征:可以通过CPU bus直接寻址(例如在嵌入式系统常见的“寄存器”)。

二,platform模块的软件架构

内核中Platform设备有关的实现位于include/linux/platform_device.h和drivers/base/platform.c两个文件中,它的软件架构如下:

由图片可知,Platform设备在内核中的实现主要包括三个部分:

Platform Bus,基于底层bus模块,抽象出一个虚拟的Platform bus,用于挂载Platform设备;

Platform Device,基于底层device模块,抽象出Platform Device,用于表示Platform设备;

Platform Driver,基于底层device_driver模块,抽象出Platform Driver,用于驱动Platform设备。

其中Platform Device和Platform Driver会给其它Driver提供封装好的API,具体可参考后面的描述。

三,platform bus/platform device/platform driver结构体

1,platform_driver

// msm_kernel\include\linux\platform_device.h
struct platform_driver {
    int (*probe)(struct platform_device *);
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
    const struct platform_device_id *id_table; /* 另外这里有一个id_table的指针,该指针和of_match_table、acpi_match_table的功能类似:提供其它方式的设备probe */
    bool prevent_deferred_probe;

    ANDROID_KABI_RESERVE(1);
};

2,platform_device

// msm_kernel\include\linux\platform_device.h
struct platform_device {
    const char    *name; /* 设备的名称,和struct device结构中的init_name意义相同。实际上,该名称在设备注册时,会拷贝到dev.init_name中 */
    int        id; /* 用于标识该设备的ID。内核允许存在多个名称相同的设备。而设备驱动的probe,依赖于名称,Linux采取的策略是:在bus的设备链表中查找device,和对应的device_driver比对name,如果相同,则查看该设备是否已经绑定了driver(查看其dev->driver指针是否为空),如果已绑定,则不会执行probe动作,如果没有绑定,则以该device的指针为参数,调用driver的probe接口。
因此,在driver的probe接口中,通过判断设备的ID,可以知道此次驱动的设备是哪个*/
    bool        id_auto; /* 指示在注册设备时,是否自动赋予ID值 */
    struct device    dev; /* 真正的设备(Platform设备只是一个特殊的设备,因此其核心逻辑还是由底层的模块实现) */
    u64        platform_dma_mask;
    struct device_dma_parameters dma_parms;
    u32        num_resources;
    struct resource    *resource; /* 该设备的资源描述,由struct resource(include/linux/ioport.h)结构抽象 */

    const struct platform_device_id    *id_entry;
    char *driver_override; /* Driver name to force a match */

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;

    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
};

3,platform bus

// msm_kernel\drivers\base\platform.c
struct bus_type platform_bus_type = {
    .name        = "platform",
    .dev_groups    = platform_dev_groups,
    .match        = platform_match,
    .uevent        = platform_uevent,
    .dma_configure    = platform_dma_configure,
    .pm        = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

四,platform bus/platform device/platform driver注册

1,platform bus注册

struct device platform_bus = {
    .init_name    = "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);

int __init platform_bus_init(void)
{
    int error;

    early_platform_cleanup(); /* 清除所有和Early device/driver相关的代码。因为执行到这里的时候,证明系统已经完成了Early阶段的启动,转而进行正常的设备初始化、启动操作,所以不再需要Early Platform相关的东西。 */

    error = device_register(&platform_bus); /* 在sysfs中创建/sys/devices/platform目录,所有的platform设备都会包含在此目录下 */
    if (error) {
        put_device(&platform_bus);
        return error;
    }
    error =  bus_register(&platform_bus_type); /* 在sysfs中创建/sys/bus/platform目录并在此目录中创建如下attributes和devices目录,drivers目录 */
    /*
    lynkco:/sys/bus/platform # ls
    devices  drivers  drivers_autoprobe  drivers_probe  uevent
    */
    if (error)
        device_unregister(&platform_bus);
    of_platform_register_reconfig_notifier();
    return error;
}

2,platform device注册

platform_device_register - add a platform device to device hierarchy

// msm_kernel\drivers\base\platform.c
platform_device_register(struct platform_device *pdev)
----device_initialize(&pdev->dev);
----platform_device_add(pdev);
--------pdev->dev.parent = &platform_bus; //该设备的sysfs目录/sys/devices/platform/xxx_device
--------pdev->dev.bus = &platform_bus_type; //该设备的bus type定义为platform_bus_type
--------dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id); //对于多个同名的设备,可以使用ID区分,在这里将实际名称修改为“name.id”的形式
--------insert_resource(p, r); /* 调用resource模块的insert_resource接口,将该设备需要使用的resource统一管理起来(我们知道,在这之前,只是声明了本设备需要使用哪些resource,但resource模块并不知情,也就无从管理,因此需要告知)。 */
--------device_add(&pdev->dev); // 将内嵌的struct device变量添加到内核中
            ......

3,platform driver注册

platform_driver_register - register a driver for platform-level devices

// msm_kernel\drivers\base\platform.c
platform_driver_register(drv)
----__platform_driver_register(drv, THIS_MODULE)
--------drv->driver.bus = &platform_bus_type; //该driver的bus type设置为platform_bus_type
------------drv->driver.probe = platform_drv_probe; /* 如果该platform driver提供了probe、remove、shutdown等回调函数,将该它内嵌的struct driver变量的probe、remove、shutdown等指针,设置为platform模块提供函数,包括platform_drv_probe、platform_drv_remove和platform_drv_shutdown。因为probe等动作会从struct driver变量开始,经过platform_drv_xxx等接口的转接就可以到达platform diver自身的回调函数中。*/
------------drv->driver.remove = platform_drv_remove;
------------drv->driver.shutdown = platform_drv_shutdown;
--------driver_register(&drv->driver); //将内嵌的struct driver变量添加到内核中
            ......

五,platform device/platform driver提供的API

1,platform driver提供的API

extern int platform_driver_register(struct platform_driver *);
extern void platform_driver_unregister(struct platform_driver *); //platform driver的注册、注销接口

extern int platform_driver_probe(struct platform_driver *driver,
                 int (*probe)(struct platform_device *)); //主动执行probe动作

static inline void *platform_get_drvdata(const struct platform_device *pdev);
static inline void platform_set_drvdata(struct platform_device *pdev,
                                         void *data); //设置或者获取driver保存在device变量中的私有数据

2,platform device提供的API

extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *); //Platform设备的注册/注销接口,和底层的device_register等接口类似

extern struct resource *platform_get_resource(struct platform_device *,
                                               unsigned int, unsigned int);
extern int platform_get_irq(struct platform_device *, unsigned int);
extern struct resource *platform_get_resource_byname(struct platform_device *,
                                                      unsigned int,
                                                      const char *);
extern int platform_get_irq_byname(struct platform_device *, const char *); //通过这些接口,可以获取platform_device变量中的resource信息,以及直接获取IRQ的number等等

extern int platform_device_add_resources(struct platform_device *pdev,
                                          const struct resource *res,
                                          unsigned int num); //向platform device中增加资源描述
                                          
extern int platform_device_add_data(struct platform_device *pdev,
                                     const void *data, size_t size); //向platform device中添加自定义的数据(保存在pdev->dev.platform_data指针中)

六,platform device resource/platform data的定义与获取

1,struct resource结构体介绍

在platform device结构体的定义中关于device resource的定义如下

    u32        num_resources;
    struct resource    *resource;

它们描述了platform_device的资源,资源本身由resource结构体描述

/*
* Resources are tree-like, allowing
* nesting etc..
*/
struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    unsigned long desc;
    struct resource *parent, *sibling, *child;

    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
    ANDROID_KABI_RESERVE(3);
    ANDROID_KABI_RESERVE(4);
};

我们通常关心start、end和flags这3个字段,它们分别标明了资源的开始值、结束值和类型,flags可以为IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA等。

start、end的含义会随着flags而变更,如当flags为IORESOURCE_MEM时,start、end分别表示该platform_device占据的内存的开始地址和结束地址;

当flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的中断号的开始值和结束值,如果只使用了一个中断号,开始值和结束值相同。

对于同种类型的资源而言,可以有多份,例如说某设备占据了两个内存区域,则可以定义两个IORESOURCE_MEM资源。

系统中所有的platform_device,都可以在/sys/devices/platform/路径下查看。另外,系统中所有的platform_device,有来自设备树的,也有来自.c文件中注册的。那么,我们怎么知道哪些platform_device是来自设备树,哪些是来自.c文件中注册的?

可以查看该platform_device的相关目录下,是否有of_node,如果有of_node,那么这个platform_device就来自于设备树;否则,来自.c文件。

2,来自.c文件中注册的platform_device

2.1 example
// msm_kernel\arch\arm\mach-ep93xx\core.c

static struct usb_ohci_pdata ep93xx_ohci_pdata = {
    .power_on    = ep93xx_ohci_power_on,
    .power_off    = ep93xx_ohci_power_off,
    .power_suspend    = ep93xx_ohci_power_off,
};

static struct resource ep93xx_ohci_resources[] = {
    DEFINE_RES_MEM(EP93XX_USB_PHYS_BASE, 0x1000),
    DEFINE_RES_IRQ(IRQ_EP93XX_USB),
};

static struct platform_device ep93xx_ohci_device = {
    .name        = "ohci-platform",
    .id        = -1,
    .num_resources    = ARRAY_SIZE(ep93xx_ohci_resources),
    .resource    = ep93xx_ohci_resources,
    .dev        = {
        .dma_mask        = &ep93xx_ohci_dma_mask,
        .coherent_dma_mask    = DMA_BIT_MASK(32),
        .platform_data        = &ep93xx_ohci_pdata,
    },
};
platform_device_register(&ep93xx_ohci_device);

设备除了可以在BSP中定义资源以外,还可以附加一些数据信息,因为对设备的硬件描述除了中断、内存等标准资源以外,还可能有一些配置信息,而这些配置信息也依赖于板,不适宜直接放置在设备驱动上。因此platform也提供了platform_data的支持,platform_data的形式是由每个驱动自定义的,如对于usb ohci设备而言,platform_data为一个usb_ohci_pdata结构体,完成定义后将可以将PM operation相关的接口信息放入platform_data中。

在usb ohci驱动msm-kernel/drivers/usb/host/ohci-platform.c的probe()函数中,通过如下方式就拿到了platform_data:

static int ohci_platform_probe(struct platform_device *dev)    
{
    struct usb_hcd *hcd;
    struct resource *res_mem;
    struct usb_ohci_pdata *pdata = dev_get_platdata(&dev->dev);
    struct ohci_platform_priv *priv;
    ... ...
}
2.2 memory resource资源的定义
#define DEFINE_RES_MEM(_start, _size)                    \
    DEFINE_RES_MEM_NAMED((_start), (_size), NULL)

#define DEFINE_RES_MEM_NAMED(_start, _size, _name)            \
    DEFINE_RES_NAMED((_start), (_size), (_name), IORESOURCE_MEM)

/* helpers to define resources */
#define DEFINE_RES_NAMED(_start, _size, _name, _flags)            \
    {                                \
        .start = (_start),                    \
        .end = (_start) + (_size) - 1,                \
        .name = (_name),                    \
        .flags = (_flags),                    \
        .desc = IORES_DESC_NONE,                \
    }
2.3 irq resource资源的定义
#define DEFINE_RES_IRQ(_irq)                        \
    DEFINE_RES_IRQ_NAMED((_irq), NULL)

#define DEFINE_RES_IRQ_NAMED(_irq, _name)                \
    DEFINE_RES_NAMED((_irq), 1, (_name), IORESOURCE_IRQ)
2.4 IO resources flags
/*
* IO resources have these defined flags.
*
* PCI devices expose these flags to userspace in the "resource" sysfs file,
* so don't move them.
*/
#define IORESOURCE_BITS        0x000000ff    /* Bus-specific bits */

#define IORESOURCE_TYPE_BITS    0x00001f00    /* Resource type */
#define IORESOURCE_IO        0x00000100    /* PCI/ISA I/O ports */
#define IORESOURCE_MEM        0x00000200
#define IORESOURCE_REG        0x00000300    /* Register offsets */
#define IORESOURCE_IRQ        0x00000400
#define IORESOURCE_DMA        0x00000800
#define IORESOURCE_BUS        0x00001000

3,来自设备树的platform_device

在Linux内核启动时,内核通过 of_platform_populate() 函数,将dts中的device node创建成platform device。为后续和各类驱动的platform driver匹配做准备。

of_platform_device_create_pdata
----of_device_alloc
--------of_address_to_resource(np, num_reg, &temp_res)
--------num_irq = of_irq_count(np);
--------dev->num_resources = num_reg + num_irq;
--------dev->resource = res;
--------of_address_to_resource(np, i, res);
--------of_irq_to_resource_table(np, res, num_irq); //将dts中的address和irq等信息转化到resource结构体中
--------dev->dev.bus = &platform_bus_type;
----dev->dev.platform_data = platform_data;
----of_device_add(dev)
--------device_add(&ofdev->dev);

具体解析转化过程会在后续的dts章节中详细分析。

4,get resource API实现及使用

4.1 get resource API实现

platform_get_resource_byname:

/**
* platform_get_resource_byname - get a resource for a device by name
* @dev: platform device
* @type: resource type
* @name: resource name
*/
struct resource *platform_get_resource_byname(struct platform_device *dev,
                          unsigned int type,
                          const char *name)
{
    u32 i;

    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)) //匹配type和name
            return r;
    }
    return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource_byname);

platform_get_resource:

/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type
* @num: resource index
*
* Return: a pointer to the resource or NULL on failure.
*/
struct resource *platform_get_resource(struct platform_device *dev,
                       unsigned int type, unsigned int num)
{
    u32 i;

    for (i = 0; i < dev->num_resources; i++) {
        struct resource *r = &dev->resource[i];

        if (type == resource_type(r) && num-- == 0) //匹配type和index
            return r;
    }
    return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource);
4.2 get resource API使用示例

get memory resource使用示例:

struct resource *res;

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
    dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
    return -ENODEV;
}

adata->acp3x_base = devm_ioremap(&pdev->dev, res->start,
                 resource_size(res));
if (!adata->acp3x_base)
    return -ENOMEM;

get irq resource 使用示例:

res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
    dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n");
    return -ENODEV;
}
adata->i2s_irq = res->start;

status = devm_request_irq(&pdev->dev, adata->i2s_irq, i2s_irq_handler,
              irqflags, "ACP3x_I2S_IRQ", adata);
if (status) {
    dev_err(&pdev->dev, "ACP3x I2S IRQ request failed\n");
    return -ENODEV;
}

get resource by name 使用示例:

struct resource *r;

/* card: irq assigned to the card itself. */
r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "card");
sock->card_irq = r ? r->start : 0;

/* stschg: irq which trigger on card status change (optional) */
r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "stschg");
sock->stschg_irq = r ? r->start : -1;

/* 36bit PCMCIA Memory area address */
r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcmcia-mem");
if (!r) {
    printk(KERN_ERR "pcmcia%d has no 'pseudo-mem' resource!\n",
        sock->nr);
    goto out0;
}
sock->phys_mem = r->start;

由以上分析可知,在设备驱动中引入platform的概念至少有如下好处:

1)使得设备被挂接在一个总线上,符合Linux2.6以后内核的设备模型。其结果是使配套的sysfs节点、设备电源管理都成为可能。

2)隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过通用API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。

3)让一个驱动支持多个设备实例。

参考链接:

Linux设备模型(8)_platform设备

dts展开为platform_device结构过程分析-腾讯云开发者社区-腾讯云

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

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

相关文章

输出梯形 C语言

解析&#xff1a;这个输出图形的题就是一个找规律加数学计算&#xff0c;我们发现每行比上一行多两个*&#xff0c;最后一行的*表达式为h&#xff08;h-1&#xff09;*2&#xff0c;即3*h-2&#xff0c;那么每一行就是一个先输出最后一行&#xff0d;当前行*个数个空格&#xf…

【Godot4自学手册】第十九节敌人的血量显示及掉血特效

这一节&#xff0c;我主要学习敌人的血量显示、掉血显示和死亡效果。敌人的血量显示和主人公的血量显示有所不同&#xff0c;主要是在敌人头顶有个红色的血条&#xff0c;受到攻击敌人的血条会减少&#xff0c;并且有掉血数量的文字显示&#xff0c;效果如下&#xff1a; 一、…

详解动态规划(算法村第十九关青铜挑战)

不同路径 62. 不同路径 - 力扣&#xff08;LeetCode&#xff09; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finis…

QT Mingw32/64编译ffmpeg源码生成32/64bit库以及测试

文章目录 前言下载msys2ysamFFmpeg 搭建编译环境安装msys2安装QT Mingw编译器到msys环境中安装ysam测试 编译FFmpeg测试 前言 FFmpeg不像VLC有支持QT的库文件&#xff0c;它仅提供源码&#xff0c;需要使用者自行编译成对应的库&#xff0c;当使用QTFFmpeg实现播放视频以及视频…

【leetcode】用队列实现栈

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家刷题&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 点击查看题目 思路: 在做此题之前&#xff0c;我们先要实现队列&#xff0c;这在上个博客中已经写过&#…

算法43:动态规划专练(最长回文子串 力扣5题)---范围模型

之前写过一篇最长回文子序列的博客算法27&#xff1a;最长回文子序列长度&#xff08;力扣516题&#xff09;——样本模型 范围模型-CSDN博客 在那一篇博客中&#xff0c;回文是可以删除某些字符串组成的。比如&#xff1a; 字符串为&#xff1a;a1b3c4fdcdba&#xff0c; 那…

赵文彬将出席无磷锅炉工艺助剂在锅炉水节水节能应用

演讲嘉宾&#xff1a;赵文彬 集团副总/技术总监 上远未来水务集团有限公司 演讲题目&#xff1a;无磷锅炉工艺助剂在锅炉水节水节能方面的应用 会议简介 “十四五”规划中提出&#xff0c;提高工业、能源领城智能化与信息化融合&#xff0c;明确“低碳经济”新的战略目标&am…

c++之旅——第四弹

大家好啊&#xff0c;这里是c之旅第三弹&#xff0c;跟随我的步伐来开始这一篇的学习吧&#xff01; 如果有知识性错误&#xff0c;欢迎各位指正&#xff01;&#xff01;一起加油&#xff01;&#xff01; 创作不易&#xff0c;希望大家多多支持哦&#xff01; 本篇文章的主…

一些C语言题目

求10个整数中最大值 #include <stdio.h>//求10个整数中最大值 int main() {int arr[10]{2,5,8,6,19,1,7,3,11,3};int i 0;int max 0;/*for(i 0;i < 10;i){scanf("%d",&arr[i]);}*/for(i 0;i < 10;i){if(arr[i] > max)max arr[i];}printf(&q…

tomcat 反向代理 自建博客 修改状态页 等

一 自建博客 随后&#xff0c;拷贝到webapps下面 并且做软连接 随后重定向 并且下载 cat >/etc/yum.repos.d/mysql.repo <<EOF [mysql57-community] nameMySQL 5.7 Community Server baseurlhttp://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/ enabled1 g…

分享一款我自己开发的自动更新小工具

我们公司最近需要开发一款自动上传的工具&#xff0c;这个工具需要安装在用户电脑上&#xff0c;但是这样不利于维护&#xff0c;于是想到了自动更新这个功能&#xff0c;需要在打开工具时顺带打开自动更新的小工具&#xff0c;这样我们在更新代码后&#xff0c;用户那边就能自…

IDEA POM文件配置profile实现不同环境切换

目录 一、背景 二、实现 2.1创建不同的配置文件 2.2配置POM文件 三、效果 3.1本地使用 2.2线上或者测试环境使用 一、背景 在企业级开发中&#xff0c;为了不影响生产环境的项目运行&#xff0c;一般情况下都会划分生产环境、测试环境、开发环境。不同环境可以配置不同的…

4. 编写app组件

1. 代码 main.ts // 引入createApp用于创建应用 import {createApp} from "vue"// 引入App根组件 import App from ./App.vue createApp(App).mount(#app) App.vue <!-- vue文件可以写三种标签1. template标签&#xff0c;写html结构2. script 脚本标签&…

Linux设备模型(十) - bus/device/device_driver/class

四&#xff0c;驱动的注册 1&#xff0c;struct device_driver结构体 /** * struct device_driver - The basic device driver structure * name: Name of the device driver. * bus: The bus which the device of this driver belongs to. * owner: The module own…

js 面试 什么是WebSockets?HTTP和HTTPS有什么不同?web worker是什么?

概念&#xff1a; webSocket 是一种在客户端和服务端之间建立持久连接的协议&#xff0c;它提供全双工通信通道&#xff0c;是服务器可以主动向客户端推送数据&#xff0c;同时也可以接受客户端发送的数据。 1 webSocket与https区别&#xff1f; 在网络通信中&#xff0c;We…

一款汇聚 精美UI+AI内容生成助手 的实用白板工具

大家好&#xff0c;我是Mandy。今天给大家分享的内容是&#xff0c;如何利用AI快速生成思维导图、PPT、绘画等功能&#xff0c;本文分享的AI功能是基于boardmix实现。 boardmix是一款非常精美的在线白板工具&#xff0c;是一个实时协作的智慧白板上、一键生成PPT、用AI协助创作…

YOLOv9保姆教程,手把手教你训练、检测,快来学习吧!!

首先在这里推送一下我的YOLOv9改进专栏&#xff0c;目前是全网最快的YOLOv9改进专栏&#xff0c;该专栏将更新最新的模块来改进YOLOv9&#xff0c;助力大家论文与科研&#xff0c;欢迎大家了解&#xff01; ⭐专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&…

Python+Selenium4 Web自动化测试框架学习(一)

主要框架及技术 1.第一个selenium例子 import timefrom selenium import webdriver from selenium.webdriver.common.by import Bydriver webdriver.Chrome() driver.get("https://www.bilibili.com") driver.find_element(By.CLASS_NAME,"nav-search-input&…

求阶乘。。

&#xff01;&#xff01;&#xff01;答案解释摘录自蓝桥云课题解 问题描述 满足N!的末尾恰好有个0的最小的N是多少? 如果这样的N不存在输出-1。 输入格式 一个整数 K 输出格式 一个整数代表答案 样例输入 2 样例输出 10 import os import sys# 请在此输入您的代码 def coun…

css通过calc动态计算宽度

max-width: calc(100% - 40px) .m-mj-status-drawing-info-data{ display: inline-block; margin: 10px; min-width: 200px; padding: 10px;border-radius: 10px; background: #ddd;max-width: calc(100% - 40px);word-wrap: break-word;white-space: pre-line;}我开发的chatg…