NXP i.MX8系列平台开发讲解 - 3.11 Linux PCIe设备调试(WIFI模块)

专栏文章目录传送门:返回专栏目录


文章目录

目录

1. WIFI 模块简单介绍

2. 设备驱动原理介绍

3. PCIE WIFI驱动实例分析

3.1 查看设备树

3.2 wifi 设备驱动代码分析

3.3 内核配置选项

4. WIFI驱动调试相关


根据前面对PCIe的讲解,对PCIe的整体都有了一定的认识,具体工作原理有了一定了解,这章将在Linux系统下以PCIe接口的WIFI模块使用能够正常跑起来,环境如下:

  • cpu: i.mx8mq

  • OS:Android 11

  • Kernel version:kernel 5.4

  • WIFI module:AW-CM276MA-PUR模组(WIFI芯片88W8997)


1. WIFI 模块简单介绍

在我们认识WIFI模块,WIFI模块的接口主要有SDIO,USB,PCIe,采用PCIe接口对于其他这些接口来说,就是速度的优势,适用于需要更高性能和带宽的应用。如图AW-CM276MA-PUR模组采用的PCIe接口,采用M.2 2230接口;

查看NXP官方提供的相关AW-CM276MA-PUR

无线模块需要在i.MX 8M Quad主机系统上加载内核驱动程序,并在88W8987/88W8997 SoC上运行固件。MLAN模块会在SDIO/PCIe总线驱动程序检测到模块的SDIO/PCIe接口时,将固件二进制文件下载到SD8987/PCIE8997适配器中。内核驱动程序(SD8987/PCIE8997)会在总线驱动程序和内核中的“cfg80211”子系统之间加载。NXP内核驱动程序包含一组控制和配置,通过以下接口之一与用户空间进行通信

• 输入/输出控制(IOCTL)

• 无线扩展(Wext)

• CFG80211

IOCTL为用户空间应用程序(如iwconfig和iwpriv)提供了一条通路,而cfg80211为不同的用户空间应用程序(如wpa_supplicant、hostapd和iw)提供了另一条通路。图6说明了Wi-Fi层接口。


2. 设备驱动原理介绍

这里项目中将涉及到PCIe的RC和EP两端,目前Soc中的RC端的驱动芯片厂商已经提供,EP端的WIFI模块中的驱动wifi模块也已经是由厂家提供,如果是对于原厂厂商制作具有PCIe功能的产品那就需要实现PCIe驱动代码。本章节重点讲诉EP端相关。

对于EP设备原理以及驱动实现的过程如图

根据上图,EP设备驱动主要包含4个步骤:

  • 初始化注册pci设备,当一个pci设备,首先需要去注册一个strcut pci_driver的结构体

  • 填充struct pci_driver,里面包含实现PCIe的Vendor ID和Device ID,probe 接口,有需要添加pcie 电源管理接口;

  • PCIe设备probe,设备里面做了很多工作,设备的初始化资源,使能设备,申请设备资源,启动总线控制权,设置DMA传输方式,设置Bar空间映射,申请DMA中断;

  • 创建字符设备驱动


3. PCIE WIFI驱动实例分析

3.1 查看设备树

从设备树入手,查看i.MX8MQ端PCIe接口

vim ./arch/arm64/boot/dts/freescale/imx8mq.dtsi

​
                pcie1: pcie@33c00000 {
                        compatible = "fsl,imx8mq-pcie";
                        reg = <0x33c00000 0x400000>,
                              <0x27f00000 0x80000>;
                        reg-names = "dbi", "config";
                        #address-cells = <3>;
                        #size-cells = <2>;
                        device_type = "pci";
                        ranges =  <0x81000000 0 0x00000000 0x27f80000 0 0x00010000 /* downstream I/O 64KB */
                                   0x82000000 0 0x20000000 0x20000000 0 0x07f00000>; /* non-prefetchable memory */
                        num-lanes = <1>;
                        num-viewport = <4>;
                        interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
                                        <GIC_SPI 80 IRQ_TYPE_LEVEL_HIGH>; /* eDMA */
                        interrupt-names = "msi", "dma";
                        #interrupt-cells = <1>;
                        interrupt-map-mask = <0 0 0 0x7>;
                        interrupt-map = <0 0 0 1 &gic GIC_SPI 77 IRQ_TYPE_LEVEL_HIGH>,
                                        <0 0 0 2 &gic GIC_SPI 76 IRQ_TYPE_LEVEL_HIGH>,
                                        <0 0 0 3 &gic GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>,
                                        <0 0 0 4 &gic GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>;
                        linux,pci-domain = <1>;
                        fsl,max-link-speed = <2>;
                        power-domains = <&pgc_pcie>;
                        resets = <&src IMX8MQ_RESET_PCIEPHY2>,
                                 <&src IMX8MQ_RESET_PCIE2_CTRL_APPS_EN>,
                                 <&src IMX8MQ_RESET_PCIE2_CTRL_APPS_CLK_REQ>,
                                 <&src IMX8MQ_RESET_PCIE2_CTRL_APPS_TURNOFF>;
                        reset-names = "pciephy", "apps", "clkreq", "turnoff";
                        assigned-clocks = <&clk IMX8MQ_CLK_PCIE2_CTRL>,
                                          <&clk IMX8MQ_CLK_PCIE2_PHY>,
                                          <&clk IMX8MQ_CLK_PCIE2_AUX>;
                        assigned-clock-parents = <&clk IMX8MQ_SYS2_PLL_250M>,
                                                 <&clk IMX8MQ_SYS2_PLL_100M>,
                                                 <&clk IMX8MQ_SYS1_PLL_80M>;
                        assigned-clock-rates = <250000000>, <100000000>,
                                               <10000000>;
                        status = "disabled";
                };

在i.MX8MQ 中含有两个pcie,这里查看pcie1,主要定义了这个PCIE控制器在i.MX8MQ处理器上的配置和属性。列举一些重要的属性:

reg = <0x33c00000 0x400000>, <0x27f00000 0x80000>; // 定义寄存器空间的基地址和大小,用于PCIe控制器的访问;
​
一共定义两个元组 <0x33c00000 0x400000>定义了Device Bus Interface 寄存器空间;另外一个 <0x33c00000 0x400000>指定了配置空间;
​
device_type = "pci";         // 指示设备类型为 PCI
compatible = "pci-host-generic"; // 或其他 PCIe 控制器的兼容性字符串
num-lanes = <1>;            // 指定通道数
fsl,max-link-speed = <2>;       // 定义最大链路速度为Gen2
​
•                        ranges =  <0x81000000 0 0x00000000 0x27f80000 0 0x00010000 /* downstream I/O 64KB */
•                                   0x82000000 0 0x20000000 0x20000000 0 0x07f00000>; /* non-prefetchable memory */
​
rangs 表示PCIe设备的地址范围和映射范围;
​
interrupt-map-mask = <0 0 0 0x7>;  //定义中断映射掩码

以上这个pcie1: pcie@33c00000就是定义的Soc i.MX8MQ其中的一个pcie。在对于wifi设备是做一个ED端,设备树是如何定义,具体查看文件

vim ./arch/arm64/boot/dts/freescale/imx8mq-evk.dts

&pcie1 {
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_pcie1>;
        disable-gpio = <&gpio5 10 GPIO_ACTIVE_LOW>;
        reset-gpio = <&gpio5 12 GPIO_ACTIVE_LOW>;
        clocks = <&clk IMX8MQ_CLK_PCIE2_ROOT>,
                 <&clk IMX8MQ_CLK_PCIE2_AUX>,
                 <&clk IMX8MQ_CLK_PCIE2_PHY>,
                 <&pcie1_refclk>;
        clock-names = "pcie", "pcie_aux", "pcie_phy", "pcie_bus";
        assigned-clocks = <&clk IMX8MQ_CLK_PCIE2_AUX>,
                          <&clk IMX8MQ_CLK_PCIE1_PHY>,
                          <&clk IMX8MQ_CLK_PCIE1_CTRL>;
        assigned-clock-rates = <10000000>, <100000000>, <250000000>;
        assigned-clock-parents = <&clk IMX8MQ_SYS2_PLL_50M>,
                                 <&clk IMX8MQ_SYS2_PLL_100M>,
                                 <&clk IMX8MQ_SYS2_PLL_250M>;
        vph-supply = <&vgen5_reg>;
        l1ss-disabled;
        status = "disabled";
​
        wifi_wake_host {
                compatible = "nxp,wifi-wake-host";
                interrupt-parent = <&gpio5>;
                interrupts = <11 IRQ_TYPE_LEVEL_LOW>;
                interrupt-names = "host-wake";
        };
};

其实对于ED设备,并不需要去怎么定义设备树,这里只是定义了wifi的唤醒相关,当唤醒后将会通过PCIE接口与它通信。pcie1表示一个

Root Complex设备,定义了相关的引脚,时钟,电源,状态,WIFI唤醒等配置。

3.2 wifi 设备驱动代码分析

初始化PCI 设备结构体,

查看pci设备接口体的数据结构

vim ./include/linux/pci.h

struct pci_driver {
        struct list_head        node;
        const char              *name;
        const struct pci_device_id *id_table;   /* Must be non-NULL for probe to be called */
        int  (*probe)(struct pci_dev *dev, const struct pci_device_id *id);     /* New device inserted */
        void (*remove)(struct pci_dev *dev);    /* Device removed (NULL if not a hot-plug capable driver) */
        int  (*suspend)(struct pci_dev *dev, pm_message_t state);       /* Device suspended */
        int  (*resume)(struct pci_dev *dev);    /* Device woken up */
        void (*shutdown)(struct pci_dev *dev);
        int  (*sriov_configure)(struct pci_dev *dev, int num_vfs); /* On PF */
        const struct pci_error_handlers *err_handler;
        const struct attribute_group **groups;
        struct device_driver    driver;
        struct pci_dynids       dynids;
​
        ANDROID_KABI_RESERVE(1);
        ANDROID_KABI_RESERVE(2);
        ANDROID_KABI_RESERVE(3);
        ANDROID_KABI_RESERVE(4);
};

作为一个PCIe设备就需要创建一个pci_driver,重点函数在

    const struct pci_device_id *id_table;   /* Must be non-NULL for probe to be called */ //EP设备的ID相关
    int  (*probe)(struct pci_dev *dev, const struct pci_device_id *id);     /* New device inserted */ //EP probe
    void (*remove)(struct pci_dev *dev);    /* Device removed (NULL if not a hot-plug capable driver) */ // EP设备remove
    int  (*suspend)(struct pci_dev *dev, pm_message_t state);       /* Device suspended */
    int  (*resume)(struct pci_dev *dev);    /* Device woken up */

具体查看PCIe wifi 设备驱动(注:这里的驱动程序放在Android路径下,不在内核路径下)

./vendor/nxp-opensource/nxp-mwifiex/mxm_wifiex/wlan_src/mlinux/moal_pcie.c

/* PCI Device Driver */
static struct pci_driver REFDATA wlan_pcie = {
        .name = "wlan_pcie",
        .id_table = wlan_ids,
        .probe = woal_pcie_probe,
        .remove = woal_pcie_remove,
#ifdef CONFIG_PM
        /* Power Management Hooks */
        .suspend = woal_pcie_suspend,
        .resume = woal_pcie_resume,
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
        .err_handler = woal_pcie_err_handler,
#endif
};

支持的wlan_ids,这里将定义了vendor_id, Device_id

static const struct pci_device_id wlan_ids[] = {
#ifdef PCIE8897
        {
                PCIE_VENDOR_ID_NXP,
                PCIE_DEVICE_ID_NXP_88W8897P,
                PCI_ANY_ID,
                PCI_ANY_ID,
                0,
                0,
        },
#endif
#ifdef PCIE8997
        {
                PCIE_VENDOR_ID_NXP,
                PCIE_DEVICE_ID_NXP_88W8997P,
                PCI_ANY_ID,
                PCI_ANY_ID,
                0,
                0,
        },
        {
                PCIE_VENDOR_ID_V2_NXP,
                PCIE_DEVICE_ID_NXP_88W8997P,
                PCI_ANY_ID,
                PCI_ANY_ID,
                0,
                0,
        },
#endif
#ifdef PCIE9097
        {
                PCIE_VENDOR_ID_V2_NXP,
                PCIE_DEVICE_ID_NXP_88W9097,
                PCI_ANY_ID,
                PCI_ANY_ID,
                0,
                0,
        },
#endif
#ifdef PCIE9098
        {
                PCIE_VENDOR_ID_V2_NXP,
                PCIE_DEVICE_ID_NXP_88W9098P_FN0,
                PCI_ANY_ID,
                PCI_ANY_ID,
                0,
                0,
        },
        {
                PCIE_VENDOR_ID_V2_NXP,
                PCIE_DEVICE_ID_NXP_88W9098P_FN1,
                PCI_ANY_ID,
                PCI_ANY_ID,
                0,
                0,
        },
        
        {},
 }

这些设备的pci_device_id包含了什么信息,主要包含当前EP设备的Vendosr id, Device id 这些信息都是设备厂商提供的,都是需要去组织里面申请的。

注册PCI设备到内核pci_register_driver

/**
 *  @brief This function registers the IF module in bus driver
 *
 *  @return         MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE
 */
mlan_status woal_pcie_bus_register(void)
{
        mlan_status ret = MLAN_STATUS_SUCCESS;
        ENTER();
​
        /* API registers the NXP PCIE driver */
        if (pci_register_driver(&wlan_pcie)) {
                PRINTM(MFATAL, "PCIE Driver Registration Failed \n");
                ret = MLAN_STATUS_FAILURE;
        }
​
        LEAVE();
        return ret;
}

当加载这个wifi时候,匹配上设备树后 ,查看probe主要做了什么;

/**
 *  @brief This function handles PCIE driver probe
 *
 *  @param pdev     A pointer to pci_dev structure
 *  @param id       A pointer to pci_device_id structure
 *
 *  @return         error code
 */
static int woal_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
        pcie_service_card *card = NULL;
        t_u16 card_type = 0;
        int ret = 0;
​
        ENTER();
​
        PRINTM(MINFO, "vendor=0x%4.04X device=0x%4.04X rev=%d\n", pdev->vendor,
               pdev->device, pdev->revision);
​
        /* Preinit PCIE device so allocate PCIE memory can be successful */
        if (woal_pcie_preinit(pdev)) {
                PRINTM(MFATAL, "MOAL PCIE preinit failed\n");
                LEAVE();
                return -EFAULT;
        }
​
        card = kzalloc(sizeof(pcie_service_card), GFP_KERNEL);
        if (!card) {
                PRINTM(MERROR, "%s: failed to alloc memory\n", __func__);
                ret = -ENOMEM;
                goto err;
        }
​
        card->dev = pdev;
​
        card_type = woal_update_card_type(card);
        if (!card_type) {
                PRINTM(MERROR, "pcie probe: woal_update_card_type() failed\n");
                ret = MLAN_STATUS_FAILURE;
                goto err;
        }
        woal_pcie_init(card);
​
        if (woal_add_card(card, &card->dev->dev, &pcie_ops, card_type) ==
            NULL) {
                woal_pcie_cleanup(card);
                PRINTM(MERROR, "%s: failed\n", __func__);
                ret = -EFAULT;
                goto err;
        }
​
#ifdef IMX_SUPPORT
        woal_regist_oob_wakeup_irq(card->handle);
#endif /* IMX_SUPPORT */
​
        LEAVE();
        return ret;
err:
        kfree(card);
        if (pci_is_enabled(pdev))
                pci_disable_device(pdev);
​
        LEAVE();
        return ret;
}
​

probe 中主要对设备进行一个初始化,最初开始调用woal_pcie_preinit进行使能PCI设备,设置一些DMA地址掩码相关

/**
 *  @brief This function pre-initializes the PCI-E host
 *  memory space, etc.
 *
 *  @param handle   A pointer to moal_handle structure
 *
 *  @return         MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE
 */
static mlan_status woal_pcie_preinit(struct pci_dev *pdev)
{
        int ret;
​
        if (pdev->multifunction)
                device_disable_async_suspend(&pdev->dev);
​
        ret = pci_enable_device(pdev); //使能PCI设备
​
        if (ret)
                goto err_enable_dev;
​
        pci_set_master(pdev); //设置成总线主模式DMA模式, EP可以发起memory 方式请求
​
        PRINTM(MINFO, "Try set_consistent_dma_mask(32)\n");
        ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); //设置设备的DMA地址掩码32位,决定了使用物理内存范围
        if (ret) {
                PRINTM(MERROR, "set_dma_mask(32) failed\n");
                goto err_set_dma_mask;
        }
​
        ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); // 设置设备的一致性 DMA 地址掩码为 32 位
        if (ret) {
                PRINTM(MERROR, "set_consistent_dma_mask(64) failed\n");
                goto err_set_dma_mask;
        }
        return MLAN_STATUS_SUCCESS;
​
err_set_dma_mask:
        pci_disable_device(pdev);
err_enable_dev:
        return MLAN_STATUS_FAILURE;
}

申请PCIe内存映射到系统内存空间,这里重点查看woal_pcie_init,截取

​
        ret = pci_request_region(pdev, 0, DRV_NAME); //请求分配第一个区域的内存资源
        if (ret) {
                PRINTM(MERROR, "req_reg(0) error\n");
                goto err_req_region0;
        }
        card->pci_mmap = pci_iomap(pdev, 0, 0); //将第一个区域进行内存映射 BAR, 将其映射到驱动程序的虚拟内存地址空间中
        if (!card->pci_mmap) {
                PRINTM(MERROR, "iomap(0) error\n");
                goto err_iomap0;
        }
        ret = pci_request_region(pdev, 2, DRV_NAME); //请求第二个区域的内存资源
        if (ret) {
                PRINTM(MERROR, "req_reg(2) error\n");
                goto err_req_region2;
        }
        card->pci_mmap1 = pci_iomap(pdev, 2, 0); //将第二个区域进行内存映射 BAR
        if (!card->pci_mmap1) {
                PRINTM(MERROR, "iomap(2) error\n");
                goto err_iomap2;
        }
​
        PRINTM(MINFO,
               "PCI memory map Virt0: %p PCI memory map Virt2: "
               "%p\n",
               card->pci_mmap, card->pci_mmap1);
​
        return MLAN_STATUS_SUCCESS;
 

从代码看到请求了两个区域的资源,也就是前几个章节提到的Configuration SpaceConfiguration Space,第一个用于配置,第二个用于数据的通道传输;

关于PCIe设备模式,这里采用PCIE_INT_MODE_MSI 中断方式

#ifdef PCIE
/* Enable/disable Message Signaled Interrupt (MSI) */
int pcie_int_mode = PCIE_INT_MODE_MSI;
static int ring_size;
#endif /* PCIE */

3.3 内核配置选项

CONFIG_WLAN_VENDOR_NXP=y

CONFIG_MXMWIFIEX=m

CONFIG_PM=y

....

还有一些必要的并未举例;


4. WIFI驱动调试相关

按照上面分析,将WIFI驱动模块编译放入设备运行,成功可以看到以下打印信息:

打印信息看到关键信息:

Link up

看到这个,表示与设备已经建立了通信链路,可以进行链路的交换;剩下很多pci 0001:01:那些信息都是WIFI设备相关的一些配置信息打印。

启动成功后,通过命令lspci可以看到WIFI PCIe的信息:

01:00.0 Class 0200: 1b4b:2b42

  • 01:00.0:PCI设备的域号、总线号和设备号。这个地址表示总线1上的设备0。

  • Class 0200:设备的类别码,表示这是一个以太网控制器设备。

  • 1b4b:2b42:设备的厂商ID和设备ID。

调试PCIe出现的问题:

  1. 设备不能Link up上:

        

  1. 对硬件电路的排查,速率的确定监视信号是否正常;

注意:对于Android 相关调试并未列出,后续整理。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ansible --- playbook 脚本+inventory 主机清单

一 inventory 主机清单 Inventory支持对主机进行分组&#xff0c;每个组内可以定义多个主机&#xff0c;每个主机都可以定义在任何一个或 多个主机组内。 如果是名称类似的主机&#xff0c;可以使用列表的方式标识各个主机。vim /etc/ansible/hosts[webservers]192.168.10.1…

Flutter弹窗链-顺序弹出对话框

效果 前言 弹窗的顺序执行在App中是一个比较常见的应用场景。比如进入App首页&#xff0c;一系列的弹窗就会弹出。如果不做处理就会导致弹窗堆积的全部弹出&#xff0c;严重影响用户体验。 如果多个弹窗中又有判断逻辑&#xff0c;根据点击后需要弹出另一个弹窗&#xff0c;这…

C++ Primer 总结索引 | 第十四章:重载运算与类型转换

1、C语言定义了 大量运算符 以及 内置类型的自动转换规则 当运算符 被用于 类类型的对象时&#xff0c;C语言允许我们 为其指定新的含义&#xff1b;也能自定义类类型之间的转换规则 例&#xff1a;可以通过下述形式输出两个Sales item的和&#xff1a; cout << item1 …

信息系统安全与对抗-网络侦查技术与网络扫描技术(期末复习)

1、网络拓扑结构在网络攻击中的作用 查明目标网络的拓扑结构&#xff0c;有利于找到目标网络的关键节点&#xff0c;从而提高攻击效率&#xff0c;达到最大攻击效果。 2、网络侦查在网络攻击中的作用 识别潜在目标系统&#xff0c;确认目标系统适合哪种类型的攻击。 3、百度…

jenkins部署服务到windows系统服务器

1、安装openSSH windows默认不支持ssh协议&#xff0c;需要下载安装&#xff0c;主要适用于jenkins传输文件已经执行命令使用 点击查看下载openSSH 2、项目配置 这里简单说说怎么配置&#xff0c;主要解决点就是ssh执行cmd或shell命令时不能开启新窗口导致应用部署失败或者断…

【机器学习系统的构建】从模型开发的过程讲清楚K-Fold 交叉验证 (Cross-Validation)的原理和应用

0、前言 最近在学习集成学习的时候了解到了k折交叉验证&#xff0c;其实在之前学习吴恩达老师的课程中也学过交叉验证&#xff0c;但是当时也不是很明白。这次借着自己的疑问以及网上搜找资料&#xff0c;终于把交叉验证给弄明白了。 在弄清楚前&#xff0c;我有这样几个疑问…

rancher/elemental 构建不可变IOS(一)

一、什么是elemental Elemental 是 Rancher 的一个变种&#xff0c;专注于提供一个更轻量级的 Kubernetes 发行版。它旨在提供简化的部署和管理体验&#xff0c;同时保持 Kubernetes 的灵活性和强大功能。Elemental 通常针对较小的部署场景或资源受限的环境&#xff0c;例如测…

BFS Ekoparty 2022 -- Linux Kernel Exploitation Challenge

前言 昨天一个师傅给了我一道 linux kernel pwn 题目&#xff0c;然后我看了感觉非常有意思&#xff0c;题目也不算难&#xff08;在看了作者的提示下&#xff09;&#xff0c;所以就花时间做了做&#xff0c;在这里简单记录一下。这个题是 BFS Lab 2022 年的一道招聘题&#…

JavaEE技术之MySql高级-搭建主从复制(主从同步原理、一主多从配置)

文章目录 MySQL主从同步1、MySQL主从同步原理2、一主多从配置2.1、准备主服务器2.2、准备从服务器2.3、启动主从同步2.4、实现主从同步2.5、停止和重置2.6、常见问题问题1问题2 MySQL主从同步 1、MySQL主从同步原理 基本原理&#xff1a; slave会从master读取binlog来进行数据…

软件架构的艺术:探索演化之路上的18大黄金原则

实际工作表明&#xff0c;一步到位的设计往往不切实际&#xff0c;而演化原则指导我们逐步优化架构&#xff0c;以灵活响应业务和技术的变化。这不仅降低了技术债务和重构风险&#xff0c;还确保了软件的稳定性和可扩展性。同时&#xff0c;架构的持续演进促进了团队协作&#…

SQL查询语句(二)逻辑运算关键字

上一篇文章中我们提到了条件查询除了一些简单的数学符号之外&#xff0c;还有一些用于条件判断的关键字&#xff0c;如逻辑判断 关键字AND,OR,NOT和范围查找关键字BETWEEN,IN等&#xff1b;下面我们来介绍一些这些关键字的用法以及他们所表达的含义。 目录 逻辑运算关键字 AND…

HarmonyOS实战开发教程-如何开发一个2048游戏

今天为大家分享的是2048小游戏&#xff0c;先看效果图&#xff1a; 这个项目对于新手友友来说可能有一点难度&#xff0c;但是只要坚持看完一定会有收获。因为小编想分享的并不局限于ArkTs语言&#xff0c;而是编程思想。 这个游戏的基本逻辑是初始化一个4乘4的数组&#xff…

【Toritoise SVN】SVN 怎么忽略文件夹下的所有文件但是不忽略文件夹本身

比如&#xff1a;忽略 Assets\StreamingAssets\LocalAsset文件夹下的所有文件但是不忽略LocalAsset这个文件夹 在TortoiseSVN中&#xff0c;你可以通过以下步骤来修改文件夹的svn:ignore属性&#xff1a; 打开Windows资源管理器&#xff0c;导航到你的工作副本中的Assets\Stre…

Python | Leetcode Python题解之第67题二进制求和

题目&#xff1a; 题解&#xff1a; class Solution:def addBinary(self, a, b) -> str:return {0:b}.format(int(a, 2) int(b, 2))

谷歌发布 HEAL 架构,4 步评估医学 AI 工具是否公平

如果把维持健康状态想象成一场赛跑&#xff0c;并不是所有人都能够站在统一起跑线上&#xff0c;有的人能够平稳的跑完全程&#xff0c;有的人即使跌倒也能够在第一时间获得帮助&#xff0c;但是有些人可能因为经济条件、居住地、教育水平、种族或其他因素而面临更多障碍。 「…

新火种AI|挑战谷歌,OpenAI要推出搜索引擎?

作者&#xff1a;一号 编辑&#xff1a;美美 在AI革新的浪潮下&#xff0c;谷歌搜索迎来了越来越多的“挑战者”。 最近&#xff0c;据多家外媒的消息&#xff0c;有知情人士透露&#xff0c;OpenAI正计划上线一款基于ChatGPT的大型产品&#xff0c;将提供一个新的搜索引擎&…

Ansible自动化运维工具 - playbook 剧本编写

一. inventory 主机清单 Inventory 支持对主机进行分组&#xff0c;每个组内可以定义多个主机&#xff0c;每个主机都可以定义在任何一个或多个主机组内。 1.1 inventory 中的变量含义 Inventory 变量名 含义ansible_hostansible连接节点时的IP地址ansible_port连接对方…

如何搜索空文件夹_名称为(纯或含)中/英/数/符

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 打开工具&#xff0c;切换到批量文件复制版块&#xff0c;快捷键Ctrl5 点击右侧的搜索添加 设定要搜索的范围、指定为文件夹、包括子目录&#xff0c;勾选…

【C语言】精品练习题

目录 题目一&#xff1a; 题目二&#xff1a; 题目三&#xff1a; 题目四&#xff1a; 题目五&#xff1a; 题目六&#xff1a; 题目七&#xff1a; 题目八&#xff1a; 题目九&#xff1a; 题目十&#xff1a; 题目十一&#xff1a; 题目十二&#xff1a; 题目十…

OFD(Open Fixed-layout Document)

OFD(Open Fixed-layout Document) &#xff0c;是由工业和信息化部软件司牵头中国电子技术标准化研究院成立的版式编写组制定的版式文档国家标准&#xff0c;属于中国的一种自主格式&#xff0c;要打破政府部门和党委机关电子公文格式不统一&#xff0c;以方便地进行电子文档的…
最新文章