当前位置: 首页 > news >正文

七、GPIO中断控制器(2)—— pcf8575

前言

上一小节,已经完成了基础的中断控制器驱动。现在需要完善并解决边缘检测的问题。这里可以参考 gpio-mcp23s08.c 驱动,仿造着它来实现,也可以直接参考 gpio-pcf857x.c 驱动,这个官方的驱动。后者使用的是 gpiolib_irqchip 的新版本API,实现起来较为简单,需要在config中配置 CONFIG_GPIOLIB_IRQCHIP 。这里介绍旧版本的API使用,无太大差异。

irq_chip

前面介绍的,在父中断处理函数中,我们直接一股脑的对所有电平变化的引脚触发中断处理函数,这将导致我们在使用GPIO扩展芯片的引脚时,设置的上升沿检测或者下降沿检测不起作用。因此,需要解决这个问题,就需要做一些额外的处理。当然,内核中的框架已经支持这些功能,只需要我们做一些简单接口实现就行了。这里就需要使用到 irq_chip 结构来完成这些事情。

static struct irq_chip pcf8575_irq_chip = {
    .name = "pcf8575_irqchip",
    .irq_mask = pcf8575_irq_mask,
    .irq_unmask = pcf8575_irq_unmask,
    .irq_set_type = pcf8575_irq_set_type,
    .irq_bus_lock = pcf8575_irq_bus_lock,
    .irq_bus_sync_unlock = pcf8575_irq_bus_unlock,
    .irq_request_resources = pcf8575_irq_reqres,
    .irq_release_resources = pcf8575_irq_relres,
};

参照 mcp23s08 的驱动来实现。如果没有定义 irq_enable,即irq_enable为NULL,那么 irq_enable 将映射到 irq_unmask 。同理,irq_disable 也是一样,会映射到 irq_mask 。因此这里实现其一即可。irq_set_type 就是我们实现上升沿和下降沿检测要用到的最关键的方法了。它会把用户配置传进来,这里的用户配置,指的配置使用GPIO扩展芯片的用户。因此,我们可以在这个方法中获取到对于某个引脚的中断检测配置。然后保存下来,在父中断处理函数中使用。

static irqreturn_t pcf8575_irq(int irq, void *data)
{
    struct pcf8575_dev *pcf8575 = data;
    unsigned int child_irq, i;
    u16 value;
    pcf8575_i2c_read(pcf8575->client, &value);

    for (i = 0; i < pcf8575->chip.ngpio; i++) {
        if (((pcf8575->in_mask >> i) & 0x01) == 0x01) {
            if (((value >> i) & 0x01) != ((pcf8575->r_val >> i) & 0x01)
                && !(pcf8575->irq_mask & (1 << i))
                && ((((pcf8575->irq_rising & (1 << i))) && (value & (1 << i)))
                    || ((pcf8575->irq_falling & (1 << i)) && !(value & (1 << i)))))
            {
                pr_info("pcf8575 irq pin: %d", i);
                child_irq = irq_find_mapping(pcf8575->irq_domain, i);
                handle_nested_irq(child_irq);
            }
        }
    }
    pcf8575->r_val = value & pcf8575->in_mask;
    return IRQ_HANDLED;
}
static void pcf8575_irq_mask(struct irq_data *data)
{
    struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

    pcf8575->irq_mask |= (1 << data->hwirq);
}

static void pcf8575_irq_unmask(struct irq_data *data)
{
    struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

    pcf8575->irq_mask &= ~(1 << data->hwirq);
}

static void pcf8575_irq_bus_lock(struct irq_data *data)
{
    struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

	mutex_lock(&pcf8575->irq_lock);
}

static void pcf8575_irq_bus_unlock(struct irq_data *data)
{
    struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

	mutex_unlock(&pcf8575->irq_lock);
}

static int pcf8575_irq_reqres(struct irq_data *data)
{
	struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

	if (gpiochip_lock_as_irq(&pcf8575->chip, data->hwirq)) {
		dev_err(pcf8575->chip.parent,
			"unable to lock HW IRQ %lu for IRQ usage\n",
			data->hwirq);
		return -EINVAL;
	}

	return 0;
}

static void pcf8575_irq_relres(struct irq_data *data)
{
	struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

	gpiochip_unlock_as_irq(&pcf8575->chip, data->hwirq);
}

static int pcf8575_irq_set_type(struct irq_data *data, unsigned int type)
{
    struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

    if ((type & IRQ_TYPE_EDGE_BOTH) ==  IRQ_TYPE_EDGE_BOTH) {
        pcf8575->irq_rising |= (1 << data->hwirq);
        pcf8575->irq_falling |= (1 << data->hwirq);
    } else if (type & IRQ_TYPE_EDGE_RISING) {
        pcf8575->irq_rising |= (1 << data->hwirq);
        pcf8575->irq_falling &= ~(1 << data->hwirq);
    } else if (type & IRQ_TYPE_EDGE_FALLING) {
        pcf8575->irq_falling |= (1 << data->hwirq);
        pcf8575->irq_rising &= ~(1 << data->hwirq);
    } else {
        return -EINVAL;
    }
    return 0;
}

irq_domain_ops

在 irq_domain_ops.map 中,我们使用我们自己定义的这个 irq_chip,作为参数传入,而不再使用 dummy_irq_chip

static int pcf8575_irq_domain_map(struct irq_domain *domain, unsigned int virq,
                irq_hw_number_t hw)
{
	struct pcf8575_dev *pcf8575 = domain->host_data;
	irq_set_chip_data(virq, pcf8575);
    irq_set_chip_and_handler(virq, &pcf8575_irq_chip, handle_edge_irq);
    return 0;
}

这里需要特别注意的是,上一节的驱动是集中创建映射,在调用 irq_create_mapping 后才生成irq号,然后我们才使用 irq_set_chip_data 为每个 irq 设置私有数据。但是这里会出现一个bug。由于在 irq_create_mapping 中会调用到:

static void pcf8575_irq_bus_lock(struct irq_data *data)
{
    struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

	mutex_lock(&pcf8575->irq_lock);
}

由于还没有调用 irq_set_chip_data 设置数据,就要获取数据,就会导致这里调用 irq_data_get_irq_chip_data 获取 pcf8575 对象不会成功,会得到一个空指针,导致panic。因此我们不再 probe 中集中的为每个引脚创建映射,而是在 chip.to_irq 回调汇总,分别为每个引脚创建映射。这里就可以在 pcf8575_irq_domain_map 中为 irq 号的中断号设置 chip_data, 然后再使用 pcf8575_irq_bus_lock 时,就不会获取到空指针了。

因此在 probe 中,取消集中创建映射:

    pcf8575->chip.to_irq = pcf8575_gpio_to_irq;
	pcf8575->irq_domain->host_data = pcf8575;
    // for (i = 0; i < pcf8575->chip.ngpio; i++) {
    //     irq = irq_create_mapping(pcf8575->irq_domain, i);
	// 	irq_set_chip_data(irq, pcf8575);
    // }

chip.to_irq 中,分别为每个pin脚创建映射:

static int pcf8575_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
{
    struct pcf8575_dev *pcf8575 = container_of(chip, struct pcf8575_dev, chip);
	return irq_create_mapping(pcf8575->irq_domain, offset);
	// return irq_find_mapping(pcf8575->irq_domain, offset);
}

验证

完成上述修改后进行测试验证。

[root@rk3399:/sys/devices/platform/pinctrl/gpio/gpio41]# echo 1 > value
[root@rk3399:/sys/devices/platform/pinctrl/gpio/gpio41]# echo 0 > value
[root@rk3399:/sys/devices/platform/pinctrl/gpio/gpio41]# [ 2156.454264] pcf8575 irq pin: 7
[ 2156.461633] intb irq handler

[root@rk3399:/sys/devices/platform/pinctrl/gpio/gpio41]# echo 1 > value
[root@rk3399:/sys/devices/platform/pinctrl/gpio/gpio41]# echo 0 > value
[root@rk3399:/sys/devices/platform/pinctrl/gpio/gpio41]# [ 2163.200807] pcf8575 irq pin: 7
[ 2163.208004] intb irq handler

[root@rk3399:/sys/devices/platform/pinctrl/gpio/gpio41]# echo 0 >value
[root@rk3399:/sys/devices/platform/pinctrl/gpio/gpio41]# echo 0 > value
[root@rk3399:/sys/devices/platform/pinctrl/gpio/gpio41]# echo 1 > value
[root@rk3399:/sys/devices/platform/pinctrl/gpio/gpio41]# echo 1 > value
[root@rk3399:/sys/devices/platform/pinctrl/gpio/gpio41]# echo 0 > value
[root@rk3399:/sys/devices/platform/pinctrl/gpio/gpio41]# [ 2176.609468] pcf8575 irq pin: 7
[ 2176.616762] intb irq handler

[root@rk3399:/sys/devices/platform/pinctrl/gpio/gpio41]#

可以看到,只有当我们电平变化是下降沿时,才会用中断处理函数的触发。

可以修改用户代码,申请中断线程化时, 设置上升沿有效进行测试。另外,设置高电平触发和低电平触发,会发现,无法申请成功。这是由于,我们的 irq_set_type 回调中,只支持了边沿检测,电平检测会直接返回 -EINVAL 。可以在其中修改,让其支持高低电平触发。以上就是一个较为完整的中断控制器驱动了。

代码

基于上节驱动代码进行修改。

#include "linux/device.h"
#include "linux/gpio/driver.h"
#include "linux/interrupt.h"
#include <linux/irq.h>
#include <linux/mutex.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/of_irq.h>
#include <linux/gpio/driver.h>

#define GPIO_NUM 16

struct pcf8575_dev {
    struct i2c_client *client;
    struct gpio_chip chip;

    u16 value;
    u16 in_mask;
    u16 r_val;

    struct irq_domain *irq_domain;
    struct mutex irq_lock;
    u16 irq_mask;
    u16 irq_rising;
    u16 irq_falling;
};

static int pcf8575_i2c_read(struct i2c_client *client, u16 *value)
{
    u8 data[2] = {0};
    struct i2c_msg msg[] = {
        {
            .flags = I2C_M_RD | I2C_M_STOP,
            .addr = client->addr,
            .buf = data,
            .len = ARRAY_SIZE(data),
        },
    };

    if (i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)) != ARRAY_SIZE(msg)) {
        dev_err(&client->dev, "i2c read error, chip=0x%02x\n", client->addr);
        return -EREMOTEIO;
    }

    *value = data[0] | (data[1] << 8);
    return 0;
}

static int pcf8575_i2c_write(struct i2c_client *client, u16 value)
{
    u8 data[2] = {(value & 0xFF), ((value >> 8) & 0xFF)};
    struct i2c_msg msg[] = {
        {
            .flags = I2C_M_STOP,
            .addr = client->addr,
            .buf = data,
            .len = ARRAY_SIZE(data),
        },
    };

    if (i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)) != ARRAY_SIZE(msg)) {
        dev_err(&client->dev, "i2c write error, chip=0x%02x\n", client->addr);
        return -EREMOTEIO;
    }

    return 0;
}

static int pcf8575_get_value(struct gpio_chip *chip, unsigned offset)
{
    u16 value = 0;
    struct pcf8575_dev *pcf8575 = container_of(chip, struct pcf8575_dev, chip);

    if (offset >= GPIO_NUM) {
        dev_err(&pcf8575->client->dev, "offset over %d\n", GPIO_NUM);
        return 0;
    }
    
    pcf8575_i2c_read(pcf8575->client, &value);
    pcf8575->r_val = value & pcf8575->in_mask;

    return (value & (1 << offset));
}

static void pcf8575_set_value(struct gpio_chip *chip, unsigned offset, int value)
{
    struct pcf8575_dev *pcf8575 = container_of(chip, struct pcf8575_dev, chip);

    if (offset >= GPIO_NUM) {
        dev_err(&pcf8575->client->dev, "offset over %d\n", GPIO_NUM);
        return ;
    }

    if (value) {
        pcf8575->value |= (1 << offset);
    } else {
        pcf8575->value &= (~(1 << offset));
    }

    pcf8575_i2c_write(pcf8575->client, pcf8575->value);
}

static int pcf8575_direction_input(struct gpio_chip *chip, unsigned offset)
{
    struct pcf8575_dev *pcf8575 = container_of(chip, struct pcf8575_dev, chip);
    pcf8575->value |= (1 << offset);
    pcf8575->in_mask |= (1 << offset);

    pcf8575_i2c_write(pcf8575->client, pcf8575->value);

    return 0;
}

static int pcf8575_direction_output(struct gpio_chip *chip, unsigned offset,
                                    int value)
{
    struct pcf8575_dev *pcf8575 = container_of(chip, struct pcf8575_dev, chip);
    pcf8575->in_mask &= (~(1 << offset));
    pcf8575_set_value(chip, offset, value);

    return 0;
}

static void pcf8575_irq_mask(struct irq_data *data)
{
    struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

    pcf8575->irq_mask |= (1 << data->hwirq);
}

static void pcf8575_irq_unmask(struct irq_data *data)
{
    struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

    pcf8575->irq_mask &= ~(1 << data->hwirq);
}

static int pcf8575_irq_set_type(struct irq_data *data, unsigned int type)
{
    struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

    if ((type & IRQ_TYPE_EDGE_BOTH) ==  IRQ_TYPE_EDGE_BOTH) {
        pcf8575->irq_rising |= (1 << data->hwirq);
        pcf8575->irq_falling |= (1 << data->hwirq);
    } else if (type & IRQ_TYPE_EDGE_RISING) {
        pcf8575->irq_rising |= (1 << data->hwirq);
        pcf8575->irq_falling &= ~(1 << data->hwirq);
    } else if (type & IRQ_TYPE_EDGE_FALLING) {
        pcf8575->irq_falling |= (1 << data->hwirq);
        pcf8575->irq_rising &= ~(1 << data->hwirq);
    } else {
        return -EINVAL;
    }
    return 0;
}

static void pcf8575_irq_bus_lock(struct irq_data *data)
{
    struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

	mutex_lock(&pcf8575->irq_lock);
}

static void pcf8575_irq_bus_unlock(struct irq_data *data)
{
    struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

	mutex_unlock(&pcf8575->irq_lock);
}

static int pcf8575_irq_reqres(struct irq_data *data)
{
	struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

	if (gpiochip_lock_as_irq(&pcf8575->chip, data->hwirq)) {
		dev_err(pcf8575->chip.parent,
			"unable to lock HW IRQ %lu for IRQ usage\n",
			data->hwirq);
		return -EINVAL;
	}

	return 0;
}

static void pcf8575_irq_relres(struct irq_data *data)
{
	struct pcf8575_dev *pcf8575 = irq_data_get_irq_chip_data(data);

	gpiochip_unlock_as_irq(&pcf8575->chip, data->hwirq);
}

static struct irq_chip pcf8575_irq_chip = {
    .name = "pcf8575_irqchip",
    .irq_mask = pcf8575_irq_mask,
    .irq_unmask = pcf8575_irq_unmask,
    .irq_set_type = pcf8575_irq_set_type,
    .irq_bus_lock = pcf8575_irq_bus_lock,
    .irq_bus_sync_unlock = pcf8575_irq_bus_unlock,
    .irq_request_resources = pcf8575_irq_reqres,
    .irq_release_resources = pcf8575_irq_relres,
};

static int pcf8575_irq_domain_map(struct irq_domain *domain, unsigned int virq,
                irq_hw_number_t hw)
{
	struct pcf8575_dev *pcf8575 = domain->host_data;
	irq_set_chip_data(virq, pcf8575);
    irq_set_chip_and_handler(virq, &pcf8575_irq_chip, handle_edge_irq);
    return 0;
}

static struct irq_domain_ops pcf8575_irq_domain_ops = {
    .map = pcf8575_irq_domain_map,
    .xlate = irq_domain_xlate_twocell,
};

static irqreturn_t pcf8575_irq(int irq, void *data)
{
    struct pcf8575_dev *pcf8575 = data;
    unsigned int child_irq, i;
    u16 value;
	// bool cond1, cond2, cond3;

    pcf8575_i2c_read(pcf8575->client, &value);
    // pr_info("pcf8575 irq handle, value:0x%02x, r_val:0x%02x\n", value, pcf8575->r_val);

    for (i = 0; i < pcf8575->chip.ngpio; i++) {
        if (((pcf8575->in_mask >> i) & 0x01) == 0x01) {
            // pr_info("pcf8575->irq_mask:0x%04x", pcf8575->irq_mask);
            // pr_info("pcf8575->irq_rising:0x%04x", pcf8575->irq_rising);
            // pr_info("pcf8575->irq_falling:0x%04x", pcf8575->irq_falling);
			// cond1 = ((value >> i) & 0x01) != ((pcf8575->r_val >> i) & 0x01);
			// cond2 = !(pcf8575->irq_mask & (1 << i));
			// cond3 = ((((pcf8575->irq_rising & (1 << i))) && (value & (1 << i)))
            //         || ((pcf8575->irq_falling & (1 << i)) && !(value & (1 << i))));
			// pr_info("cond1: %d, cond2: %d, cond3: %d", cond1, cond2, cond3);

            if (((value >> i) & 0x01) != ((pcf8575->r_val >> i) & 0x01)
                && !(pcf8575->irq_mask & (1 << i))
                && ((((pcf8575->irq_rising & (1 << i))) && (value & (1 << i)))
                    || ((pcf8575->irq_falling & (1 << i)) && !(value & (1 << i)))))
            {
                pr_info("pcf8575 irq pin: %d", i);

                child_irq = irq_find_mapping(pcf8575->irq_domain, i);
                handle_nested_irq(child_irq);
            }
        }
    }

    /* If only we read from ports, we must cache last input value after using pcf8575_i2c_read */
    pcf8575->r_val = value & pcf8575->in_mask;
    return IRQ_HANDLED;
}

static int pcf8575_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
{
    struct pcf8575_dev *pcf8575 = container_of(chip, struct pcf8575_dev, chip);

	return irq_create_mapping(pcf8575->irq_domain, offset);

	// return irq_find_mapping(pcf8575->irq_domain, offset);
}

static int pcf8575_irq_setup(struct pcf8575_dev *pcf8575)
{
    struct device *dev = &pcf8575->client->dev;
    int ret = 0;
    // int i = 0;
    // u32 irq;

    pcf8575->in_mask = 0;
    pcf8575->r_val = 0;
    pcf8575->irq_mask = 0xffff;
    mutex_init(&pcf8575->irq_lock);

    // pcf8575->client->irq = irq_of_parse_and_map(pcf8575->client->dev.of_node, 0);

    pcf8575->irq_domain = irq_domain_add_linear(dev->of_node,
        pcf8575->chip.ngpio, &pcf8575_irq_domain_ops, NULL);
    if (IS_ERR(pcf8575->irq_domain)) {
        mutex_destroy(&pcf8575->irq_lock);
        return PTR_ERR(pcf8575->irq_domain);
    }

    ret = devm_request_threaded_irq(dev, pcf8575->client->irq, NULL, pcf8575_irq,
        IRQF_ONESHOT | IRQF_TRIGGER_FALLING, dev_name(dev), pcf8575);
    if (ret) {
        mutex_destroy(&pcf8575->irq_lock);
        return ret;
    }

    pcf8575->chip.to_irq = pcf8575_gpio_to_irq;
	/**
	 * There is a bug which was occured by mutex_lock.
	 * Before we invoked irq_create_mapping, we doesn't 
	 * set irq_data to pcf8575 object. Beacuse of we can't get 
	 * the virq parameter before invoking the irq_create_mapping.
	 * So, When we get pcf8575
	 * object from the irq_data, we get the NULL. It occured a 
	 * NULL pointer panic. As above, we can invok irq_create_mapping
	 * in the chip.to_irq, we get the virq, and set pcf8575 object
	 * to irq_chip.
	 */
	pcf8575->irq_domain->host_data = pcf8575;

	/**
	 * Can create mapping concentrated in here,
	 * Or create maping in the chip.to_irq for every pin
	 */
    // for (i = 0; i < pcf8575->chip.ngpio; i++) {
    //     irq = irq_create_mapping(pcf8575->irq_domain, i);
	// 	irq_set_chip_data(irq, pcf8575);
    // }

    return 0;
}

static int pcf8575_irq_remove(struct pcf8575_dev *pcf8575)
{
    int i;
    int virq;

    for (i = 0; i < pcf8575->chip.ngpio; i++) {
        virq = pcf8575_gpio_to_irq(&pcf8575->chip, i);
        irq_dispose_mapping(virq);
    }

    irq_domain_remove(pcf8575->irq_domain);
    mutex_destroy(&pcf8575->irq_lock);
    return 0;
}

static int pcf8575_probe(struct i2c_client *client,
                        const struct i2c_device_id *id)
{
    struct pcf8575_dev *pcf8575;
    int ret;

    if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
        dev_err(&client->dev, "i2c_check_functionality not support I2C_FUNC_I2C\n");
        return -EIO;
    }

    pcf8575 = devm_kzalloc(&client->dev, sizeof(struct pcf8575_dev), GFP_KERNEL);
    if (IS_ERR(pcf8575)) {
        dev_err(&client->dev, "kzalloc error: 0x%lx\n", PTR_ERR(pcf8575));
        return PTR_ERR(pcf8575);
    }

    pcf8575->chip.label = client->name;
    pcf8575->chip.base = -1;
    pcf8575->chip.parent = &client->dev;
    pcf8575->chip.owner = THIS_MODULE,
    pcf8575->chip.ngpio = GPIO_NUM;
    pcf8575->chip.can_sleep = 1;
    pcf8575->chip.get = pcf8575_get_value,
    pcf8575->chip.set = pcf8575_set_value,
    pcf8575->chip.direction_input = pcf8575_direction_input,
    pcf8575->chip.direction_output = pcf8575_direction_output,

    /* default value is 0xFFFF after power on */
    pcf8575->value = 0xFFFF;

    i2c_set_clientdata(client, pcf8575);
    pcf8575->client = client;

    ret = devm_gpiochip_add_data(&client->dev, &pcf8575->chip, NULL);
    if (ret) {
        return ret;
    }

    ret = pcf8575_irq_setup(pcf8575);
    if (ret) {
        return ret;
    }

    dev_info(&client->dev, "pcf8575 probe success\n");
    return 0;
}

static int pcf8575_remove(struct i2c_client *client)
{
    struct pcf8575_dev *pcf8575 = i2c_get_clientdata(client);
    pcf8575_irq_remove(pcf8575);
    return 0;
}

static struct i2c_device_id pcf8575_id_table[] = {
    {"pcf8575", 0},
    {},
};
MODULE_DEVICE_TABLE(i2c, pcf8575_id_table);

static const struct of_device_id pcf8575_of_match[] = {
    {.compatible = "nxp,pcf8575_new"},
    { },
};
MODULE_DEVICE_TABLE(of, pcf8575_of_match);

static struct i2c_driver pcf8575_driver = {
    .probe = pcf8575_probe,
    .remove = pcf8575_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "pcf8575_drv",
        .of_match_table = of_match_ptr(pcf8575_of_match),
    },
    .id_table = pcf8575_id_table,
};

module_i2c_driver(pcf8575_driver);

MODULE_AUTHOR("duapple <duapple2@gmail.com>");
MODULE_LICENSE("GPL");

相关文章:

  • 阅读li2019-DOT源码--逐步调试
  • 【机器学习】什么是逻辑回归?
  • 分页查询互动问题(管理端)
  • 测试工程 常用Python库
  • FPGA_DDS_IP核
  • 【RHCE】LVS-NAT模式负载均衡实验
  • 那些正常的动态规划
  • Tekton系列之实践篇-从触发到完成的完整执行过程
  • 流影---开源网络流量分析平台(一)(小白超详细)
  • SAP-ABAP:SAP报错全链路排查指南:从界面告警到代码级诊断
  • 于纷扰中寻静谧:正念观照的智慧之旅
  • XSS复现漏洞简单前八关靶场
  • G口服务器和普通服务器之间的区别
  • LeetCode热题100JS(/100)第十三天|34
  • C语言:扫雷
  • 为什么不同的损失函数可以提升模型性能?
  • 使用QRencode生成二维码
  • selenium基本使用(一)
  • 【微前端-致命隐患】主子应用异步加载
  • C++11QT复习 (三)
  • 德雷克海峡发生7.4级地震,震源深度10千米
  • 上海浪琴环球马术冠军赛明日启幕!五一假期在这里感受精彩
  • 美国经济萎缩意味着什么?关税政策如何反噬经济?
  • 五大国货美妆去年业绩分化:珀莱雅百亿营收领跑,上海家化转亏
  • 平安资管总经理罗水权因个人工作原因辞职
  • 《求是》杂志发表习近平总书记重要文章《激励新时代青年在中国式现代化建设中挺膺担当》