Linux 内核提供了一套可缓存的设备 IO 寄存器访问机制,即 regmapregmap 机制支持以统一的接口,访问多种不同类型的设备 IO 寄存器,如内存映射的设备 IO 寄存器,和需要通过 I2C、I3C、SPI、AC97 和 SLIMBUS 等总线访问的设备寄存器等。内存映射设备 IO 寄存器和 I2C 总线是嵌入式系统中比较常见的控制设备的方式,regmap 机制对这些方式提供了良好的支持。

内存映射设备 IO 寄存器访问

内存映射设备 IO 寄存器的设备将设备的 IO 寄存器映射到物理内存地址空间,软件访问设备 IO 寄存器就像访问普通的物理内存一样。设备的 IO 寄存器在物理内存地址空间中的具体区域,由整个硬件系统的设计决定。

由于内存管理单元 MMU 及虚拟内存的应用,在 Linux 内核设备驱动程序中,也无法直接通过物理内存地址来访问设备的 IO 寄存器。在 Linux 内核设备驱动程序中,访问内存映射设备 IO 寄存器的方法如下:

  1. 在设备树文件的设备节点定义中说明内存映射设备 IO 寄存器在物理内存地址空间中的地址范围,这包括起始物理地址和地址空间大小,如 arch/arm64/boot/dts/rockchip/rk3399.dtsi 文件中 I2S0 设备节点的定义:
	i2s0: i2s@ff880000 {
compatible = "rockchip,rk3399-i2s", "rockchip,rk3066-i2s";
reg = <0x0 0xff880000 0x0 0x1000>;
. . . . . .
};

reg = <0x0 0xff880000 0x0 0x1000>; 行说明了这个设备的内存映射设备 IO 寄存器在物理内存地址空间中的地址范围,起始地址为 0xff880000,地址空间大小为 0x1000,即 4KB。

  1. 创建寄存器映射配置,如 sound/soc/rockchip/rockchip_i2s.c 文件中的寄存器映射配置:
static bool rockchip_i2s_wr_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case I2S_TXCR:
case I2S_RXCR:
case I2S_CKR:
case I2S_DMACR:
case I2S_INTCR:
case I2S_XFER:
case I2S_CLR:
case I2S_TXDR:
return true;
default:
return false;
}
} static bool rockchip_i2s_rd_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case I2S_TXCR:
case I2S_RXCR:
case I2S_CKR:
case I2S_DMACR:
case I2S_INTCR:
case I2S_XFER:
case I2S_CLR:
case I2S_TXDR:
case I2S_RXDR:
case I2S_FIFOLR:
case I2S_INTSR:
return true;
default:
return false;
}
} static bool rockchip_i2s_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case I2S_INTSR:
case I2S_CLR:
case I2S_FIFOLR:
case I2S_TXDR:
case I2S_RXDR:
return true;
default:
return false;
}
} static bool rockchip_i2s_precious_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case I2S_RXDR:
return true;
default:
return false;
}
} static const struct reg_default rockchip_i2s_reg_defaults[] = {
{0x00, 0x0000000f},
{0x04, 0x0000000f},
{0x08, 0x00071f1f},
{0x10, 0x001f0000},
{0x14, 0x01f00000},
}; static const struct regmap_config rockchip_i2s_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = I2S_RXDR,
.reg_defaults = rockchip_i2s_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(rockchip_i2s_reg_defaults),
.writeable_reg = rockchip_i2s_wr_reg,
.readable_reg = rockchip_i2s_rd_reg,
.volatile_reg = rockchip_i2s_volatile_reg,
.precious_reg = rockchip_i2s_precious_reg,
.cache_type = REGCACHE_FLAT,
};

寄存器映射配置由 struct regmap_config 结构体来描述,它告诉 regmap 机制设备寄存器的位宽,设备寄存器的值的位宽,最大的设备寄存器,有特殊默认值的设备寄存器的默认值,设备寄存器的读写属性,及设备寄存器访问的缓存类型等。

Linux 设备驱动程序通过寄存器映射配置,为 regmap 机制提供缓存访问所需的信息。

可能有很多人觉得,由于设备 IO 寄存器的特殊性,对设备 IO 寄存器的访问都是不加缓存的。实际上,CPU 不缓存对设备 IO 寄存器的访问,但 Linux 内核的 regmap 机制,在内存中开辟了一块区域来做设备 IO 寄存器的缓存访问。

  1. 在设备驱动程序的 probe 操作中,获得 IO 重映射资源,并初始化内存映射 IO,创建 struct regmap 结构对象,如 sound/soc/rockchip/rockchip_i2s.c 文件中的 rockchip_i2s_probe() 操作:
static int rockchip_i2s_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
const struct of_device_id *of_id;
struct rk_i2s_dev *i2s;
struct snd_soc_dai_driver *soc_dai;
struct resource *res;
void __iomem *regs;
. . . . . .
regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(regs)) {
ret = PTR_ERR(regs);
goto err_clk;
} i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
&rockchip_i2s_regmap_config);
if (IS_ERR(i2s->regmap)) {
dev_err(&pdev->dev,
"Failed to initialise managed register map\n");
ret = PTR_ERR(i2s->regmap);
goto err_clk;
}
. . . . . .
return ret;
}

这里看到 devm_platform_get_and_ioremap_resource() 函数,获得在设备树文件中定义的 IO 重映射资源。它的返回值是类型为 void __iomem * 的内存映射设备 IO 寄存器地址空间基地址在内核内存地址空间的虚拟地址,并通过传出参数 struct resource 返回内存映射设备 IO 寄存器的物理地址空间。

通过 devm_platform_get_and_ioremap_resource() 函数返回的指针,Linux 设备驱动程序可以像访问普通内存一样访问设备 IO 寄存器,如:

	u32 val;
. . . . . .
val = readl(regs + I2C_REG_STATUS);
. . . . . .
writel(regs + I2C_REG_CONFIGURE, 1);

regmap 机制可以提供的更多,如方便的读、写和更新操作函数,缓存的访问,及通过 debugfs 分析调试的能力等。调用 devm_regmap_init_mmio() 函数创建 struct regmap 是明智的。

  1. 通过 regmap 机制提供的读、写和更新等操作函数访问设备 IO 寄存器,如 sound/soc/rockchip/rockchip_i2s.c 文件中的如下代码片段:
		if (!i2s->rx_start) {
regmap_update_bits(i2s->regmap, I2S_XFER,
I2S_XFER_TXS_START |
I2S_XFER_RXS_START,
I2S_XFER_TXS_STOP |
I2S_XFER_RXS_STOP); udelay(150);
regmap_update_bits(i2s->regmap, I2S_CLR,
I2S_CLR_TXC | I2S_CLR_RXC,
I2S_CLR_TXC | I2S_CLR_RXC); regmap_read(i2s->regmap, I2S_CLR, &val); /* Should wait for clear operation to finish */
while (val) {
regmap_read(i2s->regmap, I2S_CLR, &val);
retry--;
if (!retry) {
dev_warn(i2s->dev, "fail to clear\n");
break;
}
}
}

可用以访问设备 IO 寄存器的函数有很多,如:

int regmap_write(struct regmap *map, unsigned int reg, unsigned int val);
int regmap_write_async(struct regmap *map, unsigned int reg, unsigned int val);
int regmap_raw_write(struct regmap *map, unsigned int reg,
const void *val, size_t val_len); int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);
int regmap_raw_read(struct regmap *map, unsigned int reg,
void *val, size_t val_len); int regmap_update_bits_base(struct regmap *map, unsigned int reg,
unsigned int mask, unsigned int val,
bool *change, bool async, bool force); static inline int regmap_update_bits(struct regmap *map, unsigned int reg,
unsigned int mask, unsigned int val)
{
return regmap_update_bits_base(map, reg, mask, val, NULL, false, false);
} static inline int regmap_set_bits(struct regmap *map,
unsigned int reg, unsigned int bits)
{
return regmap_update_bits_base(map, reg, bits, bits,
NULL, false, false);
} static inline int regmap_clear_bits(struct regmap *map,
unsigned int reg, unsigned int bits)
{
return regmap_update_bits_base(map, reg, bits, 0, NULL, false, false);
} int regmap_test_bits(struct regmap *map, unsigned int reg, unsigned int bits);

访问内存映射的设备 IO 寄存器是设备驱动程序所需的最基本的操作。

regmap 的实现

struct regmap_config 的定义 (位于 include/linux/regmap.h) 如下:

enum regcache_type {
REGCACHE_NONE,
REGCACHE_RBTREE,
REGCACHE_COMPRESSED,
REGCACHE_FLAT,
};
. . . . . .
struct reg_default {
unsigned int reg;
unsigned int def;
};
. . . . . .
struct regmap_range {
unsigned int range_min;
unsigned int range_max;
}; #define regmap_reg_range(low, high) { .range_min = low, .range_max = high, }
. . . . . .
struct regmap_access_table {
const struct regmap_range *yes_ranges;
unsigned int n_yes_ranges;
const struct regmap_range *no_ranges;
unsigned int n_no_ranges;
}; typedef void (*regmap_lock)(void *);
typedef void (*regmap_unlock)(void *);
. . . . . .
struct regmap_config {
const char *name; int reg_bits;
int reg_stride;
int pad_bits;
int val_bits; bool (*writeable_reg)(struct device *dev, unsigned int reg);
bool (*readable_reg)(struct device *dev, unsigned int reg);
bool (*volatile_reg)(struct device *dev, unsigned int reg);
bool (*precious_reg)(struct device *dev, unsigned int reg);
bool (*writeable_noinc_reg)(struct device *dev, unsigned int reg);
bool (*readable_noinc_reg)(struct device *dev, unsigned int reg); bool disable_locking;
regmap_lock lock;
regmap_unlock unlock;
void *lock_arg; int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
int (*reg_write)(void *context, unsigned int reg, unsigned int val); bool fast_io; unsigned int max_register;
const struct regmap_access_table *wr_table;
const struct regmap_access_table *rd_table;
const struct regmap_access_table *volatile_table;
const struct regmap_access_table *precious_table;
const struct regmap_access_table *wr_noinc_table;
const struct regmap_access_table *rd_noinc_table;
const struct reg_default *reg_defaults;
unsigned int num_reg_defaults;
enum regcache_type cache_type;
const void *reg_defaults_raw;
unsigned int num_reg_defaults_raw; unsigned long read_flag_mask;
unsigned long write_flag_mask;
bool zero_flag_mask; bool use_single_read;
bool use_single_write;
bool can_multi_write; enum regmap_endian reg_format_endian;
enum regmap_endian val_format_endian; const struct regmap_range_cfg *ranges;
unsigned int num_ranges; bool use_hwlock;
unsigned int hwlock_id;
unsigned int hwlock_mode; bool can_sleep;
};

struct regmap_config 中,驱动程序除了可以用 writeable_reg 等回调函数来描述设备 IO 寄存器的读写属性外,还可以用 struct regmap_access_table 来描述。如果具有相同读写属性的设备 IO 寄存器是连续的,则 struct regmap_access_table 要方便很多,否则用回调函数比较方便。

Linux 设备驱动程序,可以通过 reg_readreg_write 字段配置设备 IO 寄存器的读写操作。对于 I2C、SPI、I3C、AC97 和 mmio 等标准总线,Linux 内核已经提供相应的设备 IO 寄存器的读写操作。对于比较特别的设备,设备驱动程序可以自行提供读写操作。

设备驱动程序通过 devm_regmap_init_mmio() 创建并初始化 struct regmapdevm_regmap_init_mmio() 定义 (位于 include/linux/regmap.h) 如下:

struct regmap *__devm_regmap_init_mmio_clk(struct device *dev,
const char *clk_id,
void __iomem *regs,
const struct regmap_config *config,
struct lock_class_key *lock_key,
const char *lock_name);
. . . . . .
#ifdef CONFIG_LOCKDEP
#define __regmap_lockdep_wrapper(fn, name, ...) \
( \
({ \
static struct lock_class_key _key; \
fn(__VA_ARGS__, &_key, \
KBUILD_BASENAME ":" \
__stringify(__LINE__) ":" \
"(" name ")->lock"); \
}) \
)
#else
#define __regmap_lockdep_wrapper(fn, name, ...) fn(__VA_ARGS__, NULL, NULL)
#endif
. . . . . .
#define devm_regmap_init_mmio_clk(dev, clk_id, regs, config) \
__regmap_lockdep_wrapper(__devm_regmap_init_mmio_clk, #config, \
dev, clk_id, regs, config) /**
* devm_regmap_init_mmio() - Initialise managed register map
*
* @dev: Device that will be interacted with
* @regs: Pointer to memory-mapped IO region
* @config: Configuration for register map
*
* The return value will be an ERR_PTR() on error or a valid pointer
* to a struct regmap. The regmap will be automatically freed by the
* device management code.
*/
#define devm_regmap_init_mmio(dev, regs, config) \
devm_regmap_init_mmio_clk(dev, NULL, regs, config)

创建并初始化 struct regmap 的工作由 __regmap_init_mmio_clk() 函数执行,这个函数定义 (位于 drivers/base/regmap/regmap-mmio.c) 如下:

struct regmap_mmio_context {
void __iomem *regs;
unsigned val_bytes; bool attached_clk;
struct clk *clk; void (*reg_write)(struct regmap_mmio_context *ctx,
unsigned int reg, unsigned int val);
unsigned int (*reg_read)(struct regmap_mmio_context *ctx,
unsigned int reg);
}; static int regmap_mmio_regbits_check(size_t reg_bits)
{
switch (reg_bits) {
case 8:
case 16:
case 32:
#ifdef CONFIG_64BIT
case 64:
#endif
return 0;
default:
return -EINVAL;
}
} static int regmap_mmio_get_min_stride(size_t val_bits)
{
int min_stride; switch (val_bits) {
case 8:
/* The core treats 0 as 1 */
min_stride = 0;
return 0;
case 16:
min_stride = 2;
break;
case 32:
min_stride = 4;
break;
#ifdef CONFIG_64BIT
case 64:
min_stride = 8;
break;
#endif
default:
return -EINVAL;
} return min_stride;
}
. . . . . .
static void regmap_mmio_write32le(struct regmap_mmio_context *ctx,
unsigned int reg,
unsigned int val)
{
writel(val, ctx->regs + reg);
} static void regmap_mmio_write32be(struct regmap_mmio_context *ctx,
unsigned int reg,
unsigned int val)
{
iowrite32be(val, ctx->regs + reg);
} #ifdef CONFIG_64BIT
static void regmap_mmio_write64le(struct regmap_mmio_context *ctx,
unsigned int reg,
unsigned int val)
{
writeq(val, ctx->regs + reg);
}
#endif
. . . . . .
static unsigned int regmap_mmio_read32le(struct regmap_mmio_context *ctx,
unsigned int reg)
{
return readl(ctx->regs + reg);
} static unsigned int regmap_mmio_read32be(struct regmap_mmio_context *ctx,
unsigned int reg)
{
return ioread32be(ctx->regs + reg);
} #ifdef CONFIG_64BIT
static unsigned int regmap_mmio_read64le(struct regmap_mmio_context *ctx,
unsigned int reg)
{
return readq(ctx->regs + reg);
}
#endif
. . . . . .
static struct regmap_mmio_context *regmap_mmio_gen_context(struct device *dev,
const char *clk_id,
void __iomem *regs,
const struct regmap_config *config)
{
struct regmap_mmio_context *ctx;
int min_stride;
int ret; ret = regmap_mmio_regbits_check(config->reg_bits);
if (ret)
return ERR_PTR(ret); if (config->pad_bits)
return ERR_PTR(-EINVAL); min_stride = regmap_mmio_get_min_stride(config->val_bits);
if (min_stride < 0)
return ERR_PTR(min_stride); if (config->reg_stride < min_stride)
return ERR_PTR(-EINVAL); ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return ERR_PTR(-ENOMEM); ctx->regs = regs;
ctx->val_bytes = config->val_bits / 8;
ctx->clk = ERR_PTR(-ENODEV); switch (regmap_get_val_endian(dev, &regmap_mmio, config)) {
case REGMAP_ENDIAN_DEFAULT:
case REGMAP_ENDIAN_LITTLE:
#ifdef __LITTLE_ENDIAN
case REGMAP_ENDIAN_NATIVE:
#endif
switch (config->val_bits) {
case 8:
ctx->reg_read = regmap_mmio_read8;
ctx->reg_write = regmap_mmio_write8;
break;
case 16:
ctx->reg_read = regmap_mmio_read16le;
ctx->reg_write = regmap_mmio_write16le;
break;
case 32:
ctx->reg_read = regmap_mmio_read32le;
ctx->reg_write = regmap_mmio_write32le;
break;
#ifdef CONFIG_64BIT
case 64:
ctx->reg_read = regmap_mmio_read64le;
ctx->reg_write = regmap_mmio_write64le;
break;
#endif
default:
ret = -EINVAL;
goto err_free;
}
break;
case REGMAP_ENDIAN_BIG:
#ifdef __BIG_ENDIAN
case REGMAP_ENDIAN_NATIVE:
#endif
switch (config->val_bits) {
case 8:
ctx->reg_read = regmap_mmio_read8;
ctx->reg_write = regmap_mmio_write8;
break;
case 16:
ctx->reg_read = regmap_mmio_read16be;
ctx->reg_write = regmap_mmio_write16be;
break;
case 32:
ctx->reg_read = regmap_mmio_read32be;
ctx->reg_write = regmap_mmio_write32be;
break;
default:
ret = -EINVAL;
goto err_free;
}
break;
default:
ret = -EINVAL;
goto err_free;
} if (clk_id == NULL)
return ctx; ctx->clk = clk_get(dev, clk_id);
if (IS_ERR(ctx->clk)) {
ret = PTR_ERR(ctx->clk);
goto err_free;
} ret = clk_prepare(ctx->clk);
if (ret < 0) {
clk_put(ctx->clk);
goto err_free;
} return ctx; err_free:
kfree(ctx); return ERR_PTR(ret);
}
. . . . . .
struct regmap *__devm_regmap_init_mmio_clk(struct device *dev,
const char *clk_id,
void __iomem *regs,
const struct regmap_config *config,
struct lock_class_key *lock_key,
const char *lock_name)
{
struct regmap_mmio_context *ctx; ctx = regmap_mmio_gen_context(dev, clk_id, regs, config);
if (IS_ERR(ctx))
return ERR_CAST(ctx); return __devm_regmap_init(dev, &regmap_mmio, ctx, config,
lock_key, lock_name);
}
EXPORT_SYMBOL_GPL(__devm_regmap_init_mmio_clk);

最终读写设备 IO 寄存器的操作由 context 提供,如这里的 struct regmap_mmio_context。也可以将 context 看作是 struct regmap_bus 的实现。

__regmap_init_mmio_clk() 函数通过 regmap_mmio_gen_context() 函数分配并初始化 context,并调用 __devm_regmap_init() 函数分配并初始化 struct regmap

regmap_mmio_gen_context() 函数检查寄存器映射配置,并根据配置的寄存器值的位宽,及尾端是大尾端还是小尾端,选择适当的读写操作函数。大尾端不支持 64 位的寄存器读写。最终的读写操作,由各个硬件平台特有的 IO 操作完成。

struct regmap_bus 表示寄存器映射总线,这个结构体的定义 (位于 include/linux/regmap.h) 如下:

struct regmap_async;

typedef int (*regmap_hw_write)(void *context, const void *data,
size_t count);
typedef int (*regmap_hw_gather_write)(void *context,
const void *reg, size_t reg_len,
const void *val, size_t val_len);
typedef int (*regmap_hw_async_write)(void *context,
const void *reg, size_t reg_len,
const void *val, size_t val_len,
struct regmap_async *async);
typedef int (*regmap_hw_read)(void *context,
const void *reg_buf, size_t reg_size,
void *val_buf, size_t val_size);
typedef int (*regmap_hw_reg_read)(void *context, unsigned int reg,
unsigned int *val);
typedef int (*regmap_hw_reg_write)(void *context, unsigned int reg,
unsigned int val);
typedef int (*regmap_hw_reg_update_bits)(void *context, unsigned int reg,
unsigned int mask, unsigned int val);
typedef struct regmap_async *(*regmap_hw_async_alloc)(void);
typedef void (*regmap_hw_free_context)(void *context);
. . . . . .
struct regmap_bus {
bool fast_io;
regmap_hw_write write;
regmap_hw_gather_write gather_write;
regmap_hw_async_write async_write;
regmap_hw_reg_write reg_write;
regmap_hw_reg_update_bits reg_update_bits;
regmap_hw_read read;
regmap_hw_reg_read reg_read;
regmap_hw_free_context free_context;
regmap_hw_async_alloc async_alloc;
u8 read_flag_mask;
enum regmap_endian reg_format_endian_default;
enum regmap_endian val_format_endian_default;
size_t max_raw_read;
size_t max_raw_write;
};

struct regmap_bus 基本上是各种寄存器 IO 操作的集合。对于 mmio,它的 struct regmap_bus 定义 (位于 drivers/base/regmap/regmap-mmio.c) 如下:

static int regmap_mmio_write(void *context, unsigned int reg, unsigned int val)
{
struct regmap_mmio_context *ctx = context;
int ret; if (!IS_ERR(ctx->clk)) {
ret = clk_enable(ctx->clk);
if (ret < 0)
return ret;
} ctx->reg_write(ctx, reg, val); if (!IS_ERR(ctx->clk))
clk_disable(ctx->clk); return 0;
}
. . . . . .
static int regmap_mmio_read(void *context, unsigned int reg, unsigned int *val)
{
struct regmap_mmio_context *ctx = context;
int ret; if (!IS_ERR(ctx->clk)) {
ret = clk_enable(ctx->clk);
if (ret < 0)
return ret;
} *val = ctx->reg_read(ctx, reg); if (!IS_ERR(ctx->clk))
clk_disable(ctx->clk); return 0;
} static void regmap_mmio_free_context(void *context)
{
struct regmap_mmio_context *ctx = context; if (!IS_ERR(ctx->clk)) {
clk_unprepare(ctx->clk);
if (!ctx->attached_clk)
clk_put(ctx->clk);
}
kfree(context);
} static const struct regmap_bus regmap_mmio = {
.fast_io = true,
.reg_write = regmap_mmio_write,
.reg_read = regmap_mmio_read,
.free_context = regmap_mmio_free_context,
.val_format_endian_default = REGMAP_ENDIAN_LITTLE,
};

regmap 机制中,struct regmap_bus 的角色定位即是完成对硬件设备 IO 寄存器的直接访问。对于 mmio,struct regmap_bus 是通向 context struct regmap_mmio_context 的桥梁。

__devm_regmap_init() 函数定义 (位于 drivers/base/regmap/regmap.c) 如下:

int regmap_attach_dev(struct device *dev, struct regmap *map,
const struct regmap_config *config)
{
struct regmap **m;
int ret; map->dev = dev; ret = regmap_set_name(map, config);
if (ret)
return ret; regmap_debugfs_exit(map);
regmap_debugfs_init(map); /* Add a devres resource for dev_get_regmap() */
m = devres_alloc(dev_get_regmap_release, sizeof(*m), GFP_KERNEL);
if (!m) {
regmap_debugfs_exit(map);
return -ENOMEM;
}
*m = map;
devres_add(dev, m); return 0;
}
EXPORT_SYMBOL_GPL(regmap_attach_dev);
. . . . . .
struct regmap *__regmap_init(struct device *dev,
const struct regmap_bus *bus,
void *bus_context,
const struct regmap_config *config,
struct lock_class_key *lock_key,
const char *lock_name)
{
struct regmap *map;
int ret = -EINVAL;
enum regmap_endian reg_endian, val_endian;
int i, j; if (!config)
goto err; map = kzalloc(sizeof(*map), GFP_KERNEL);
if (map == NULL) {
ret = -ENOMEM;
goto err;
} ret = regmap_set_name(map, config);
if (ret)
goto err_map; ret = -EINVAL; /* Later error paths rely on this */ if (config->disable_locking) {
map->lock = map->unlock = regmap_lock_unlock_none;
map->can_sleep = config->can_sleep;
regmap_debugfs_disable(map);
} else if (config->lock && config->unlock) {
map->lock = config->lock;
map->unlock = config->unlock;
map->lock_arg = config->lock_arg;
map->can_sleep = config->can_sleep;
} else if (config->use_hwlock) {
map->hwlock = hwspin_lock_request_specific(config->hwlock_id);
if (!map->hwlock) {
ret = -ENXIO;
goto err_name;
} switch (config->hwlock_mode) {
case HWLOCK_IRQSTATE:
map->lock = regmap_lock_hwlock_irqsave;
map->unlock = regmap_unlock_hwlock_irqrestore;
break;
case HWLOCK_IRQ:
map->lock = regmap_lock_hwlock_irq;
map->unlock = regmap_unlock_hwlock_irq;
break;
default:
map->lock = regmap_lock_hwlock;
map->unlock = regmap_unlock_hwlock;
break;
} map->lock_arg = map;
} else {
if ((bus && bus->fast_io) ||
config->fast_io) {
spin_lock_init(&map->spinlock);
map->lock = regmap_lock_spinlock;
map->unlock = regmap_unlock_spinlock;
lockdep_set_class_and_name(&map->spinlock,
lock_key, lock_name);
} else {
mutex_init(&map->mutex);
map->lock = regmap_lock_mutex;
map->unlock = regmap_unlock_mutex;
map->can_sleep = true;
lockdep_set_class_and_name(&map->mutex,
lock_key, lock_name);
}
map->lock_arg = map;
} /*
* When we write in fast-paths with regmap_bulk_write() don't allocate
* scratch buffers with sleeping allocations.
*/
if ((bus && bus->fast_io) || config->fast_io)
map->alloc_flags = GFP_ATOMIC;
else
map->alloc_flags = GFP_KERNEL; map->format.reg_bytes = DIV_ROUND_UP(config->reg_bits, 8);
map->format.pad_bytes = config->pad_bits / 8;
map->format.val_bytes = DIV_ROUND_UP(config->val_bits, 8);
map->format.buf_size = DIV_ROUND_UP(config->reg_bits +
config->val_bits + config->pad_bits, 8);
map->reg_shift = config->pad_bits % 8;
if (config->reg_stride)
map->reg_stride = config->reg_stride;
else
map->reg_stride = 1;
if (is_power_of_2(map->reg_stride))
map->reg_stride_order = ilog2(map->reg_stride);
else
map->reg_stride_order = -1;
map->use_single_read = config->use_single_read || !bus || !bus->read;
map->use_single_write = config->use_single_write || !bus || !bus->write;
map->can_multi_write = config->can_multi_write && bus && bus->write;
if (bus) {
map->max_raw_read = bus->max_raw_read;
map->max_raw_write = bus->max_raw_write;
}
map->dev = dev;
map->bus = bus;
map->bus_context = bus_context;
map->max_register = config->max_register;
map->wr_table = config->wr_table;
map->rd_table = config->rd_table;
map->volatile_table = config->volatile_table;
map->precious_table = config->precious_table;
map->wr_noinc_table = config->wr_noinc_table;
map->rd_noinc_table = config->rd_noinc_table;
map->writeable_reg = config->writeable_reg;
map->readable_reg = config->readable_reg;
map->volatile_reg = config->volatile_reg;
map->precious_reg = config->precious_reg;
map->writeable_noinc_reg = config->writeable_noinc_reg;
map->readable_noinc_reg = config->readable_noinc_reg;
map->cache_type = config->cache_type; spin_lock_init(&map->async_lock);
INIT_LIST_HEAD(&map->async_list);
INIT_LIST_HEAD(&map->async_free);
init_waitqueue_head(&map->async_waitq); if (config->read_flag_mask ||
config->write_flag_mask ||
config->zero_flag_mask) {
map->read_flag_mask = config->read_flag_mask;
map->write_flag_mask = config->write_flag_mask;
} else if (bus) {
map->read_flag_mask = bus->read_flag_mask;
} if (!bus) {
map->reg_read = config->reg_read;
map->reg_write = config->reg_write; map->defer_caching = false;
goto skip_format_initialization;
} else if (!bus->read || !bus->write) {
map->reg_read = _regmap_bus_reg_read;
map->reg_write = _regmap_bus_reg_write;
map->reg_update_bits = bus->reg_update_bits; map->defer_caching = false;
goto skip_format_initialization;
} else {
map->reg_read = _regmap_bus_read;
map->reg_update_bits = bus->reg_update_bits;
} reg_endian = regmap_get_reg_endian(bus, config);
val_endian = regmap_get_val_endian(dev, bus, config); switch (config->reg_bits + map->reg_shift) {
case 2:
switch (config->val_bits) {
case 6:
map->format.format_write = regmap_format_2_6_write;
break;
default:
goto err_hwlock;
}
break; case 4:
switch (config->val_bits) {
case 12:
map->format.format_write = regmap_format_4_12_write;
break;
default:
goto err_hwlock;
}
break; case 7:
switch (config->val_bits) {
case 9:
map->format.format_write = regmap_format_7_9_write;
break;
default:
goto err_hwlock;
}
break; case 10:
switch (config->val_bits) {
case 14:
map->format.format_write = regmap_format_10_14_write;
break;
default:
goto err_hwlock;
}
break; case 12:
switch (config->val_bits) {
case 20:
map->format.format_write = regmap_format_12_20_write;
break;
default:
goto err_hwlock;
}
break; case 8:
map->format.format_reg = regmap_format_8;
break; case 16:
switch (reg_endian) {
case REGMAP_ENDIAN_BIG:
map->format.format_reg = regmap_format_16_be;
break;
case REGMAP_ENDIAN_LITTLE:
map->format.format_reg = regmap_format_16_le;
break;
case REGMAP_ENDIAN_NATIVE:
map->format.format_reg = regmap_format_16_native;
break;
default:
goto err_hwlock;
}
break; case 24:
if (reg_endian != REGMAP_ENDIAN_BIG)
goto err_hwlock;
map->format.format_reg = regmap_format_24;
break; case 32:
switch (reg_endian) {
case REGMAP_ENDIAN_BIG:
map->format.format_reg = regmap_format_32_be;
break;
case REGMAP_ENDIAN_LITTLE:
map->format.format_reg = regmap_format_32_le;
break;
case REGMAP_ENDIAN_NATIVE:
map->format.format_reg = regmap_format_32_native;
break;
default:
goto err_hwlock;
}
break; #ifdef CONFIG_64BIT
case 64:
switch (reg_endian) {
case REGMAP_ENDIAN_BIG:
map->format.format_reg = regmap_format_64_be;
break;
case REGMAP_ENDIAN_LITTLE:
map->format.format_reg = regmap_format_64_le;
break;
case REGMAP_ENDIAN_NATIVE:
map->format.format_reg = regmap_format_64_native;
break;
default:
goto err_hwlock;
}
break;
#endif default:
goto err_hwlock;
} if (val_endian == REGMAP_ENDIAN_NATIVE)
map->format.parse_inplace = regmap_parse_inplace_noop; switch (config->val_bits) {
case 8:
map->format.format_val = regmap_format_8;
map->format.parse_val = regmap_parse_8;
map->format.parse_inplace = regmap_parse_inplace_noop;
break;
case 16:
switch (val_endian) {
case REGMAP_ENDIAN_BIG:
map->format.format_val = regmap_format_16_be;
map->format.parse_val = regmap_parse_16_be;
map->format.parse_inplace = regmap_parse_16_be_inplace;
break;
case REGMAP_ENDIAN_LITTLE:
map->format.format_val = regmap_format_16_le;
map->format.parse_val = regmap_parse_16_le;
map->format.parse_inplace = regmap_parse_16_le_inplace;
break;
case REGMAP_ENDIAN_NATIVE:
map->format.format_val = regmap_format_16_native;
map->format.parse_val = regmap_parse_16_native;
break;
default:
goto err_hwlock;
}
break;
case 24:
if (val_endian != REGMAP_ENDIAN_BIG)
goto err_hwlock;
map->format.format_val = regmap_format_24;
map->format.parse_val = regmap_parse_24;
break;
case 32:
switch (val_endian) {
case REGMAP_ENDIAN_BIG:
map->format.format_val = regmap_format_32_be;
map->format.parse_val = regmap_parse_32_be;
map->format.parse_inplace = regmap_parse_32_be_inplace;
break;
case REGMAP_ENDIAN_LITTLE:
map->format.format_val = regmap_format_32_le;
map->format.parse_val = regmap_parse_32_le;
map->format.parse_inplace = regmap_parse_32_le_inplace;
break;
case REGMAP_ENDIAN_NATIVE:
map->format.format_val = regmap_format_32_native;
map->format.parse_val = regmap_parse_32_native;
break;
default:
goto err_hwlock;
}
break;
#ifdef CONFIG_64BIT
case 64:
switch (val_endian) {
case REGMAP_ENDIAN_BIG:
map->format.format_val = regmap_format_64_be;
map->format.parse_val = regmap_parse_64_be;
map->format.parse_inplace = regmap_parse_64_be_inplace;
break;
case REGMAP_ENDIAN_LITTLE:
map->format.format_val = regmap_format_64_le;
map->format.parse_val = regmap_parse_64_le;
map->format.parse_inplace = regmap_parse_64_le_inplace;
break;
case REGMAP_ENDIAN_NATIVE:
map->format.format_val = regmap_format_64_native;
map->format.parse_val = regmap_parse_64_native;
break;
default:
goto err_hwlock;
}
break;
#endif
} if (map->format.format_write) {
if ((reg_endian != REGMAP_ENDIAN_BIG) ||
(val_endian != REGMAP_ENDIAN_BIG))
goto err_hwlock;
map->use_single_write = true;
} if (!map->format.format_write &&
!(map->format.format_reg && map->format.format_val))
goto err_hwlock; map->work_buf = kzalloc(map->format.buf_size, GFP_KERNEL);
if (map->work_buf == NULL) {
ret = -ENOMEM;
goto err_hwlock;
} if (map->format.format_write) {
map->defer_caching = false;
map->reg_write = _regmap_bus_formatted_write;
} else if (map->format.format_val) {
map->defer_caching = true;
map->reg_write = _regmap_bus_raw_write;
} skip_format_initialization: map->range_tree = RB_ROOT;
for (i = 0; i < config->num_ranges; i++) {
const struct regmap_range_cfg *range_cfg = &config->ranges[i];
struct regmap_range_node *new; /* Sanity check */
if (range_cfg->range_max < range_cfg->range_min) {
dev_err(map->dev, "Invalid range %d: %d < %d\n", i,
range_cfg->range_max, range_cfg->range_min);
goto err_range;
} if (range_cfg->range_max > map->max_register) {
dev_err(map->dev, "Invalid range %d: %d > %d\n", i,
range_cfg->range_max, map->max_register);
goto err_range;
} if (range_cfg->selector_reg > map->max_register) {
dev_err(map->dev,
"Invalid range %d: selector out of map\n", i);
goto err_range;
} if (range_cfg->window_len == 0) {
dev_err(map->dev, "Invalid range %d: window_len 0\n",
i);
goto err_range;
} /* Make sure, that this register range has no selector
or data window within its boundary */
for (j = 0; j < config->num_ranges; j++) {
unsigned sel_reg = config->ranges[j].selector_reg;
unsigned win_min = config->ranges[j].window_start;
unsigned win_max = win_min +
config->ranges[j].window_len - 1; /* Allow data window inside its own virtual range */
if (j == i)
continue; if (range_cfg->range_min <= sel_reg &&
sel_reg <= range_cfg->range_max) {
dev_err(map->dev,
"Range %d: selector for %d in window\n",
i, j);
goto err_range;
} if (!(win_max < range_cfg->range_min ||
win_min > range_cfg->range_max)) {
dev_err(map->dev,
"Range %d: window for %d in window\n",
i, j);
goto err_range;
}
} new = kzalloc(sizeof(*new), GFP_KERNEL);
if (new == NULL) {
ret = -ENOMEM;
goto err_range;
} new->map = map;
new->name = range_cfg->name;
new->range_min = range_cfg->range_min;
new->range_max = range_cfg->range_max;
new->selector_reg = range_cfg->selector_reg;
new->selector_mask = range_cfg->selector_mask;
new->selector_shift = range_cfg->selector_shift;
new->window_start = range_cfg->window_start;
new->window_len = range_cfg->window_len; if (!_regmap_range_add(map, new)) {
dev_err(map->dev, "Failed to add range %d\n", i);
kfree(new);
goto err_range;
} if (map->selector_work_buf == NULL) {
map->selector_work_buf =
kzalloc(map->format.buf_size, GFP_KERNEL);
if (map->selector_work_buf == NULL) {
ret = -ENOMEM;
goto err_range;
}
}
} ret = regcache_init(map, config);
if (ret != 0)
goto err_range; if (dev) {
ret = regmap_attach_dev(dev, map, config);
if (ret != 0)
goto err_regcache;
} else {
regmap_debugfs_init(map);
} return map; err_regcache:
regcache_exit(map);
err_range:
regmap_range_exit(map);
kfree(map->work_buf);
err_hwlock:
if (map->hwlock)
hwspin_lock_free(map->hwlock);
err_name:
kfree_const(map->name);
err_map:
kfree(map);
err:
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(__regmap_init); static void devm_regmap_release(struct device *dev, void *res)
{
regmap_exit(*(struct regmap **)res);
} struct regmap *__devm_regmap_init(struct device *dev,
const struct regmap_bus *bus,
void *bus_context,
const struct regmap_config *config,
struct lock_class_key *lock_key,
const char *lock_name)
{
struct regmap **ptr, *regmap; ptr = devres_alloc(devm_regmap_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM); regmap = __regmap_init(dev, bus, bus_context, config,
lock_key, lock_name);
if (!IS_ERR(regmap)) {
*ptr = regmap;
devres_add(dev, ptr);
} else {
devres_free(ptr);
} return regmap;
}
EXPORT_SYMBOL_GPL(__devm_regmap_init);

__devm_regmap_init() 函数通过 __regmap_init() 函数创建并初始化 struct regmap 对象,并创建 devres 来维护 struct regmap 对象的生命周期,使得它可以随着 struct device 对象的生命周期的结束而结束。

__regmap_init() 函数比较长,但它做的事情比较清晰:

  1. 分配 struct regmap 对象。
  2. 根据寄存器映射配置选择 lock/unlock 操作。
  3. 根据传入的寄存器映射配置、regmap_busbus_context 等初始化 struct regmap 对象。
  4. 根据传入的寄存器映射配置和 regmap_bus 等选择设备 IO 寄存器的读写操作。
  5. 根据传入的寄存器映射配置选择格式化的寄存器操作。
  6. 处理寄存器映射范围。
  7. 初始化寄存器映射缓存。
  8. 连接 dev,这包括初始化 debugfs 等。

regcache_init() 函数初始化寄存器映射缓存,它的定义 (位于 drivers/base/regmap/regcache.c) 如下:

static const struct regcache_ops *cache_types[] = {
&regcache_rbtree_ops,
#if IS_ENABLED(CONFIG_REGCACHE_COMPRESSED)
&regcache_lzo_ops,
#endif
&regcache_flat_ops,
};
. . . . . .
int regcache_init(struct regmap *map, const struct regmap_config *config)
{
int ret;
int i;
void *tmp_buf; if (map->cache_type == REGCACHE_NONE) {
if (config->reg_defaults || config->num_reg_defaults_raw)
dev_warn(map->dev,
"No cache used with register defaults set!\n"); map->cache_bypass = true;
return 0;
} if (config->reg_defaults && !config->num_reg_defaults) {
dev_err(map->dev,
"Register defaults are set without the number!\n");
return -EINVAL;
} for (i = 0; i < config->num_reg_defaults; i++)
if (config->reg_defaults[i].reg % map->reg_stride)
return -EINVAL; for (i = 0; i < ARRAY_SIZE(cache_types); i++)
if (cache_types[i]->type == map->cache_type)
break; if (i == ARRAY_SIZE(cache_types)) {
dev_err(map->dev, "Could not match compress type: %d\n",
map->cache_type);
return -EINVAL;
} map->num_reg_defaults = config->num_reg_defaults;
map->num_reg_defaults_raw = config->num_reg_defaults_raw;
map->reg_defaults_raw = config->reg_defaults_raw;
map->cache_word_size = DIV_ROUND_UP(config->val_bits, 8);
map->cache_size_raw = map->cache_word_size * config->num_reg_defaults_raw; map->cache = NULL;
map->cache_ops = cache_types[i]; if (!map->cache_ops->read ||
!map->cache_ops->write ||
!map->cache_ops->name)
return -EINVAL; /* We still need to ensure that the reg_defaults
* won't vanish from under us. We'll need to make
* a copy of it.
*/
if (config->reg_defaults) {
tmp_buf = kmemdup(config->reg_defaults, map->num_reg_defaults *
sizeof(struct reg_default), GFP_KERNEL);
if (!tmp_buf)
return -ENOMEM;
map->reg_defaults = tmp_buf;
} else if (map->num_reg_defaults_raw) {
/* Some devices such as PMICs don't have cache defaults,
* we cope with this by reading back the HW registers and
* crafting the cache defaults by hand.
*/
ret = regcache_hw_init(map);
if (ret < 0)
return ret;
if (map->cache_bypass)
return 0;
} if (!map->max_register)
map->max_register = map->num_reg_defaults_raw; if (map->cache_ops->init) {
dev_dbg(map->dev, "Initializing %s cache\n",
map->cache_ops->name);
ret = map->cache_ops->init(map);
if (ret)
goto err_free;
}
return 0; err_free:
kfree(map->reg_defaults);
if (map->cache_free)
kfree(map->reg_defaults_raw); return ret;
}

这个函数根据传入的配置的缓存类型,选择适当的 struct regcache_ops,并初始化缓存。struct regcache_ops 定义 (位于 drivers/base/regmap/internal.h) 如下:

struct regcache_ops {
const char *name;
enum regcache_type type;
int (*init)(struct regmap *map);
int (*exit)(struct regmap *map);
#ifdef CONFIG_DEBUG_FS
void (*debugfs_init)(struct regmap *map);
#endif
int (*read)(struct regmap *map, unsigned int reg, unsigned int *value);
int (*write)(struct regmap *map, unsigned int reg, unsigned int value);
int (*sync)(struct regmap *map, unsigned int min, unsigned int max);
int (*drop)(struct regmap *map, unsigned int min, unsigned int max);
};

struct regmap 的定义 (位于 drivers/base/regmap/internal.h) 如下:

struct regmap {
union {
struct mutex mutex;
struct {
spinlock_t spinlock;
unsigned long spinlock_flags;
};
};
regmap_lock lock;
regmap_unlock unlock;
void *lock_arg; /* This is passed to lock/unlock functions */
gfp_t alloc_flags; struct device *dev; /* Device we do I/O on */
void *work_buf; /* Scratch buffer used to format I/O */
struct regmap_format format; /* Buffer format */
const struct regmap_bus *bus;
void *bus_context;
const char *name; bool async;
spinlock_t async_lock;
wait_queue_head_t async_waitq;
struct list_head async_list;
struct list_head async_free;
int async_ret; #ifdef CONFIG_DEBUG_FS
bool debugfs_disable;
struct dentry *debugfs;
const char *debugfs_name; unsigned int debugfs_reg_len;
unsigned int debugfs_val_len;
unsigned int debugfs_tot_len; struct list_head debugfs_off_cache;
struct mutex cache_lock;
#endif unsigned int max_register;
bool (*writeable_reg)(struct device *dev, unsigned int reg);
bool (*readable_reg)(struct device *dev, unsigned int reg);
bool (*volatile_reg)(struct device *dev, unsigned int reg);
bool (*precious_reg)(struct device *dev, unsigned int reg);
bool (*writeable_noinc_reg)(struct device *dev, unsigned int reg);
bool (*readable_noinc_reg)(struct device *dev, unsigned int reg);
const struct regmap_access_table *wr_table;
const struct regmap_access_table *rd_table;
const struct regmap_access_table *volatile_table;
const struct regmap_access_table *precious_table;
const struct regmap_access_table *wr_noinc_table;
const struct regmap_access_table *rd_noinc_table; int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
int (*reg_write)(void *context, unsigned int reg, unsigned int val);
int (*reg_update_bits)(void *context, unsigned int reg,
unsigned int mask, unsigned int val); bool defer_caching; unsigned long read_flag_mask;
unsigned long write_flag_mask; /* number of bits to (left) shift the reg value when formatting*/
int reg_shift;
int reg_stride;
int reg_stride_order; /* regcache specific members */
const struct regcache_ops *cache_ops;
enum regcache_type cache_type; /* number of bytes in reg_defaults_raw */
unsigned int cache_size_raw;
/* number of bytes per word in reg_defaults_raw */
unsigned int cache_word_size;
/* number of entries in reg_defaults */
unsigned int num_reg_defaults;
/* number of entries in reg_defaults_raw */
unsigned int num_reg_defaults_raw; /* if set, only the cache is modified not the HW */
bool cache_only;
/* if set, only the HW is modified not the cache */
bool cache_bypass;
/* if set, remember to free reg_defaults_raw */
bool cache_free; struct reg_default *reg_defaults;
const void *reg_defaults_raw;
void *cache;
/* if set, the cache contains newer data than the HW */
bool cache_dirty;
/* if set, the HW registers are known to match map->reg_defaults */
bool no_sync_defaults; struct reg_sequence *patch;
int patch_regs; /* if set, converts bulk read to single read */
bool use_single_read;
/* if set, converts bulk write to single write */
bool use_single_write;
/* if set, the device supports multi write mode */
bool can_multi_write; /* if set, raw reads/writes are limited to this size */
size_t max_raw_read;
size_t max_raw_write; struct rb_root range_tree;
void *selector_work_buf; /* Scratch buffer used for selector */ struct hwspinlock *hwlock; /* if set, the regmap core can sleep */
bool can_sleep;
};

struct regmapstruct regmap_configstruct regmap_bus 和 cached 三者的结合,它通过 struct regmap_config 连接具体设备的寄存器的信息,通过 struct regmap_bus 连接对具体设备的 IO 寄存器的读写操作,通过 cached 连接缓存。

Done.

Linux 内核设备驱动程序的IO寄存器访问 (上)的更多相关文章

  1. 嵌入式Linux设备驱动程序:编写内核设备驱动程序

    嵌入式Linux设备驱动程序:编写内核设备驱动程序 Embedded Linux device drivers: Writing a kernel device driver 编写内核设备驱动程序 最 ...

  2. (linux)块设备驱动程序

      1.4.1  Linux块设备驱动程序原理(1) 顾名思义,块设备驱动程序就是支持以块的方式进行读写的设备.块设备和字符设备最大的区别在于读写数据的基本单元不同.块设备读写数据的基本单元为块,例如 ...

  3. 浅析Linux字符设备驱动程序内核机制

    前段时间在学习linux设备驱动的时候,看了陈学松著的<深入Linux设备驱动程序内核机制>一书. 说实话.这是一本非常好的书,作者不但给出了在设备驱动程序开发过程中的所须要的知识点(如对 ...

  4. IO调度 | Linux块设备中的IO路径及调度策略

    当文件系统通过submit_bio提交IO之后,请求就进入了通用块层.通用块层会对IO进行一些预处理的动作,其目的是为了保证请求能够更加合理的发送到底层的磁盘设备,尽量保证性能最佳.这里面比较重要的就 ...

  5. ARM Linux字符设备驱动程序

    1.主设备号和次设备号(二者一起为设备号): 一个字符设备或块设备都有一个主设备号和一个次设备号.主设备号用来标识与设备文件相连的驱动程序,用来反  映设备类型.次设备号被驱动程序用来辨别操作的是哪个 ...

  6. linux内核编程入门--系统调用监控文件访问

    参考的资料: hello world   https://www.cnblogs.com/bitor/p/9608725.html linux内核监控模块--系统调用的截获  https://www. ...

  7. 简单linux块设备驱动程序

    本文代码参考<LINUX设备驱动程序>第十六章 块设备驱动程序 本文中的“块设备”是一段大小为PAGE_SIZE的内存空间(两个扇区,每个扇区512字节) 功能:向块设备中输入内容,从块设 ...

  8. 一个简单的演示用的Linux字符设备驱动程序

    实现如下的功能:--字符设备驱动程序的结构及驱动程序需要实现的系统调用--可以使用cat命令或者自编的readtest命令读出"设备"里的内容--以8139网卡为例,演示了I/O端 ...

  9. 简单linux字符设备驱动程序

    本文代码参考<LINUX设备驱动程序>第三章 字符设备驱动程序 本文中的“字符设备”是一段大小为PAGE_SIZE的内存空间 功能:向字符设备写入字符串:从字符设备读出字符串 代码: 1. ...

  10. 深入Linux内核架构——进程管理和调度(上)

    如果系统只有一个处理器,那么给定时刻只有一个程序可以运行.在多处理器系统中,真正并行运行的进程数目取决于物理CPU的数目.内核和处理器建立了多任务的错觉,是通过以很短的间隔在系统运行的应用程序之间不停 ...

随机推荐

  1. values() 字典形式显示查询结果

    values() 字典形式显示查询结果 name,age为数据库的两个列 Student.objects.values('name','age')

  2. L3-017 森森快递

    一.题目: 7-2 森森快递 (30 分) 森森开了一家快递公司,叫森森快递.因为公司刚刚开张,所以业务路线很简单,可以认为是一条直线上的N个城市,这些城市从左到右依次从0到(N−1)编号.由于道路限 ...

  3. 2023-05-23:如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等, 那么称 X 和 Y 两个字符串相似。如果这两个字符串本身是相等的,那它们也是相似的。 例如,“tars“

    2023-05-23:如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等, 那么称 X 和 Y 两个字符串相似.如果这两个字符串本身是相等的,那它们也是相似的. 例如,"t ...

  4. 什么是 Spring?为什么学它?

    前言 欢迎来到本篇文章!在这里,我将带领大家快速学习 Spring 的基本概念,并解答两个关键问题:什么是 Spring,以及为什么学习 Spring. 废话少说,下面,我们开始吧! Spring 官 ...

  5. Galaxy 生信平台(四):邮件与管理员配置

    前几天看到中山大学和国家基因库合作开发的 Translatome Workbench 翻译组学可视化在线数据分析平台 (db.cngb.org/galaxy/) 的推送信息,也上去看了一下,工具和教程 ...

  6. 解决github无法打开问题

    在国内访问国外服务器(如github)会有卡顿.无法加载等问题,提供两种解决方案: 1.查看github的IP地址并修改Hosts windows键+R,打开cmd(或windows键+X,打开Win ...

  7. 2. IOC

    ‍ 对于 IOC 的理解 : 在 Spring 框架中,IOC(Inversion of Control,控制反转)是一个重要的概念,它是框架实现松耦合的一种方式.在传统的程序设计中,应用程序会主动创 ...

  8. 【SpringBoot】WebSocket在线聊天

    先看一下页面效果,有点简单粗暴!哈哈哈哈哈,别介意. 本文参考:SpringBoot2.0集成WebSocket,实现后台向前端推送信息 新建一个springboot项目 引入相关依赖 <dep ...

  9. ubuntu发行版内核源码下载

    Ubuntu 发行版linux内核在哪里? 内核安装包:http://archive.ubuntu.com/ubuntu/pool/main/l/linux/ 内核源码:https://git.lau ...

  10. Go 语言 for-range 的两个坑,你踩过吗?

    坑一:迭代时协程引用索引和值 先看看下面的例子,你知道最终输出的结果是什么吗? package main import ( "fmt" "time" ) fun ...