Linux的ARM PrimeCell(tm)通用输入/输出(PL061)的Rust和C代码的对比
Rust code appears to be a driver for the ARM PrimeCell General Purpose Input/Output (PL061). This Rust driver, just like a C driver, operates at a low level to manage the operation of the PL061 GPIO chip.
The code covers various device-specific operations like direction output, getting and setting the GPIO value, managing interrupts, and implementing power management features (like suspend, resume, freeze, and restore).
Let's take a closer look at a few key sections of the code:
direction_output
: This method sets the direction of a GPIO pin to output and sets its initial value. It operates on a GPIO pin defined byoffset
and sets its value tovalue
.get
andset
: These methods are used for reading and writing the state of a GPIO pin.The
irq_handler_set_type
function configures the interrupt to trigger on rising or falling edges of the signal. It reads the current interrupt configuration, updates it, and then writes it back.mask
andunmask
: These methods manage the masking of the interrupts.suspend
,resume
,freeze
,restore
: These methods are for power management. They save and restore the state of the GPIO pins when the device is put into and brought out of a low-power state.module_amba_driver!
: This macro defines the driver, providing necessary metadata about the module like the type, name, author, and license.
Please note, the code appears to be incomplete or contains some formatting errors, as there are some inconsistencies and orphaned code blocks. For example, the if rising { "RISING" } else { "FALLING}" }
block is floating without context.
Rust
这段Rust代码是Linux的ARM PrimeCell(tm)通用输入/输出(PL061)驱动的部分实现。下面我来解释一下代码的主要部分。
首先,这段代码引入了一些Rust的内核模块,如kernel
、amba
、device
、gpio
等,这些模块包含了驱动编写所需的一些基础结构和函数。
然后,定义了一些常量,如GPIODIR
、GPIOIS
等,这些常量是GPIO寄存器的地址偏移量。
在direction_output
函数中,我们看到对GPIO方向进行设置。如果value
为真,那么GPIO被设置为输出模式,同时输出的值也被设置。
在get
函数中,我们看到获取GPIO值的操作。这个函数返回GPIO当前的状态,如果GPIO的值为1,那么返回真。
在set
函数中,我们看到设置GPIO值的操作。如果value
为真,那么GPIO的值被设置为1,否则被设置为0。
在irq_set_type
函数中,我们看到了中断类型的设置。根据trigger
的值,会设置为上升沿触发,下降沿触发,或者禁用所有触发。
在mask
函数中,我们看到中断被屏蔽的操作。这个函数会关闭与指定硬件中断号相关的GPIO中断。
在suspend
、resume
、freeze
和restore
函数中,我们看到了电源管理相关的操作。在suspend
和freeze
函数中,驱动会保存当前的GPIO状态,并关闭GPIO的操作。在resume
和restore
函数中,驱动会恢复之前保存的GPIO状态。
最后,module_amba_driver!
宏定义了驱动的一些元信息,如驱动的名字、作者和许可证。这是一个Rust版本的模块初始化宏,它定义了驱动的加载和卸载函数,以及驱动与硬件设备之间的匹配信息。
use kernel::{
amba, bit, bits_iter, define_amba_id_table, device, gpio,
io_mem::IoMem,
irq::{self, ExtraResult, IrqData, LockedIrqData},
power,
prelude::*,
sync::{Arc, ArcBorrow, RawSpinLock},
};
const GPIODIR: usize = 0x400;
const GPIOIS: usize = 0x404;
const GPIOIBE: usize = 0x408;
const GPIOIEV: usize = 0x40C;
const GPIOIE: usize = 0x410;
const GPIOMIS: usize = 0x418;
fn direction_output(data: ArcBorrow<'_, DeviceData>, offset: u32, value: bool) -> Result {
let woffset = bit(offset + 2).into();
let _guard = data.inner.lock_irqdisable();
let pl061 = data.resources().ok_or(ENXIO)?;
pl061.base.try_writeb((value as u8) << offset, woffset)?;
let mut gpiodir = pl061.base.readb(GPIODIR);
gpiodir |= bit(offset);
pl061.base.writeb(gpiodir, GPIODIR);
// gpio value is set again, because pl061 doesn't allow to set value of a gpio pin before
// configuring it in OUT mode.
pl061.base.try_writeb((value as u8) << offset, woffset)?;
Ok(())
}
fn get(data: ArcBorrow<'_, DeviceData>, offset: u32) -> Result<bool> {
let pl061 = data.resources().ok_or(ENXIO)?;
Ok(pl061.base.try_readb(bit(offset + 2).into())? != 0)
}
fn set(data: ArcBorrow<'_, DeviceData>, offset: u32, value: bool) {
if let Some(pl061) = data.resources() {
let woffset = bit(offset + 2).into();
let _ = pl061.base.try_writeb((value as u8) << offset, woffset);
offset,
if rising { "RISING" } else { "FALLING}" }
);
} else {
// No trigger: disable everything.
gpiois &= !bit;
gpioibe &= !bit;
gpioiev &= !bit;
irq_data.set_bad_handler();
dev_warn!(data.dev, "no trigger selected for line {}\n", offset);
}
pl061.base.writeb(gpiois, GPIOIS);
pl061.base.writeb(gpioibe, GPIOIBE);
pl061.base.writeb(gpioiev, GPIOIEV);
Ok(ExtraResult::None)
}
fn mask(data: ArcBorrow<'_, DeviceData>, irq_data: &IrqData) {
let mask = bit(irq_data.hwirq() % irq::HwNumber::from(PL061_GPIO_NR));
let _guard = data.inner.lock();
if let Some(pl061) = data.resources() {
let gpioie = pl061.base.readb(GPIOIE) & !mask;
inner.csave_regs.gpio_data = 0;
inner.csave_regs.gpio_dir = pl061.base.readb(GPIODIR);
inner.csave_regs.gpio_is = pl061.base.readb(GPIOIS);
inner.csave_regs.gpio_ibe = pl061.base.readb(GPIOIBE);
inner.csave_regs.gpio_iev = pl061.base.readb(GPIOIEV);
inner.csave_regs.gpio_ie = pl061.base.readb(GPIOIE);
for offset in 0..PL061_GPIO_NR {
if inner.csave_regs.gpio_dir & bit(offset) != 0 {
if let Ok(v) = <Self as gpio::Chip>::get(data, offset.into()) {
inner.csave_regs.gpio_data |= (v as u8) << offset;
}
}
}
Ok(())
}
fn resume(data: ArcBorrow<'_, DeviceData>) -> Result {
let inner = data.inner.lock();
let pl061 = data.resources().ok_or(ENXIO)?;
for offset in 0..PL061_GPIO_NR {
if inner.csave_regs.gpio_dir & bit(offset) != 0 {
let value = inner.csave_regs.gpio_data & bit(offset) !
pl061.base.writeb(inner.csave_regs.gpio_is, GPIOIS);
pl061.base.writeb(inner.csave_regs.gpio_ibe, GPIOIBE);
pl061.base.writeb(inner.csave_regs.gpio_iev, GPIOIEV);
pl061.base.writeb(inner.csave_regs.gpio_ie, GPIOIE);
Ok(())
}
fn freeze(data: ArcBorrow<'_, DeviceData>) -> Result {
Self::suspend(data)
}
fn restore(data: ArcBorrow<'_, DeviceData>) -> Result {
Self::resume(data)
}
}
module_amba_driver! {
type: PL061Device,
name: "pl061_gpio",
author: "Wedson Almeida Filho",
license: "GPL",
}
C
#include <linux/amba/bus.h>
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/gpio/driver.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/irq.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pm.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#define GPIODIR 0x400
#define GPIOIS 0x404
#define GPIOIBE 0x408
#define GPIOIEV 0x40C
#define GPIOIE 0x410
#define GPIORIS 0x414
#define GPIOMIS 0x418
#define GPIOIC 0x41C
#define PL061_GPIO_NR 8
#ifdef CONFIG_PM
struct pl061_context_save_regs {
u8 gpio_data;
u8 gpio_dir;
u8 gpio_is;
u8 gpio_ibe;
u8 gpio_iev;
u8 gpio_ie;
};
#endif
struct pl061 {
raw_spinlock_t lock;
void __iomem *base;
struct gpio_chip gc;
int parent_irq;
#ifdef CONFIG_PM
struct pl061_context_save_regs csave_regs;
#endif
};
static int pl061_get_direction(struct gpio_chip *gc, unsigned offset)
{
struct pl061 *pl061 = gpiochip_get_data(gc);
if (readb(pl061->base + GPIODIR) & BIT(offset))
return GPIO_LINE_DIRECTION_OUT;
return GPIO_LINE_DIRECTION_IN;
}
static int pl061_direction_input(struct gpio_chip *gc, unsigned offset)
{
struct pl061 *pl061 = gpiochip_get_data(gc);
unsigned long flags;
unsigned char gpiodir;
raw_spin_lock_irqsave(&pl061->lock, flags);
gpiodir = readb(pl061->base + GPIODIR);
gpiodir &= ~(BIT(offset));
writeb(gpiodir, pl061->base + GPIODIR);
raw_spin_unlock_irqrestore(&pl061->lock, flags);
return 0;
}
static int pl061_direction_output(struct gpio_chip *gc, unsigned offset,
int value)
{
struct pl061 *pl061 = gpiochip_get_data(gc);
unsigned long flags;
unsigned char gpiodir;
raw_spin_lock_irqsave(&pl061->lock, flags);
writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
gpiodir = readb(pl061->base + GPIODIR);
gpiodir |= BIT(offset);
writeb(gpiodir, pl061->base + GPIODIR);
/*
* gpio value is set again, because pl061 doesn't allow to set value of
* a gpio pin before configuring it in OUT mode.
*/
writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
raw_spin_unlock_irqrestore(&pl061->lock, flags);
return 0;
}
static int pl061_get_value(struct gpio_chip *gc, unsigned offset)
{
struct pl061 *pl061 = gpiochip_get_data(gc);
return !!readb(pl061->base + (BIT(offset + 2)));
}
static void pl061_set_value(struct gpio_chip *gc, unsigned offset, int value)
{
struct pl061 *pl061 = gpiochip_get_data(gc);
writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
}
static int pl061_irq_type(struct irq_data *d, unsigned trigger)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct pl061 *pl061 = gpiochip_get_data(gc);
int offset = irqd_to_hwirq(d);
unsigned long flags;
u8 gpiois, gpioibe, gpioiev;
u8 bit = BIT(offset);
if (offset < 0 || offset >= PL061_GPIO_NR)
return -EINVAL;
if ((trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) &&
(trigger & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)))
{
dev_err(gc->parent,
"trying to configure line %d for both level and edge "
"detection, choose one!\n",
offset);
return -EINVAL;
}
raw_spin_lock_irqsave(&pl061->lock, flags);
gpioiev = readb(pl061->base + GPIOIEV);
gpiois = readb(pl061->base + GPIOIS);
gpioibe = readb(pl061->base + GPIOIBE);
if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) {
bool polarity = trigger & IRQ_TYPE_LEVEL_HIGH;
/* Disable edge detection */
gpioibe &= ~bit;
/* Enable level detection */
gpiois |= bit;
/* Select polarity */
if (polarity)
gpioiev |= bit;
else
gpioiev &= ~bit;
irq_set_handler_locked(d, handle_level_irq);
dev_dbg(gc->parent, "line %d: IRQ on %s level\n",
offset,
polarity ? "HIGH" : "LOW");
} else if ((trigger & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) {
/* Disable level detection */
gpiois &= ~bit;
/* Select both edges, setting this makes GPIOEV be ignored */
gpioibe |= bit;
irq_set_handler_locked(d, handle_edge_irq);
dev_dbg(gc->parent, "line %d: IRQ on both edges\n", offset);
} else if ((trigger & IRQ_TYPE_EDGE_RISING) ||
(trigger & IRQ_TYPE_EDGE_FALLING)) {
bool rising = trigger & IRQ_TYPE_EDGE_RISING;
/* Disable level detection */
gpiois &= ~bit;
/* Clear detection on both edges */
gpioibe &= ~bit;
/* Select edge */
if (rising)
gpioiev |= bit;
else
gpioiev &= ~bit;
irq_set_handler_locked(d, handle_edge_irq);
dev_dbg(gc->parent, "line %d: IRQ on %s edge\n",
offset,
rising ? "RISING" : "FALLING");
} else {
/* No trigger: disable everything */
gpiois &= ~bit;
gpioibe &= ~bit;
gpioiev &= ~bit;
irq_set_handler_locked(d, handle_bad_irq);
dev_warn(gc->parent, "no trigger selected for line %d\n",
offset);
}
writeb(gpiois, pl061->base + GPIOIS);
writeb(gpioibe, pl061->base + GPIOIBE);
writeb(gpioiev, pl061->base + GPIOIEV);
raw_spin_unlock_irqrestore(&pl061->lock, flags);
return 0;
}
static void pl061_irq_handler(struct irq_desc *desc)
{
unsigned long pending;
int offset;
struct gpio_chip *gc = irq_desc_get_handler_data(desc);
struct pl061 *pl061 = gpiochip_get_data(gc);
struct irq_chip *irqchip = irq_desc_get_chip(desc);
chained_irq_enter(irqchip, desc);
pending = readb(pl061->base + GPIOMIS);
if (pending) {
for_each_set_bit(offset, &pending, PL061_GPIO_NR)
generic_handle_domain_irq(gc->irq.domain,
offset);
}
chained_irq_exit(irqchip, desc);
}
static void pl061_irq_mask(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct pl061 *pl061 = gpiochip_get_data(gc);
u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
u8 gpioie;
raw_spin_lock(&pl061->lock);
gpioie = readb(pl061->base + GPIOIE) & ~mask;
writeb(gpioie, pl061->base + GPIOIE);
raw_spin_unlock(&pl061->lock);
gpiochip_disable_irq(gc, d->hwirq);
}
static void pl061_irq_unmask(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct pl061 *pl061 = gpiochip_get_data(gc);
u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
u8 gpioie;
gpiochip_enable_irq(gc, d->hwirq);
raw_spin_lock(&pl061->lock);
gpioie = readb(pl061->base + GPIOIE) | mask;
writeb(gpioie, pl061->base + GPIOIE);
raw_spin_unlock(&pl061->lock);
}
/**
* pl061_irq_ack() - ACK an edge IRQ
* @d: IRQ data for this IRQ
*
* This gets called from the edge IRQ handler to ACK the edge IRQ
* in the GPIOIC (interrupt-clear) register. For level IRQs this is
* not needed: these go away when the level signal goes away.
*/
static void pl061_irq_ack(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct pl061 *pl061 = gpiochip_get_data(gc);
u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
raw_spin_lock(&pl061->lock);
writeb(mask, pl061->base + GPIOIC);
raw_spin_unlock(&pl061->lock);
}
static int pl061_irq_set_wake(struct irq_data *d, unsigned int state)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct pl061 *pl061 = gpiochip_get_data(gc);
return irq_set_irq_wake(pl061->parent_irq, state);
}
static void pl061_irq_print_chip(struct irq_data *data, struct seq_file *p)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
seq_printf(p, dev_name(gc->parent));
}
static const struct irq_chip pl061_irq_chip = {
.irq_ack = pl061_irq_ack,
.irq_mask = pl061_irq_mask,
.irq_unmask = pl061_irq_unmask,
.irq_set_type = pl061_irq_type,
.irq_set_wake = pl061_irq_set_wake,
.irq_print_chip = pl061_irq_print_chip,
.flags = IRQCHIP_IMMUTABLE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
{
struct device *dev = &adev->dev;
struct pl061 *pl061;
struct gpio_irq_chip *girq;
int ret, irq;
pl061 = devm_kzalloc(dev, sizeof(*pl061), GFP_KERNEL);
if (pl061 == NULL)
return -ENOMEM;
pl061->base = devm_ioremap_resource(dev, &adev->res);
if (IS_ERR(pl061->base))
return PTR_ERR(pl061->base);
raw_spin_lock_init(&pl061->lock);
pl061->gc.request = gpiochip_generic_request;
pl061->gc.free = gpiochip_generic_free;
pl061->gc.base = -1;
pl061->gc.get_direction = pl061_get_direction;
pl061->gc.direction_input = pl061_direction_input;
pl061->gc.direction_output = pl061_direction_output;
pl061->gc.get = pl061_get_value;
pl061->gc.set = pl061_set_value;
pl061->gc.ngpio = PL061_GPIO_NR;
pl061->gc.label = dev_name(dev);
pl061->gc.parent = dev;
pl061->gc.owner = THIS_MODULE;
/*
* irq_chip support
*/
writeb(0, pl061->base + GPIOIE); /* disable irqs */
irq = adev->irq[0];
if (!irq)
dev_warn(&adev->dev, "IRQ support disabled\n");
pl061->parent_irq = irq;
girq = &pl061->gc.irq;
gpio_irq_chip_set_chip(girq, &pl061_irq_chip);
girq->parent_handler = pl061_irq_handler;
girq->num_parents = 1;
girq->parents = devm_kcalloc(dev, 1, sizeof(*girq->parents),
GFP_KERNEL);
if (!girq->parents)
return -ENOMEM;
girq->parents[0] = irq;
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_bad_irq;
ret = devm_gpiochip_add_data(dev, &pl061->gc, pl061);
if (ret)
return ret;
amba_set_drvdata(adev, pl061);
dev_info(dev, "PL061 GPIO chip registered\n");
return 0;
}
#ifdef CONFIG_PM
static int pl061_suspend(struct device *dev)
{
struct pl061 *pl061 = dev_get_drvdata(dev);
int offset;
pl061->csave_regs.gpio_data = 0;
pl061->csave_regs.gpio_dir = readb(pl061->base + GPIODIR);
pl061->csave_regs.gpio_is = readb(pl061->base + GPIOIS);
pl061->csave_regs.gpio_ibe = readb(pl061->base + GPIOIBE);
pl061->csave_regs.gpio_iev = readb(pl061->base + GPIOIEV);
pl061->csave_regs.gpio_ie = readb(pl061->base + GPIOIE);
for (offset = 0; offset < PL061_GPIO_NR; offset++) {
if (pl061->csave_regs.gpio_dir & (BIT(offset)))
pl061->csave_regs.gpio_data |=
pl061_get_value(&pl061->gc, offset) << offset;
}
return 0;
}
static int pl061_resume(struct device *dev)
{
struct pl061 *pl061 = dev_get_drvdata(dev);
int offset;
for (offset = 0; offset < PL061_GPIO_NR; offset++) {
if (pl061->csave_regs.gpio_dir & (BIT(offset)))
pl061_direction_output(&pl061->gc, offset,
pl061->csave_regs.gpio_data &
(BIT(offset)));
else
pl061_direction_input(&pl061->gc, offset);
}
writeb(pl061->csave_regs.gpio_is, pl061->base + GPIOIS);
writeb(pl061->csave_regs.gpio_ibe, pl061->base + GPIOIBE);
writeb(pl061->csave_regs.gpio_iev, pl061->base + GPIOIEV);
writeb(pl061->csave_regs.gpio_ie, pl061->base + GPIOIE);
return 0;
}
static const struct dev_pm_ops pl061_dev_pm_ops = {
.suspend = pl061_suspend,
.resume = pl061_resume,
.freeze = pl061_suspend,
.restore = pl061_resume,
};
#endif
static const struct amba_id pl061_ids[] = {
{
.id = 0x00041061,
.mask = 0x000fffff,
},
{ 0, 0 },
};
MODULE_DEVICE_TABLE(amba, pl061_ids);
static struct amba_driver pl061_gpio_driver = {
.drv = {
.name = "pl061_gpio",
#ifdef CONFIG_PM
.pm = &pl061_dev_pm_ops,
#endif
},
.id_table = pl061_ids,
.probe = pl061_probe,
};
module_amba_driver(pl061_gpio_driver);
MODULE_LICENSE("GPL v2");