linux驱动移植-GPIO控制器驱动
----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------
这一节我们来分析Mini2440 GPIO控制器驱动的源码,由于GPIO控制器驱动比较简单,所以这一节内容相对来说也是比较少的。
GPIO控制器驱动编写主要包含两个步骤:
- 为SoC的每个GPIO控制器分配一个gpio_chip,并进行初始化;
- 调用gpiochip_add注册每一个gpio_chip;
一、S3C2440 GPIO
根据平台的不同。GPIO控制器的数量也不同,GPIO控制器的寄存器端口映射方式和映射地址都不同,这些都是与平台先关的,需要平台去适配。
当然,不同的GPIO控制器的寄存器数量和功能也不尽相同,一般情况下,GPIO控制器都提供两个寄存器:一个是配置(控制)寄存器,另一个是数据寄存器。
S3C2440提供了9个GPIO控制器,分别为:
- 端口A(GPA):25位输出端口 ;
- 端口B(GPB):11 位输入/输出端口 ;
- 端口C(GPC):16 位输入/输出端口;
- 端口D(GPD):16 位输入/输出端口;
- 端口E(GPE):16 位输入/输出端口 ;
- 端口F(GPF):8 位输入/输出端口;
- 端口G(GPG):16 位输入/输出端口;
- 端口H(GPH):9 位输入/输出端口;
- 端口J(GPJ):13 位输入/输出端口;
其中每个GPIO控制器至少提供了2个32位寄存器:
- 端口配置寄存器:GPACON~GPJCON,配置寄存器的作用是,用来配置该芯片的每根引脚的输入输出状态或者特殊功能等;
- 端口数据寄存器:GPADAT~GPJDAT,如果端口配置为输出端口,可以写入数据到 PnDAT 的相应位。如果端口配置为输入端口,可以从 PnDAT 的相应位读取数据;
- 端口上拉寄存器:GPBUP~GPJUP,端口上拉寄存器控制每个端口组的使能/禁止上拉电阻。当相应位为0时使能引脚的上拉电阻,当为 1 时禁止上拉电阻。如果使能了上拉电阻,那么上拉电阻与引脚的功能设置无关(输入、输出、DATAn、EINTn 等等);
GPIOA的配置寄存器GPACON不同于其他GPIO的配置寄存器:
- 使用1bit控制GPIO的相应引脚状态,32bit寄存器使用低23bit控制23个引脚;
- 当控制位值为0的时候,表示该引脚是通用输出状态;
- 控制位为1的时候,引脚是特殊功能状态,用于控制其他外接器件(此时,数据寄存器GPADAT无效)。
因而,可以看出,GPIOA芯片不能实现通用输入功能。一般来说,GPIOA控制寄存器配置成1,用于控制其他外接器件。
GPIOB~GPIOJ的配置寄存器GPBCON~GPJCON,使用2bit控制的相应引脚状态(bit0,1控制引脚0,bit2,3控制引脚1,以此类推),每个GPIO控制器提供的引脚个数不尽相同,但最多只能提供32/2=16个引脚;
- 控制位为00时,表示相应引脚处于通用输入状态;
- 控制位为01时,表示相应引脚为通用输出状态;
- 控制位为10时,表示相应引脚为特殊功能状态;
- 控制位为11时,表示相应引脚为特殊功能状态,或者是预留;
在介绍之前,我们先了解一下三星为了GPIO定义的一些数据结构、
1.1 samsung_gpio_cfg
samsung_gpio_cfg这个结构体用来描述三星芯片GPIO的配置,它描述了 GPIO 端口在系统中的不同用途,并提供了一些控制函数来设置和读取GPIO端口的状态和配置信息。
类型定义在arch/arm/plat-samsung/include/plat/gpio-cfg.h:
/** * struct samsung_gpio_cfg GPIO configuration * @cfg_eint: Configuration setting when used for external interrupt source * @get_pull: Read the current pull configuration for the GPIO * @set_pull: Set the current pull configuration for the GPIO * @set_config: Set the current configuration for the GPIO * @get_config: Read the current configuration for the GPIO * * Each chip can have more than one type of GPIO bank available and some * have different capabilites even when they have the same control register * layouts. Provide an point to vector control routine and provide any * per-bank configuration information that other systems such as the * external interrupt code will need. * * @sa samsung_gpio_cfgpin * @sa s3c_gpio_getcfg * @sa s3c_gpio_setpull * @sa s3c_gpio_getpull */ struct samsung_gpio_cfg { unsigned int cfg_eint; samsung_gpio_pull_t (*get_pull)(struct samsung_gpio_chip *chip, unsigned offs); int (*set_pull)(struct samsung_gpio_chip *chip, unsigned offs, samsung_gpio_pull_t pull); unsigned (*get_config)(struct samsung_gpio_chip *chip, unsigned offs); int (*set_config)(struct samsung_gpio_chip *chip, unsigned offs, unsigned config); };
其成员如下:
- cfg_eint:用于外部中断源时的配置;
- get_pull:读取当前GPIO的上拉配置;
- set_pull:设置当前GPIO上拉配置;
- set_config:设置GPIO配置;
- get_config:获取GPIO配置;
该结构体的主要作用是提供每个GPIO bank的配置和控制函数的定义,为操作系统提供接口以便可以对GPIO进行读写操作。
需要注意的是,struct samsung_gpio_cfg 是在平台特定的gpio芯片驱动程序中使用的结构体,它们利用这些函数,通过配置寄存器来控制硬件设备的行为,读取设备状态,对外提供设备的访问接口。
1.2 samsung_gpio_chip
struct samsung_gpio_chip 是一个结构体,用于实现对特定 Samsung 平台上的 GPIO 控制器的封装。它包含了对 gpio_chip 的扩展信息,并提供其他必要的信息,以便让系统其他部分可以使用和识别这些信息。
定义在arch/arm/plat-samsung/include/plat/gpio-core.h:
/** * struct samsung_gpio_chip - wrapper for specific implementation of gpio * @chip: The chip structure to be exported via gpiolib. * @base: The base pointer to the gpio configuration registers. * @group: The group register number for gpio interrupt support. * @irq_base: The base irq number. * @config: special function and pull-resistor control information. * @lock: Lock for exclusive access to this gpio bank. * @pm_save: Save information for suspend/resume support. * @bitmap_gpio_int: Bitmap for representing GPIO interrupt or not. * * This wrapper provides the necessary information for the Samsung * specific gpios being registered with gpiolib. * * The lock protects each gpio bank from multiple access of the shared * configuration registers, or from reading of data whilst another thread * is writing to the register set. * * Each chip has its own lock to avoid any contention between different * CPU cores trying to get one lock for different GPIO banks, where each * bank of GPIO has its own register space and configuration registers. */ struct samsung_gpio_chip { struct gpio_chip chip; struct samsung_gpio_cfg *config; struct samsung_gpio_pm *pm; void __iomem *base; int irq_base; int group; spinlock_t lock; #ifdef CONFIG_PM u32 pm_save[4]; #endif u32 bitmap_gpio_int; };
其成员如下:
- chip:gpiolib核心数据结构gpio_chip;
- base:指向GPIO控制器寄存器基地址;
- group:GPIO中断支持的组寄存器编号;
- irq_base:中断base编号;
- config:用于对 GPIO 进行配置的 samsung_gpio_cfg 结构体指针;
- lock:用于对该 GPIO控制器进行锁定,保证在多线程环境下的安全性和共享性;
- bitmap_gpio_int:位映射,用于表示GPIO是否启用中断的位图;
二、分配gpio_chip
2.1 s3c24xx_gpios
我们定位到arch/arm/plat-samsung/gpio-samsung.c文件:
struct samsung_gpio_chip s3c24xx_gpios[] = { #ifdef CONFIG_PLAT_S3C24XX { .config = &s3c24xx_gpiocfg_banka, .chip = { .base = S3C2410_GPA(0), .owner = THIS_MODULE, .label = "GPIOA", .ngpio = 27, .direction_input = s3c24xx_gpiolib_banka_input, .direction_output = s3c24xx_gpiolib_banka_output, }, }, { .chip = { .base = S3C2410_GPB(0), .owner = THIS_MODULE, .label = "GPIOB", .ngpio = 11, }, }, { .chip = { .base = S3C2410_GPC(0), .owner = THIS_MODULE, .label = "GPIOC", .ngpio = 16, }, }, { .chip = { .base = S3C2410_GPD(0), .owner = THIS_MODULE, .label = "GPIOD", .ngpio = 16, }, }, { .chip = { .base = S3C2410_GPE(0), .label = "GPIOE", .owner = THIS_MODULE, .ngpio = 16, }, }, { .chip = { .base = S3C2410_GPF(0), .owner = THIS_MODULE, .label = "GPIOF", .ngpio = 8, .to_irq = s3c24xx_gpiolib_fbank_to_irq, }, }, { .irq_base = IRQ_EINT8, .chip = { .base = S3C2410_GPG(0), .owner = THIS_MODULE, .label = "GPIOG", .ngpio = 16, .to_irq = samsung_gpiolib_to_irq, }, }, { .chip = { .base = S3C2410_GPH(0), .owner = THIS_MODULE, .label = "GPIOH", .ngpio = 15, }, }, /* GPIOS for the S3C2443 and later devices. */ { .base = S3C2440_GPJCON, .chip = { .base = S3C2410_GPJ(0), .owner = THIS_MODULE, .label = "GPIOJ", .ngpio = 16, }, }, { .base = S3C2443_GPKCON, .chip = { .base = S3C2410_GPK(0), .owner = THIS_MODULE, .label = "GPIOK", .ngpio = 16, }, }, { .base = S3C2443_GPLCON, .chip = { .base = S3C2410_GPL(0), .owner = THIS_MODULE, .label = "GPIOL", .ngpio = 15, }, }, { .base = S3C2443_GPMCON, .chip = { .base = S3C2410_GPM(0), .owner = THIS_MODULE, .label = "GPIOM", .ngpio = 2, }, }, #endif };
根据SoC datasheet定义好每一个GPIO控制器寄存器的基地址和 GPIO的个数。以及支持中断的情况。
关于这个GPIO编号我们上一节已经介绍过了,我们为GPIO控制器下的每个GPIO分配一个唯一的编号,比如GPIOA端口编号范围为0~31。
2.2 s3c24xx_gpiocfg_banka
由于GPIOA比较特殊,所以s3c24xx_gpios对于GPIOA对config单独配置,s3c24xx_gpiocfg_banka:
static struct samsung_gpio_cfg s3c24xx_gpiocfg_banka = { .set_config = s3c24xx_gpio_setcfg_abank, .get_config = s3c24xx_gpio_getcfg_abank, };
这里我们大概看一下这两个函数s3c24xx_gpio_setcfg_abank、s3c24xx_gpio_getcfg_abank。这俩函数都定义在arch/arm/plat-samsung/gpio-samsung.c文件中。
2.2.1 s3c24xx_gpio_setcfg_abank
s3c24xx_gpio_setcfg_abank用于配置 S3C24XX 平台的 GPIO 端口。它是专门针对 bank A的GPIO 端口的配置函数。该函数的参数包含了要进行配置的GPIO控制器、端口号和配置值。
在 S3C24XX 平台上,每个GPIOA端口的控制寄存器都只有一个位来控制其模式。当该位设置为 1 时,GPIO 处于特殊模式,反之则表示 GPIO 处于输出模式。
s3c24xx_gpio_setcfg_abank函数实际上就是配置GPIOA第off个引脚为输出/其他功能。
/* * s3c24xx_gpio_setcfg_abank - S3C24XX style GPIO configuration (Bank A) * @chip: The gpio chip that is being configured. * @off: The offset for the GPIO being configured. * @cfg: The configuration value to set. * * This helper deal with the GPIO cases where the control register * has one bit of configuration for the gpio, where setting the bit * means the pin is in special function mode and unset means output. */ static int s3c24xx_gpio_setcfg_abank(struct samsung_gpio_chip *chip, unsigned int off, unsigned int cfg) { void __iomem *reg = chip->base; unsigned int shift = off; u32 con; if (samsung_gpio_is_cfg_special(cfg)) { cfg &= 0xf; /* Map output to 0, and SFN2 to 1 */ cfg -= 1; if (cfg > 1) return -EINVAL; cfg <<= shift; } con = __raw_readl(reg); // 读取CPACON寄存器 con &= ~(0x1 << shift); // 第shift位清0 con |= cfg; // 配置第shift位 __raw_writel(con, reg); return 0; }
s3c24xx_gpio_setcfg_abank函数实际上就是去读取GPACON寄存器的值,然后将第off位先清0,然后再去配置第off位的值,其值取自cfg第off位。
需要注意的是,该函数只适用于S3C24XX平台上bank A的GPIO端口的配置。如果需要对其他 GPIO 进行配置,则需要使用其他适配的函数。
2.2.2 s3c24xx_gpio_getcfg_abank
s3c24xx_gpio_getcfg_abank函数用于获取S3C24XX平台的GPIO端口的配置模式。它是专门针对 bank A 的 GPIO 端口的查询函数。该函数的参数包含了要查询的 GPIO 控制器和端口号。
在 S3C24XX 平台上,每个GPIOA端口的控制寄存器只有一个位来控制其模式。当该位设置为 1 时,GPIO 处于特殊模式,反之则表示 GPIO 处于输出模式。因此,通过该函数可以将 GPIO 的工作模式转换成可用的GPIO配置值,以方便对 GPIO 进行状态查询。
s3c24xx_gpio_getcfg_abank函数实际上就是去读取GPACON寄存器的值,然后通过位运算,获取第off位的值。
/* * s3c24xx_gpio_getcfg_abank - S3C24XX style GPIO configuration read (Bank A) * @chip: The gpio chip that is being configured. * @off: The offset for the GPIO being configured. * * The reverse of s3c24xx_gpio_setcfg_abank() turning an GPIO into a usable * GPIO configuration value. * * @sa samsung_gpio_getcfg_2bit * @sa samsung_gpio_getcfg_4bit */ static unsigned s3c24xx_gpio_getcfg_abank(struct samsung_gpio_chip *chip, unsigned int off) { u32 con; con = __raw_readl(chip->base); // 读取GPACON寄存器 con >>= off; // 右移off con &= 1; // &运算 con++; return S3C_GPIO_SFN(con); }
需要注意的是,该函数只适用于 S3C24XX 平台上 bank A 的 GPIO 端口的状态查询。如果需要对其他 GPIO 进行状态查询,则需要使用其他适配的函数。同时,该函数是 s3c24xx_gpio_setcfg_abank 函数的反向函数,通过两个函数可以方便地操作和查询 GPIO 的状态。
2.3 to_irq
上一节我们介绍过gpio_chip成员将to_irq作用是偏移为offset的GPIO映射到IRQ并返回相关的中断编号;那么我们这里以GPIOF控制器为例,其to_irq被初始化为了s3c24xx_gpiolib_fbank_to_irq:
static int s3c24xx_gpiolib_fbank_to_irq(struct gpio_chip *chip, unsigned offset) { if (offset < 4) { if (soc_is_s3c2412()) return IRQ_EINT0_2412 + offset; else return IRQ_EINT0 + offset; // 外部中断0 + 偏移 } if (offset < 8) return IRQ_EINT4 + offset - 4; return -EINVAL; }
我们去看GPFCON寄存器我们会发现当GPF0~GPF8对应引脚被配置为10时,其功能复用为外部中断。
在中断子系统中,每一个中断也是有一个IRQ编号,比如外部中断0~外部中断7等,定义在arch/arm/mach-s3c24xx/include/mach/irqs.h:
/* main cpu interrupts */ #define IRQ_EINT0 S3C2410_IRQ(0) /* 16 */ #define IRQ_EINT1 S3C2410_IRQ(1) #define IRQ_EINT2 S3C2410_IRQ(2) #define IRQ_EINT3 S3C2410_IRQ(3) #define IRQ_EINT4t7 S3C2410_IRQ(4) /* 20 */ /* interrupts generated from the external interrupts sources */ #define IRQ_EINT4 S3C2410_IRQ(36) /* 52 */ #define IRQ_EINT5 S3C2410_IRQ(37) #define IRQ_EINT6 S3C2410_IRQ(38) #define IRQ_EINT7 S3C2410_IRQ(39)
需要注意的是:
- IRQ_EINT0~IRQ_EINT3、IRQ~EINT4t7为主中断源;
- IRQ_EINT4~IRQ_EINT7为外部中断源,其对应的主中断源为IRQ~EINT4t7。
三、注册gpio_chip
在arch/arm/plat-samsung/gpio-samsung.c文件,定位到模块入口函数:
/* TODO: cleanup soc_is_* */ static __init int samsung_gpiolib_init(void) { /* * Currently there are two drivers that can provide GPIO support for * Samsung SoCs. For device tree enabled platforms, the new * pinctrl-samsung driver is used, providing both GPIO and pin control * interfaces. For legacy (non-DT) platforms this driver is used. */ if (of_have_populated_dt()) return 0; if (soc_is_s3c24xx()) { // 走这里 samsung_gpiolib_set_cfg(samsung_gpio_cfgs, ARRAY_SIZE(samsung_gpio_cfgs)); s3c24xx_gpiolib_add_chips(s3c24xx_gpios, ARRAY_SIZE(s3c24xx_gpios), S3C24XX_VA_GPIO); } else if (soc_is_s3c64xx()) { samsung_gpiolib_set_cfg(samsung_gpio_cfgs, ARRAY_SIZE(samsung_gpio_cfgs)); samsung_gpiolib_add_2bit_chips(s3c64xx_gpios_2bit, ARRAY_SIZE(s3c64xx_gpios_2bit), S3C64XX_VA_GPIO + 0xE0, 0x20); samsung_gpiolib_add_4bit_chips(s3c64xx_gpios_4bit, ARRAY_SIZE(s3c64xx_gpios_4bit), S3C64XX_VA_GPIO); samsung_gpiolib_add_4bit2_chips(s3c64xx_gpios_4bit2, ARRAY_SIZE(s3c64xx_gpios_4bit2)); } return 0; } core_initcall(samsung_gpiolib_init);
首先调用samsung_gpiolib_set_cfg初始化samsung_gpio_cfgs数组成员。
然后调用了 samsung_gpiolib_set_cfg的函数,就将gpio_chip结构体相关的成员进行了赋值,并最终调用到了gpiochip_add_data函数,将其注册到了内核的gpiolib 子系统。
3.1 samsung_gpiolib_set_cfg
samsung_gpiolib_set_cfg函数用来遍历samsung_gpio_chip数组,并依次配置:
- set_config:默认配置为samsung_gpio_setcfg_4bit;
- get_config:默认配置为samsung_gpio_getcfg_4bit;
- set_pull:默认配置为samsung_gpio_setpull_updown;
- get_pull:默认配置为samsung_gpio_getpull_updown;
static void __init samsung_gpiolib_set_cfg(struct samsung_gpio_cfg *chipcfg, int nr_chips) { for (; nr_chips > 0; nr_chips--, chipcfg++) { if (!chipcfg->set_config) chipcfg->set_config = samsung_gpio_setcfg_4bit; if (!chipcfg->get_config) chipcfg->get_config = samsung_gpio_getcfg_4bit; if (!chipcfg->set_pull) chipcfg->set_pull = samsung_gpio_setpull_updown; if (!chipcfg->get_pull) chipcfg->get_pull = samsung_gpio_getpull_updown; } }
samsung_gpio_cfgs数组定义如下,数组长度为8,依次对应S3C2440 GPA~GPH的配置。
static struct samsung_gpio_cfg samsung_gpio_cfgs[] = { [0] = { .cfg_eint = 0x0, }, [1] = { .cfg_eint = 0x3, }, [2] = { .cfg_eint = 0x7, }, [3] = { .cfg_eint = 0xF, }, [4] = { .cfg_eint = 0x0, .set_config = samsung_gpio_setcfg_2bit, .get_config = samsung_gpio_getcfg_2bit, }, [5] = { .cfg_eint = 0x2, .set_config = samsung_gpio_setcfg_2bit, .get_config = samsung_gpio_getcfg_2bit, }, [6] = { .cfg_eint = 0x3, .set_config = samsung_gpio_setcfg_2bit, .get_config = samsung_gpio_getcfg_2bit, }, [7] = { .set_config = samsung_gpio_setcfg_2bit, .get_config = samsung_gpio_getcfg_2bit, }, };
实际上关于samsung_gpio_cfgs数组的配置,我们在注册gpio_chip到gpiolib时并没有使用到,我们忽略就行。
3.2 s3c24xx_gpiolib_add_chips
samsung_gpiolib_set_cfg函数用来遍历samsung_gpio_chip数组,并依次初始化chip成员:config、base等,最后调用samsung_gpiolib_add注册chip;
static void __init s3c24xx_gpiolib_add_chips(struct samsung_gpio_chip *chip, int nr_chips, void __iomem *base) { int i; struct gpio_chip *gc = &chip->chip; for (i = 0 ; i < nr_chips; i++, chip++) { /* skip banks not present on SoC */ if (chip->chip.base >= S3C_GPIO_END) // #define S3C_GPIO_END (S3C2410_GPJ(0) + 32) 这里跳过了GPJ以及以及后面的接口 continue; if (!chip->config) chip->config = &s3c24xx_gpiocfg_default; // 如果没有设置config,默认是2bit配置操作。对GPIOA,已经显示配置成s3c24xx_gpiocfg_banka if (!chip->pm) chip->pm = __gpio_pm(&samsung_gpio_pm_2bit); // if ((base != NULL) && (chip->base == NULL)) chip->base = base + ((i) * 0x10); // GPIO控制器寄存器基地址 虚拟地址 if (!gc->direction_input) gc->direction_input = samsung_gpiolib_2bit_input; // 配置为输入 平台相关配置函数,默认2bit配置操作 if (!gc->direction_output) gc->direction_output = samsung_gpiolib_2bit_output; // 配置为输出 平台相关配置函数,默认2bit配置操作 samsung_gpiolib_add(chip); // 注册chip } }
执行完上面代码,我们就可以知道:
- GPIOA chip->config设置为了s3c24xx_gpiocfg_banka;
- GPIOB~GPIOJ chip->config设置为了s3c24xx_gpiocfg_default;
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了