1. 簡介 Regmap 機制是在 Linux 3.1 加入進來的特性。主要目的是減少慢速 I/O 驅動上的重覆邏輯,提供一種通用的介面來操作底層硬體上的寄存器。其實這就是內核做的一次重構。Regmap 除了能做到統一的 I/O 介面,還可以在驅動和硬體 IC 之間做一層緩存,從而能減少底層 I/O ...
1. 簡介
Regmap 機制是在 Linux 3.1 加入進來的特性。主要目的是減少慢速 I/O 驅動上的重覆邏輯,提供一種通用的介面來操作底層硬體上的寄存器。其實這就是內核做的一次重構。Regmap 除了能做到統一的 I/O 介面,還可以在驅動和硬體 IC 之間做一層緩存,從而能減少底層 I/O 的操作次數。
2. 使用對比
在瞭解 Regmap 的實現細節前,我們先來對比一下,傳統操作寄存器的方式,與 Regmap 之間的差異。
2.1 傳統方式
我們以一個 I2C 設備為例。讀寫一個寄存器,肯定需要用到 i2c_transfer
這樣的 I2C 函數。為了方便,一般的驅動中,會在這之上再寫一個 Wrapper,然後通過調用這個 Wrapper 來讀寫寄存器。比如如下這個讀取寄存器的函數:
static int xxx_i2c_read_reg(struct i2c_client *client, u8 reg, u8 *val)
{
struct i2c_msg msg[] = {
{
.addr = client->addr,
.flags = 0,
.len = 1,
.buf = ®,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = 1,
.buf = val,
},
};
return i2c_transfer(client->adapter, msg, 2);
}
2.2 Regmap方式
如果 regmap 的方式來實現,對於上面這種讀寄存器操作,其實現如下:
// first step: define regmap_config
static const struct regmap_config xxx_regmap_config = {
.reg_bits = 10,
.val_bits = 14,
.max_register = 40,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = false,
.readable_reg = false,
};
// second step: initialize regmap in driver loading
regmap = regmap_init_i2c(i2c_client, &xxx_regmap_config);
// third step: register operations
regmap_read(regmap, XXX_REG, &value);
代碼中,做的第一步就是定義 IC 的一些寄存器信息。比如:位寬,地址位寬,寄存器總數等。然後在驅動載入的時候,初始化 Regmap,這樣就可以正常調用 Regmap 的 API 了。
可以看到,為了讓慢速 I/O 能夠專註於自身的邏輯,內核把 SPI, I2C 等匯流排操作方式全部封裝在 Regmap 里,這樣驅動若要做 I/O 操作,直接調用 Regmap 的函數就可以了。
3. 實現細節
整個 Regmap 是分為 3 層,其拓撲結構如下:
這裡通過其中 3 個核心結構體來分別說明。
3.1 regmap_config
struct regmap_config
構體代表一個設備的寄存器配置信息,在做 Regmap 初始化時,驅動就需要把這個結構體傳給 Regmap。這個結構體的定義在 include/linux/regmap.h
,其中包含該設備的寄存器數量,寄存器位寬,緩存類型,讀寫屬性等。
這一層是直接和驅動對接的。Regmap 根據傳進來的 regmap_config
初始化對應的緩存和匯流排操作介面,驅動就可以正常調用 regmap_write
和 regmap_read
函數。
3.2 regmap_ops
struct regmap_ops
是用來定義一個緩存類型的,具體定義如下:
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);
};
在最新 Linux 4.0 版本中,已經有 3 種緩存類型,分別是數組(flat)、LZO 壓縮和紅黑樹(rbtree)。數組好理解,是最簡單的緩存類型,當設備寄存器很少時,可以用這種類型來緩存寄存器值。LZO(Lempel–Ziv–Oberhumer) 是 Linux 中經常用到的一種壓縮演算法,Linux 編譯後就會用這個演算法來壓縮。這個演算法有 3 個特性:壓縮快,解壓不需要額外記憶體,壓縮比可以自動調節。在這裡,你可以理解為一個數組緩存,套了一層壓縮,來節約記憶體。當設備寄存器數量中等時,可以考慮這種緩存類型。而最後一類紅黑樹,它的特性就是索引快,所以當設備寄存器數量比較大,或者對寄存器操作延時要求低時,就可以用這種緩存類型。
緩存的類型是在 Regmap 初始化時,由.cache_type = REGCACHE_RBTREE
來指定的。對於regmap_read
獲取值,若需要從硬體上讀取,則調用具體協議的讀寫函數,若是 I2C,調用i2c_transfer
。寫的過程也是大同小異。
3.3 regmap_bus
前面說的都是 Regmap 所做的封裝,而真正進行 I/O 操作就是這最後一層。struct regmap_bus
定義了一個匯流排上的讀寫函數,這一層就像之前對 i2c_transfer
所做的封裝一樣。其定義如下:
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_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;
};
在 Lernel 4.0 中,已經支持了 I2C、SPI、AC97、MMIO 和 SPMI 五種匯流排類型。相信在未來,有更多的匯流排會加進來。其實添加一個匯流排也不是很難,只需 4 個函數就可以了:xxx_read
、xxx_write
、xxx_init
和 xxx_deinit
。具體可以看源碼,這裡就不多說了,留個任務在這吧。