很多同学在操作寄存器的时候,要设置或清除某个位时,都是手动使用左移(<<)或右移(>>)来进行操作,但其实内核中已经为我们提供了一些宏,今天我们来看一下这些宏。
#define BIT(nr) (UL(1) << (nr))
生成第 nr 位为 1,其余位为 0 的整数
使用场景
#define CTRL_ENABLE BIT(0)#define CTRL_RESET BIT(1)#define CTRL_IRQ_EN BIT(5)reg |= CTRL_ENABLE;reg &= ~CTRL_RESET;
#define FLAG_RUNNING BIT(0)#define FLAG_SUSPEND BIT(1)#define FLAG_ERROR BIT(2)unsigned long flags;//判断标志位flags |= FLAG_RUNNING;if (flags & FLAG_ERROR) { ...}
#define GENMASK(h, l) \ (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l))#define __GENMASK(h, l) \ (((~UL(0)) - (UL(1) << (l)) + 1) & \ (~UL(0) >> (BITS_PER_LONG - 1 - (h))))
从第 h 位到第 l 位(包含 h 和 l),全部置 1,其余为 0
比如:
GENMASK(7, 4)位号: 76543210结果: 11110000
即:
#define FIELD_GET(_mask, _reg) \ ({ \ (typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask)); \ })
从寄存器值 reg 中提取由 mask 定义的位,并将其转换为原始数值(自动右移)。
在没有宏之前,我们需要这样写代码才能获取到寄存器中的值:
// 假设我们要设置寄存器的 [7:4] 位#define MY_FIELD_MASK 0xF0#define MY_FIELD_SHIFT 4// 提取数据val = (reg & MY_FIELD_MASK) >> MY_FIELD_SHIFT;
有了宏之后,我们就不需要关心shift (偏移)了。
比如:
有一个 8 位的寄存器,我们要获取中间 [5:2] 位的值(掩码为 0x3C,即二进制 00111100):
输入寄存器值: 10110110
应用 Mask: (10110110 & 00111100) -> 00110100
自动右移: 宏会自动计算出需要右移 2 位,结果得到 00001101 (十进制 13)。
#define FIELD_PREP(_mask, _val) \ ({ \ ((typeof(_mask))(_val) << __bf_shf(_mask)) & (_mask); \ })
将数值 val 放入 mask 定义的对应位置中(自动左移)
在没有宏之前,我们需要这样写代码才能设置寄存器值:
// 假设我们要设置寄存器的 [7:4] 位#define MY_FIELD_MASK 0xF0#define MY_FIELD_SHIFT 4// 准备写入reg |= (val << MY_FIELD_SHIFT) & MY_FIELD_MASK;
有了宏之后,和上面一样,不需要再关心shift (偏移)了。
比如:
如果要将数值 5 (二进制 101) 放入上述寄存器的 [5:2] 位:
输入数值: 00000101
自动左移: 宏根据 Mask 0x3C 自动左移 2 位 -> 00010100
#include<linux/bitfield.h>/* 1. 定义掩码 (通常使用 GENMASK 宏) */#define REG_CONTROL_SPEED_MASK GENMASK(7, 4) // 定义位 [7:4]voidupdate_speed(void __iomem *reg_addr, u32 new_speed){ u32 reg_val = readl(reg_addr);/* 2. 使用 FIELD_GET 提取当前速度 */ u32 current_speed = FIELD_GET(REG_CONTROL_SPEED_MASK, reg_val);/* 3. 使用 FIELD_PREP 准备新的寄存器值 */// 清除旧位并填入新位 reg_val &= ~REG_CONTROL_SPEED_MASK; reg_val |= FIELD_PREP(REG_CONTROL_SPEED_MASK, new_speed); writel(reg_val, reg_addr);}