Linux内核USB3.0驱动框架分析--USB主机控制器hcd驱动分析

news2024/11/23 20:05:49

一,概述

usb主机控制器驱动一般以platform的形式将驱动注册进内核,,因此我们需要从前面一篇文章的框图说起。主要分析下图中橙色部分的内容。
在这里插入图片描述
二,usb主机控制器相关函数

2.1 usb_create_hcd

我们来看一下usb_create_hcd函数,该函数定义在drivers/usb/core/hcd.c:

struct usb_hcd *usb_create_hcd(const struct hc_driver *driver,    // 参数1为ohci_s3c2410_hc_driver,参数2为s3c_device_ohci ->dev,参数3位s3c24xx
                struct device *dev, const char *bus_name)
{
        return __usb_create_hcd(driver, dev, dev, bus_name, NULL);
}

这里调用了__usb_create_hcd函数:

struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver,   
                struct device *sysdev, struct device *dev, const char *bus_name,
                struct usb_hcd *primary_hcd)
{
        struct usb_hcd *hcd;

        hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL); // 动态申请内存空间,额外申请hcd私有数据大小的内存

        timer_setup(&hcd->rh_timer, rh_timer_func, 0);  // 初始化定时器,设置定时器超时函数

        hcd->driver = driver;   // 绑定hc_driver
       
        return hcd;
}

总结一下函数执行流程:

首先分配一个hcd的内存空间,包含私有数据空间;
设置hcd->rh_timer设置定时器超时函数rh_timer_func,主机控制器以轮询的方式查找端口变化状态;
设置 hcd->driver = &_hc_driver等 ;

hc_driver是usb主机控制器的驱动函数,实现了通过主机控制器硬件向外通信的方法。

2.2 rh_timer_func

定时器回调函数定义在drivers/usb/core/hcd.c文件中:

/* timer callback */
static void rh_timer_func (struct timer_list *t)
{
        struct usb_hcd *_hcd = from_timer(_hcd, t, rh_timer);

        usb_hcd_poll_rh_status(_hcd);
}

2.3 usc_add_hcd

用usb_hcd结构体定义好usb_hdc设备后,用usb_add_hcd函数向linux内核注册usb主机控制器驱动,函数定义在drivers/usb/core/hcd.c:

int usb_add_hcd(struct usb_hcd *hcd,
                unsigned int irqnum, unsigned long irqflags)
{
        int retval;
        struct usb_device *rhdev;

      
                        retval = usb_phy_roothub_set_mode(hcd->phy_roothub,
                                                          PHY_MODE_USB_HOST);
             

        retval = usb_register_bus(&hcd->self);
        if (retval < 0)
                goto err_register_bus;

        rhdev = usb_alloc_dev(NULL, &hcd->self, 0);
        if (rhdev == NULL) {
                dev_err(hcd->self.sysdev, "unable to allocate root hub\n");
                retval = -ENOMEM;
                goto err_allocate_root_hub;
        }
        mutex_lock(&usb_port_peer_mutex);
        hcd->self.root_hub = rhdev;
        mutex_unlock(&usb_port_peer_mutex);


       
        /* initialize tasklets */
        init_giveback_urb_bh(&hcd->high_prio_bh);
        init_giveback_urb_bh(&hcd->low_prio_bh);

       
    
        /* starting here, usbcore will pay attention to this root hub */
        retval = register_root_hub(hcd);
      
        if (hcd->uses_new_polling && HCD_POLL_RH(hcd))
                usb_hcd_poll_rh_status(hcd);     // 定时器轮询root hub端口状态

        return retval;
}

该函数代码比较长,这里挑重点说:

usb_register_bus(&hcd->self) ,将 hcd->usb_bus 注册到全局链表usb_bus_list;
usb_alloc_dev为根hub分配一个usb_device 结构(内核中,所有的真实的usb设备;比如hub,鼠标等都用usb_device结构来描述);
register_root_hub注册根hub设备到usb_bus_type;
usb_hcd_poll_rh_status定时器函数轮询hub端口状态;

usb主机控制器也只不过是分配了一个usb_hcd结构体,为它的根hub分配了一个usb_device 结构体,注册到usb_bus_type罢了,后边是根hub的注册和设备枚举过程了。

2.4 usb_alloc_dev

usb_alloc_dev函数定义在drivers/usb/core/hcd.c:

struct usb_device *usb_alloc_dev(struct usb_device *parent,
                                 struct usb_bus *bus, unsigned port1)
{
        struct usb_device *dev;
  
        dev = kzalloc(sizeof(*dev), GFP_KERNEL);
        
        device_initialize(&dev->dev);
        dev->dev.bus = &usb_bus_type;
        dev->dev.type = &usb_device_type;
        dev->dev.groups = usb_device_groups;
       
        dev->state = USB_STATE_ATTACHED;
       
        dev->ep0.desc.bLength = USB_DT_ENDPOINT_SIZE;
        dev->ep0.desc.bDescriptorType = USB_DT_ENDPOINT;
        /* ep0 maxpacket comes later, from device descriptor */
        usb_enable_endpoint(dev, &dev->ep0, false);
        dev->can_submit = 1;

        /* Save readable and stable topology id, distinguishing devices
         * by location for diagnostics, tools, driver model, etc.  The
         * string is a path along hub ports, from the root.  Each device's
         * dev->devpath will be stable until USB is re-cabled, and hubs
         * are often labeled with these port numbers.  The name isn't
         * as stable:  bus->busnum changes easily from modprobe order,
         * cardbus or pci hotplugging, and so on.
         */
        if (unlikely(!parent)) {
                dev->devpath[0] = '0';
                dev->route = 0;

                dev->dev.parent = bus->controller;
                device_set_of_node_from_dev(&dev->dev, bus->sysdev);
                dev_set_name(&dev->dev, "usb%d", bus->busnum);
                root_hub = 1;
        }

        
        return dev;
}

该函数主要做了以下事情:

这里实际上就是为根hub,动态分配一个usb_device;
初始化设备基类dev->dev:
     dev->dev.bus = &usb_bus_type,设置总线类型
     dev->dev.type = &usb_device_type,设置设备类型,用于和usb接口区分;
    等等;
初始化dev其它成员;
    dev->state = USB_STATE_ATTACHED,设备状态设置位已连接;
    usb_enable_endpoint函数使能usb端点0,用于usb通信,usb主机控制器会通过该端点和root hub通信获取设备/配置描述信息;一个 usb 设备有多个配置,每个配置又有多个接口,每个接口有多个端点。但是端点0比较特殊,它是整个usb设备共享的,端点0是直接存储在usb_device->ep0字段中。
    等等;

2.5 register_root_hub

register_root_hub函数定义在drivers/usb/core/hcd.c,顾名思义,就是注册root_hub设备,先是对自身初始化,然后获取设备描述符,最后把自己当普通usb设备给注册了。

static int register_root_hub(struct usb_hcd *hcd)
{
        struct device *parent_dev = hcd->self.controller;
        struct usb_device *usb_dev = hcd->self.root_hub;  // 根hub设备
        const int devnum = 1;
        int retval;

        usb_dev->devnum = devnum;        //根hub设备地址为1
        usb_dev->bus->devnum_next = devnum + 1;
        set_bit (devnum, usb_dev->bus->devmap.devicemap);
        usb_set_device_state(usb_dev, USB_STATE_ADDRESS);   // 变更状态

        mutex_lock(&usb_bus_idr_lock);

        usb_dev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
        retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE);  // 获取设备描述符
     

        retval = usb_new_device (usb_dev);
        if (retval) {
                dev_err (parent_dev, "can't register root hub for %s, %d\n",
                                dev_name(&usb_dev->dev), retval);
        } else {
                spin_lock_irq (&hcd_root_hub_lock);
                hcd->rh_registered = 1;
                spin_unlock_irq (&hcd_root_hub_lock);

                /* Did the HC die before the root hub was registered? */
                if (HCD_DEAD(hcd))
                        usb_hc_died (hcd);      /* This time clean up */
        }
        mutex_unlock(&usb_bus_idr_lock);

        return retval;
}

这里我们先介绍usb_get_device_descriptor函数,该函数用于获取根hub设备的描述符;

int usb_get_device_descriptor(struct usb_device *dev, unsigned int size)
{
        struct usb_device_descriptor *desc;
       
        desc = kmalloc(sizeof(*desc), GFP_NOIO);
     
        ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, size); // 获取usb设备描述符,request = 0x06,requestType = 0x80、value = 0x100
      
        return ret;
}

2.6 usb_new_device
接下来我们再来看看usb_new_device。

int usb_new_device(struct usb_device *udev)
{
        err = usb_enumerate_device(udev);       /* Read descriptors */
      
        dev_dbg(&udev->dev, "udev %d, busnum %d, minor = %d\n",
                        udev->devnum, udev->bus->busnum,
                        (((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
        /* export the usbdev device-node for libusb */
        udev->dev.devt = MKDEV(USB_DEVICE_MAJOR,
                        (((udev->bus->busnum-1) * 128) + (udev->devnum-1)));

        /* Tell the world! */
        announce_device(udev);
  		 if (udev->serial)
                add_device_randomness(udev->serial, strlen(udev->serial));
        if (udev->product)
                add_device_randomness(udev->product, strlen(udev->product));
        if (udev->manufacturer)
                add_device_randomness(udev->manufacturer,
                                      strlen(udev->manufacturer));
      
        err = device_add(&udev->dev);
  
        return err;
}

usb_new_device函数用于注册usb根hub设备,先是枚举调用usb_enumerate_device获取设备配置描述符信息(这里枚举获取的pid、vid等用于设备驱动匹配的id使用):

static int usb_enumerate_device(struct usb_device *udev)
{
        int err;
        struct usb_hcd *hcd = bus_to_hcd(udev->bus);

        if (udev->config == NULL) {
                err = usb_get_configuration(udev);    // 获取配置描述符,这里根据设备描述符dev->descriptor.bNumConfiguratios中配置数量,来一一发送USC_DT_CONFIG(value=0x200)命令获取配置描述符,初始化dev->config[num]成员
              
        }

        /* read the standard strings and cache them if present */
        udev->product = usb_cache_string(udev, udev->descriptor.iProduct);  // 从设备描述符获取产品名信息
        udev->manufacturer = usb_cache_string(udev,                          // 从设备描述符获取厂商信息 
                                              udev->descriptor.iManufacturer);
        udev->serial = usb_cache_string(udev, udev->descriptor.iSerialNumber);  // 从设备描述符获取产品串号信息

        err = usb_enumerate_device_otg(udev);
 
        return 0;
}

usb_get_configuration,大致步骤也是调用usb_control_msg函数通过usb主机控制器向root hub发送控制报文,获取配置描述符信息。

然后调用device_add将设备加入到设备链表中,在执行device_add后,将执行总线的匹配函数usb_device_match:

device_add
    bus_probe_devic
        device_attach
            bus_for_each_drv
                __device_attach
                    driver_match_device
                        drv->bus->match(dev, drv)
                            usb_device_match

注册根hub设备后,由于在usb子系统初始化函数中注册了usb通用设备驱动usb_generic_driver,这样就会执行usb_generic_driver的probe函数,即generic_probe。这里就和前面一幅图的流程一样了。

2.7 usb_hcd_poll_rh_status

/*
 * Root Hub interrupt transfers are polled using a timer if the
 * driver requests it; otherwise the driver is responsible for
 * calling usb_hcd_poll_rh_status() when an event occurs.
 *
 * Completions are called in_interrupt(), but they may or may not
 * be in_irq().
 */
void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
{
        struct urb      *urb;
  
        length = hcd->driver->hub_status_data(hcd, buffer);
        if (length > 0) {

                /* try to complete the status urb */
                spin_lock_irqsave(&hcd_root_hub_lock, flags);
                urb = hcd->status_urb;
                if (urb) {
                        clear_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
                        hcd->status_urb = NULL;
                        urb->actual_length = length;
                        memcpy(urb->transfer_buffer, buffer, length);

                        usb_hcd_unlink_urb_from_ep(hcd, urb);
                        usb_hcd_giveback_urb(hcd, urb, 0);
                } else {
                        length = 0;
                        set_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
                }
                spin_unlock_irqrestore(&hcd_root_hub_lock, flags);
        }

        /* The USB 2.0 spec says 256 ms.  This is close enough and won't
         * exceed that limit if HZ is 100. The math is more clunky than
         * maybe expected, this is to make sure that all timers for USB devices
         * fire at the same time to give the CPU a break in between */
        if (hcd->uses_new_polling ? HCD_POLL_RH(hcd) :
                        (length == 0 && hcd->status_urb != NULL))
                mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));   // 修改定时器超时时间,并重新激活定时器
}

usb_hcd_poll_rh_status 调用主机控制器的hub_status_data函数获取端口状态。如果端口的状态有变化,那么length > 0,把获取到的端口状态的数组拷贝到urb->transfer_buffer中,就是前面的hub->buffer中,同时调用usb_hcd_giveback_urb函数。

2.8 usb_hcd_giveback_urb

void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status)
{   
        if (!hcd_giveback_urb_in_bh(hcd) && !is_root_hub(urb->dev)) {
                __usb_hcd_giveback_urb(urb);
                return;
        }

}
static void __usb_hcd_giveback_urb(struct urb *urb)
{
       
        urb->complete(urb);
   
}

usb_hcd_giveback_urb函数中调用urb->complete (urb),而urb->complete = hub_irq,这样就返回到了hub中 ;

三,usb驱动的probe匹配过程
前面我们分析到调用usb_new_device来进行配置使usb设备可以正常工作,我们现在分析一下具体过程。主要是找到对应的客户驱动程序和该USB设备挂钩。

usb_new_device中调用device_add,将设备添加到设备层次结构中。

大概调用流程:device_add -> bus_probe_device -> device_initial_probe -> __device_attach -> bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);

3.1 1. usb_device_match

我们分析一下就是usb_device_match,这个函数只是做一些粗略的匹配, 如果匹配成功则返回1。这个函数只是做一些粗略的匹配, 如果匹配成功则返回1, 然后由driver_probe_device来做进一步的匹配, 如果匹配失败则返回0, 并且driver_probe_device也不会在执行. 这个函数的调用保证了dev, drv 要么都是设备级别的( 即dev 代表usb 设备,drv 代表usb 设备驱动), 要么都是接口级别的( 即dev 代表usb 设备的一个interface,drv 代表usb 接口驱动).

static int usb_device_match(struct device *dev, struct device_driver *drv)
{
    /* devices and interfaces are handled separately */
    if (is_usb_device(dev)) {
        /* interface drivers never match devices */ //是匹配USB设备的驱动,USB接口的驱动不能匹配
        if (!is_usb_device_driver(drv))
            return 0;
        /* TODO: Add real matching code */
        return 1;
    } else if (is_usb_interface(dev)) { //如果是USB接口
        struct usb_interface *intf;
        struct usb_driver *usb_drv;
        const struct usb_device_id *id;
        /* device drivers never match interfaces */
        //usb接口在注册driver时将for_devices设置为0,for_devices =1,表示设备驱动,for_devices = 0,表示接口驱动
        if (is_usb_device_driver(drv))
            return 0;
        intf = to_usb_interface(dev);
        usb_drv = to_usb_driver(drv);

        id = usb_match_id(intf, usb_drv->id_table); //匹配id table
        if (id)
            return 1;
        id = usb_match_dynamic_id(intf, usb_drv); //匹配动态id table
        if (id)
            return 1;
    }
    return 0;
}

3.2 driver_probe_device

driver_probe_device主要是调用really_probe -> (drv->probe)

对于usb 来说这个函数的调用有2 种分支, 1: dev,drv 代表的是设备级别的, 2 dev,drv 代表的是接口级别的. 其他情况组合在usb_device_match 中被过滤掉了.

分支1: dev,drv 代表的是设备级别:

此时的drv 肯定是usb_generic_driver. 因为在当前的usb 系统中只有这个driver 是代表整个设备的驱动, 它是在usb_init 中被注册的, 而我们通常写的usb 驱动都是代表一个interface 的.

因此, 此时的drv->probe 将调用generic_probe().再到usb_set_configuration

int usb_set_configuration(struct usb_device *dev, int configuration)
{
   for(I = 0; I < nintf; i++) {
   struct usb_interface *intf = cp->interface[i];
   device_add(&intf->dev); //又会进入匹配
}

该函数比较重要, 但我们只关心probe 过程因此省掉了很多东西. 它为当前配置下的每个interface 调用device_add() 函数, 根据前面的分析可知, 这个过程将会走到接下来我们要分析的分支2.

分支2: dev,drv 代表的是interface 级别:

此时的dev 代表着一个interface, 而drv 就代表了我们自己的usb 驱动. 但是我们应当看到drv是device_driver 类型, 而我们写的usb 驱动的类型一般是usb_driver, 因此这里的probe 和我们自己写的probe 显然不是同一个. 实际上这里的drv 是我们的驱动对象里内嵌的一个子对象( 因为linux 下所以的驱动都必须用device_driver 来代表,). 那这个子对象的probe 函数是在哪里赋值的呢?

这就要看usb_register宏了,实际里面是调用usb_register_driver

int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
            const char *mod_name)
{
    new_driver->drvwrap.for_devices = 0;
    new_driver->drvwrap.driver.name = new_driver->name;
    new_driver->drvwrap.driver.bus = &usb_bus_type;
    new_driver->drvwrap.driver.probe = usb_probe_interface; //这里是probe函数
    new_driver->drvwrap.driver.remove = usb_unbind_interface; 
    new_driver->drvwrap.driver.owner = owner;
    new_driver->drvwrap.driver.mod_name = mod_name;
    new_driver->drvwrap.driver.dev_groups = new_driver->dev_groups;
    spin_lock_init(&new_driver->dynids.lock);
    INIT_LIST_HEAD(&new_driver->dynids.list);

    retval = driver_register(&new_driver->drvwrap.driver);
    if (retval)
        goto out;
    retval = usb_create_newid_files(new_driver);
    if (retval)
        goto out_newid;

    pr_info("%s: registered new interface driver %s\n",
            usbcore_name, new_driver->name); //一般log会打印这个
}

跟踪这个函数我们可以看到这里的probe 函数实际上是usb_probe_interface( 所有的usb interface 驱动都是一样的).

 static int usb_probe_interface(struct device *dev)
{
  struct driver = to_usb_driver(dev->driver);  //dev->driver   在really_probe 中设置.
  error = driver->probe(intf, id);   // 这个就是我们自己写的probe 函数了.
}

driver->probe(intf, id); 这就调用到我们自己写的代码里面了,

3.3 流程图

大概流程图是一样的:
在这里插入图片描述

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

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

相关文章

如何成为 Rust 核心贡献者?Rust 开发的核​​心是什么?Rust 重要技术专家揭秘

10 月 17 - 18日&#xff0c;由 GOSIM 开源创新汇主办、CSDN 承办的 GOSIM CHINA 2024 将在北京盛大启幕。作为 GOSIM 开源年度大会的第三届盛会&#xff0c;本次活动邀请了 60 多位国际开源专家&#xff0c;汇聚了来自全球百余家顶尖科技企业、知名高校及开源社区的技术大咖、…

图像增强论文精读笔记-Kindling the Darkness: A Practical Low-light Image Enhancer(KinD)

1. 论文基本信息 论文标题&#xff1a;Kindling the Darkness: A Practical Low-light Image Enhancer 作者&#xff1a;Yonghua Zhang等 发表时间和期刊&#xff1a;2019&#xff1b;ACM MM 论文链接&#xff1a;https://arxiv.org/abs/1905.04161 2. 研究背景和动机 现有…

C++入门基础知识110—【关于C++ if...else 语句】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于C if...else 语句的相关内容&#xff01…

SAP SD学习笔记09 - 受注传票中的不完全Log 和 Business Partner(取引先机能)

好久没写SD了&#xff0c;今天继续写。 上一章讲了SD的如下知识 - SD的售前的流程&#xff08;引合和見積&#xff08;询价和报价&#xff09;&#xff09; - 数据流的概念&#xff0c;主要就是后传票可以参照前传票&#xff0c;以实现数据的流动&#xff0c;减少输入 - Co…

PHP游泳馆会员管理系统-计算机毕业设计源码86627

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于游泳馆会员管理系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了游泳馆会员管理系统&#xff0c;它彻底改…

二分查找法 ← Python实现

【二分查找法】 ★ 二分查找&#xff0c;是一种效率较高的查找方法。但是&#xff0c;二分查找要求元素按关键字有序排列。 ★ 二分查找每一次查找都使查找范围缩小一半&#xff0c;与顺序查找相比&#xff0c;很显然会提高查找效率。为了标记查找过程中每一次的查找区间&#…

自动猫砂盆真的有必要吗?买自动猫砂盆不看这四点小心害死猫。

现在越来越多铲屎官选择购买自动猫砂盆来代替自己给猫咪铲屎&#xff0c;可是自动猫砂盆真的有必要吗&#xff1f;要知道&#xff0c;在现在忙碌的生活中&#xff0c;有很多人因为工作上的忙碌而不小心忽视了猫咪&#xff0c;猫咪的猫砂盆堆满粪便&#xff0c;要知道猫砂盆一天…

windows如何设置右键新建文档

1. windows如何设置右键新建文档 文章目录 1. windows如何设置右键新建文档1.1. 注意注意注意1.2. 参考资料1.3. 注册列表中各项的意思1.4. 右键新建文档1.4.1. 新建文件夹快捷键1.4.2. 新建txt文件快捷键1.4.3. 新建Word文档快捷键1.4.4. 新建PowerPoint文档快捷键1.4.5. 新建…

Linux基础(五):linux目录配置

1.Linux系统目录 不同的Linux发布版本的目录都是类似的&#xff0c;这是因为Linux系统的目录配置有一个大概的标准——Filesystem Hierarchy Standard&#xff08; FHS&#xff09;。FHS规定了目录有4种交互形态&#xff1a; ①可分享的&#xff1a; 可以分享给其他系统挂载使…

【优选算法】(第三十五篇)

目录 验证栈序列&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 N叉树的层序遍历&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 验证栈序列&#xff08;medium&#xff09; 题目解析 1.题目链接&#xff1a;. - 力扣&#xff08;L…

校园网网页认证设备限制环境下基于OpenWRT的路由器选型与解决方案

校园网环境下基于OpenWRT的路由器选型与解决方案 网页认证(锐捷认证)解除校园网设备限制,路由器选型和解决方案 openwrt 我们学校校园网一个账号只能登录两台设备&#xff0c;多了直接就退出联网状态&#xff0c;然后校园网是基于锐捷认证进行认证的&#xff0c;然后通过ment…

【closerAI ComfyUI】爹妈都认不出的美女模糊照片,这个高清放大模型竟然能还原出来!这个AI模型我给满分

兄弟们&#xff0c;太离谱了&#xff0c;大家都知道FLUX模型的牛逼&#xff0c;现在基于FLUX的生态越发成熟&#xff0c;但一张模糊到五官都不能辨认的图片&#xff0c;通过AI模型的计算&#xff0c;竟然能还原出来。当然&#xff0c;这里我们的测试是过于极端。一般模糊一点的…

SpringCloud网关聚合knife4j方案

微服务开发中想将Spring-Cloud-Gateway网关聚合knife4j&#xff0c;形成一个统一入口方便查阅的开发辅助接口文档&#xff0c;并且将Swagger抽取成一个公共模块&#xff0c;那么我们可以参考以下的做法 约定&#xff1a; Java Version&#xff1a;11.0.24 Spring Boot&#xff…

一探究竟:全能型人体存在传感器如何革新全屋智能生活

如今&#xff0c;全屋智能家居已蔚然成风&#xff0c;亲历过个性化智能家居配置的用户无不深知传感器在其联动逻辑中扮演的关键角色。市场上主流的传感器类型多为移动监测型&#xff0c;此类设备通过探测区域内是否存在人员活动来触发相应的自动化操作。尽管它们在众多应用场景…

实用软件分享

文章路径 Pfolg_Source/实用软件记录.md at main Pfolg/Pfolg_Source (github.com)https://github.com/Pfolg/Pfolg_Source/blob/main/%E5%AE%9E%E7%94%A8%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95.md 注 这篇文章水的不得了&#xff0c;但我花了半小时来水……

谷歌-BERT-第四步:模型部署

1 需求 需求1&#xff1a;基于gradio实现大模型的WEB UI交互界面 2 接口 3 示例 import gradio as gr from transformers import *classifier pipeline("text-classification", model"./model", tokenizer"./model")gr.Interface.from_pipel…

c++(多态)

多态的定义 多态是⼀个继承关系的下的类对象&#xff0c;去调⽤同⼀函数&#xff0c;产⽣了不同的⾏为 ⽐如Student继承了Person。Person对象买票全价&#xff0c;Student对象优惠买票。 多态实现的条件 • 必须指针或者引⽤调⽤虚函数 第⼀必须是基类的指针或引⽤&#xff0c;…

【HarmonyOS NEXT】实现二个直角梯形按钮,拼接为矩形,斜边可以点击

【问题描述】 实现二个直角梯形按钮两梯形的斜边&#xff0c;对接再一起&#xff0c;组成一个矩形斜边附近的区域能点击 【原型图】 【方案】 canvas——斜边附近的区域无法点击Shape——斜边附近的区域无法点击clipShape——完美解决 【代码】 Entry Component struct …

Spring Task 使用详解

在应用开发中&#xff0c;定时任务扮演着至关重要的角色&#xff0c;例如定时数据同步、定时邮件发送、定时清理缓存等。Spring Framework 提供了一个强大而灵活的定时任务框架——Spring Task&#xff0c;它可以帮助我们轻松地实现各种定时任务&#xff0c;而无需依赖复杂的第…

光影魔术手 0.1.5 | 免费的修图神器,支持AI智能美颜、证件照制作等功能

光影魔术手是一款完全免费的修图软件、AI智能调色软件、证件照制作软件。支持的功能包括&#xff1a;AI智能美颜、AI人脸变清晰、AI智能调色、AI滤镜、拼图、证件照制作、图片裁剪。证件照制作功能提供了丰富的证件照尺寸&#xff0c;支持7种背景颜色切换。拼图功能支持横向长图…