位运算
进制转换与位编号
位编号从0开始
bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
0000 1000 //表示 bit3 = 1
对应:
1U << 3
十六进制:
0x08
十六进制与二进制的关系
每一个十六进制数字对应4个二进制位: 例如:
0x4c = 0100 1100
几种基础位运算
按位与:&
规则:
1 & 1 = 1
其他情况 = 0
- 判断某个位
if(isr & TX_FINISH)
如果:
isr = 0010 0010
TX_FINISH = 0000 0010
result = 0000 0010
结果非零,条件成立
- 保留指定字段
id & CAN_TX_ID_MASK
掩码中为1的位被保留,为0的位被清除。
按位或:|
规则:
有一个为1,结果就是1
主要用于:
- 设置某个位
val |= WORK_MODE;
- 同时设置多个位
val |= WORK_MODE | MODE_AUTO_RETX;
- 组合多个标志
err_int = ERR_WARN_INT |
RX_BUF_OV|
PASSIVE_ERR|
TX_LOSTARB|
BUS_ERR_INT;
按位取反:~
将每一位取反:
0 -> 1
1 -> 0
主要用于生成清楚掩码
fi &= ~CAN_DLC_MASK;
假设:
CAN_DLC_MASK = 0000 1111;
~CAN_DLC_MASK = 1111 0000;
标准写法
val &= ~MASK;
按位异或: ^
规则:
相同为0 ,不同为1
- 翻转某个位
val ^= BIT(3);
详细解释:
val ^= BIT(3);
等价于:
val = val ^ 0000 1000
bit3 原来为 0:
0000 0000
^ 0000 1000
-----------
0000 1000
bit3:0 → 1
bit3 原来为 1:
0000 1000
^ 0000 1000
-----------
0000 0000
bit3:1 → 0
结论:某一位与 1 异或,该位翻转;与 0 异或,该位不变。
左移: <<
将数据向高位移动:
1U << 3;
结果:
0000 0001
->
0000 1000
主要用于:
- 生成单个位
- 将字段值放入寄存器指定的位置
(bt->sjw - 1) << BT_SJW_SHIFT;
其中:
#define BT_SJW_SHIFT 14
将表示将 SJW 字段放到 bit15:14
右移: >>
将数据向低位移动。 驱动中主要用于:
- 将寄存器字段移动到低位
- 数值换算 例如:
(ecc & ERR_TYPE_MASK) >> ERR_TYPE_SHIFT
表示:
- 先保留
bit24:22 - 再右移 22 位
- 得到普通数值
0~7CAN 驱动中还有:
(bt->brp >> 1) - 1
这里不是字段提取,而是硬件规定的 BRP 编码换算。
Linux操作宏
BIT(n) 生成单个位
相当于
#define BIT(nr) (1UL << (nr))
例如:
BIT(0) // 0x00000001
BIT(2) // 0x00000004
BIT(10) // 0x00000400
驱动示例
#define WORK_MODE BIT(0)
#define SELF_TEST_EN BIT(2)
#define MODE_AUTO_RETX BIT(10)
GENMASK(h, 1) 生成连续位掩码
例如:
GENMASK(15,14);
生成:
bit15:14 为 1
其他位置为0
即
0000 0000 0000 0000 1100 0000 0000 0000
驱动实例:
#define BT_SJW_MASK GENMASK(15, 14)
#define BT_BRP_MASK GENMASK(13, 8)
#define BT_TSEG2_MASK GENMASK(6, 4)
#define BT_TSEG1_MASK GENMASK(3, 0)
FILED_GET(mask, reg) 提取字段
传统写法:
field = (reg & MASK) >> SHIFT;
KERNEL内核:
field = FIELD_GET(MASK, reg);
驱动实例:
switch((ecc & ERR_TYPE_MASK) >> ERR_TYPE_SHIFT)
可以改写成:
switch(FIELD_GET(GET_TYPE_MASK, ecc))
定义:
#define ERR_TYPE_NASK GETMASK(24,22)
FILED_GET()会自动根据mask计算右移位值
FILED_PREP(mask,value) 构造字段
传统写法:
field = (value << SHIFT) & MASK;
Kernel:
field = FIELD_PREP(mask, value);
例如CAN时序
cfg = FIELD_PREP(BT_SJW_MASK, bt->sjw - 1) |
FIELD_PREP(BT_BRP_MASK, (bt->brp >> 1) - 1) |
FIELD_PREP(BT_TSEG2_MASK, bt->phase_seg2 - 1) |
FIELD_PREP(BT_TSEG1_MASK,
bt->prop_seg + bt->phase_seg1 - 1);
相比直接移位,它的优点是:
- 不需要手动写 shift
- 字段位置更加清晰
- 自动限制到 mask 范围
- 不容易把偏移写错
FIELD_MAX(mask) 字段最大值
例如:
FIELD_MAX(GENMASK(3, 1))
字段宽度为 3 位,最大值为:
0b111 = 7
适合参数范围检查。
FIELD_FIT(mask, value):判断数值能否放入字段
例如:
if (!FIELD_FIT(BT_TSEG2_MASK, value))
return -EINVAL;
若字段为 3 位,则合法范围为:
0~7
在成熟驱动里,参数通常已经由上层框架校验,所以不一定每次都显式使用。
驱动中常见位操作
CAN驱动举例 rockchip_can.c
设置单个位
val |= FLAG;
CAN:
val |= WORK_MODE;
同时设置多个值
val |= FLAG_A | FLAG_B
CAN驱动:
val = readl(rcan->base + CAN_MODE);
val |= WORK_MODE | MODE_AUTO_RETX;
writel(val, rcan->base + CAN_MODE);
示例:
FLAG_A = BIT(0) // 0000 0001
FLAG_B = BIT(3) // 0000 1000
先合并:
FLAG_A 0000 0001
| FLAG_B 0000 1000
-------------------
0000 1001
然后设置寄存器:
val 0010 0000
| 0000 1001
-----------
0010 1001
清除单个位
val &= ~FLAG;
例如关闭某功能
val &= ~WORK_MODE;
假设寄存器当前:
val = 1010 1011
执行:
val 1010 1011
& ~WORK_MODE 1111 1110
----------------------
1010 1010
结果:
bit0: 1 → 0
其他位:
保持不变
清除连续字段
val &= ~MASK;
CAN 驱动:
fi &= ~CAN_DLC_MASK;
定义:
#define CAN_DLC_MASK GENMASK(3, 0)
作用:清除 DLC 的低 4 位。
判断是否为1
if(reg & FLAG)
CAN驱动:
if (isr & TX_FINISH)
if (isr & RX_FINISH)
if (sta_reg & BUS_OFF)
if (fi & CAN_RTR)
判断结果是“是否非零”,不是必须等于 1。
错误写法:
if ((isr & TX_FINISH) == 1)
TX_FINISH 是 BIT(1),结果可能为 0x2。
判断位是否为 0
if (!(reg & FLAG))
CAN 驱动:
if ((ecc & ERR_DIR_RX) == 0)
cf->data[2] |= CAN_ERR_PROT_TX;
也可以写成:
if (!(ecc & ERR_DIR_RX))
含义:
ERR_DIR_RX = 0
错误发生在发送方向
判断多个标志的任意一个
if(reg & mask)
CAN驱动:
u8 err_int = ERR_WARN_INT |
RX_BUF_OV |
PASSIVE_ERR |
TX_LOSTARB |
BUS_ERR_INT;
if (isr & err_int)
rockchip_can_err(ndev, isr);
判断多个标志是否全部存在
if ((reg & mask) == mask)
例如:
u32 mask = STATUS_READY | STATUS_DONE;
if ((status & mask) == mask)
/* READY 和 DONE 都为 1 */
区别:
if (status & mask)
表示至少一个位存在。
if ((status & mask) == mask)
表示所有目标位都存在。
过滤有效位
val &= MASK;
CAN驱动:
writel(id & CAN_TX_ID_MASK, rcan->base + CAN_TX_ID);
定义:
#define CAN_TX_ID_MASK 0x1fffffff
0x1fffffff 表示低 29 位有效。
Linux 的 can_id 还可能包含:
CAN_EFF_FLAG
CAN_RTR_FLAG
CAN_ERR_FLAG
这些软件标志不能直接写入硬件 CAN ID 寄存器,因此需要:
id & CAN_TX_ID_MASK
只保留实际 CAN ID。
提取低位字段
字段位于最低位时,可以直接:
field = reg & MASK;
CAN 驱动:
cf->can_dlc = can_cc_dlc2len(fi & CAN_DLC_MASK);
因为:
#define CAN_DLC_MASK GENMASK(3, 0)
DLC 已经位于最低位,不需要右移。 也可以写成:
FIELD_GET(CAN_DLC_MASK, fi)
模式 11:提取非低位字段
传统写法:
field = (reg & MASK) >> SHIFT;
CAN 驱动:
switch ((ecc & ERR_TYPE_MASK) >> ERR_TYPE_SHIFT)
定义:
#define ERR_TYPE_MASK GENMASK(24, 22)
#define ERR_TYPE_SHIFT 22
过程:
ecc
↓ 与 ERR_TYPE_MASK
只保留 bit24:22
↓ 右移 22 位
得到 0~7
推荐:
switch (FIELD_GET(ERR_TYPE_MASK, ecc))
构造完整寄存器
CAN位时序:
cfg = ((bt->sjw - 1) << BT_SJW_SHIFT) |
(((bt->brp >> 1) - 1) << BT_BRP_SHIFT) |
((bt->phase_seg2 - 1) << BT_TSEG2_SHIFT) |
((bt->prop_seg + bt->phase_seg1 - 1));
每个字段都遵循:
filed_value << filed_shift
最后使用:
|
组合成完整的32位寄存器 更明确的写法:
cfg = FIELD_PREP(BT_SJW_MASK, bt->sjw - 1) |
FIELD_PREP(BT_BRP_MASK, (bt->brp >> 1) - 1) |
FIELD_PREP(BT_TSEG2_MASK, bt->phase_seg2 - 1) |
FIELD_PREP(BT_TSEG1_MASK,
bt->prop_seg + bt->phase_seg1 - 1);
修改已有字段
不能直接:
val |= FIELD_PREP(MASK, new_value);
因为旧字段可能残留。 正确写法:
val &= ~MASK;
val |= FIELD_PREP(MASK, new_value);
或者:
val = (val & ~MASK) |
FIELD_PREP(MASK, new_value);
例如旧字段:
011
新字段:
100
错误地直接 OR:
011 | 100 = 111
正确操作应先将旧字段清零。
寄存器偏移与位掩码
CAN驱动中: 寄存器偏移地址
#define CAN_MODE 0x00
寄存器中的位:
#define WORK_MODE BIT(0)
使用方式:
val = readl(rcan->base + CAN_MODE);
val |= WORK_MODE;
writel(val, rcan->base + CAN_MODE);
结构:
rcan->base + CAN_MODE
↓
CAN_MODE 寄存器地址
↓
WORK_MODE
CAN_MODE 寄存器内部的 bit0
bitmap&atomic bit opreation
区别
| 类型 | 用途 |
|---|---|
| 普通位操作 | 操作寄存器、变量中的 bit |
| bitmap | 管理大量 bit 状态 |
| 原子位操作 | 多线程/中断环境安全修改 bit |
Kernel bitops
set_bit(n, addr);
clear_bit(n, addr);
test_bit(n, addr);
例如:
unsigned long state;
set_bit(3, &state);
if (test_bit(3, &state))
clear_bit(3, &state);
常见接口:
set_bit()
clear_bit()
change_bit()
test_bit()
test_and_set_bit()
test_and_clear_bit()
test_and_change_bit()
test_and_set_bit() 会:
- 返回原来的位值
- 同时将该位置 1 例如:
if (test_and_set_bit(DEVICE_BUSY, &flags))
return -EBUSY;
含义:
如果原来已经是 busy,则返回忙
否则原子地设置 busy
- 不要对 MMIO 寄存器使用
set_bit()错误理解:
set_bit(3, rcan->base + CAN_MODE);
MMIO 寄存器应该使用:
val = readl(rcan->base + CAN_MODE);
val |= BIT(3);
writel(val, rcan->base + CAN_MODE);
原因是:
set_bit()
用于普通内存位图,不是设备 MMIO 寄存器访问接口。
bitmap 接口
当位数很多时,Kernel 使用 bitmap:
DECLARE_BITMAP(bitmap, 128);
常见操作:
bitmap_zero()
bitmap_fill()
bitmap_set()
bitmap_clear()
bitmap_test_bit()
find_first_bit()
find_next_bit()
驱动初学阶段先认识即可,优先掌握寄存器位操作。