RK3399—中断

article/2025/8/30 5:19:20

  中断是操作系统最常见的事件之一,无论是系统层的“软中断”还是CPU底层的“硬中断”都是编程时常用的。中断的作用之一是充分利用CPU资源,正常情况下,CPU执行用户任务,当外设触发中断产生时,CPU停止当前任务,转而去处理中断信息。处理完中断再返回任务处继续执行。


  对于硬中断,顾名思义,由硬件产生,CPU定时器、各类总线、GPIO以及外设键盘、磁盘、鼠标等。对于嵌入式来说,触摸屏、传感器等都可以产生中断信号。硬中断处理要实时和高效率,一般由驱动层处理。


  对于软中断,是由操作系统实现,不会直接中断CPU。操作系统在任务管理、调度过程会有软中断过程。对于驱动层面,软中断往往结合硬中断一起使用。


1. linux中断

1.1 中断上半部和下半部

  中断的基本原则是“快速执行完并退出”,但一些设备中往往需要处理大量的耗时事务。不同于裸机编程,操作系统是多个进程和多个线程执行,宏观上达到并行运行的状态,外设中断则会打断内核中任务调度和运行,及屏蔽其外设的中断响应,中断函数耗时过长会使得系统实时性和并发性降低。为了提高系统的实时性和并发性,linux内核将中断处理程序分为上半部(top half)和下半部(bottom half)。上半部分任务比较少,处理一些寄存器操作、时间敏感任务,以及“登记中断”通知内核及时处理下半部的任务。下半部分,则负责处理中断任务中的大部分任务,特别是耗时任务必需放在下半部。

以触摸屏外设为例:

中断上半部:有触摸信号时,产生一个中断通知CPU,驱动负责将中断信息登记到内核,并通知内核处理, 然后退出中断。
中断下半部:内核获取中断信息,读取触摸屏数据返回给系统使用。


中断上下部区别:

  • 上半部由外设中断触发,下半部由上半部触发。
  • 上半部不会被打断,下半部可以被其他中断打断。
  • 上半部分处理时间敏感任务,主要任务、耗时任务放在下半部。

1.2 中断设计

  一个完整的中断程序由上半部和下半部分共同构成,在编写设备驱动程序前,就需考虑好上半部和下半部的分配。很多时候上半部与下半部并没有严格的区分界限,主要由程序员根据实际设计,如某些外设中断可以没有下半部。关于上下半部的划分原则,就是主要事务、耗时事务划分在下半部处理。


可以参考以下原则:

  • 与硬件相关的操作,如寄存器访问,必须放在上半部。
  • 对时间敏感、要求实时性的任务放在上半部。
  • 该任务不能被其他中断或者进程打断的放在上半部。
  • 实时性要求不高的任务、耗时任务放在下半部。

1.3 中断上半部实现

  上半部中断一般包括几个步骤

  • 硬件相关中断配置
  • 中断回调函数
  • 中断号申请
  • 中断注册

1.3.1 中断回调函数

  该部分为真正的中断上半部,处理时间敏感任务和中断状态清除,同时触发内核调度下半部。 中断回调函数类型,是一个函数指针,位于“kernel/include/linux/interrupt.h”中。

typedef irqreturn_t (*irq_handler_t)(int, void *);
  • 参数1,中断号。
  • 参数2,通用void指针,一般指向设备数据结构体,引用前通过强制转换获取设备私有信息。
  • 返回值,irqreturn_t 枚举类型,位于“kernel/include/linux/irqreturn.h”。
/*** enum irqreturn* @IRQ_NONE		interrupt was not from this device or was not handled* @IRQ_HANDLED		interrupt was handled by this device* @IRQ_WAKE_THREAD	handler requests to wake the handler thread*/
enum irqreturn {IRQ_NONE		= (0 << 0),	/* 收到的中断信号与注册中断源信号不一致 */IRQ_HANDLED		= (1 << 0),	/* 接收到正确中断信号,并且作相应处理 */IRQ_WAKE_THREAD		= (1 << 1),
};typedef enum irqreturn irqreturn_t;


中断函数是不存在返回值的,该回调函数返回值,表示系统响应中断信号的处理状态。


1.3.2 中断号

  系统中断源是多元的,linux内核会给每个中断源分配一个唯一的中断号,用以区分不同设备的中断。终端号为一个int类型的整数。引入设备树后,中断号一般在会设备树中设备节点描述了,驱动程序通过指定函数获取。


  • 从设备树获取中断号
unsigned int irq_of_parse_and_map(struct device_node *node, int index);
参数含义
引用#include <linux/of_irq.h>
node设备节点
index索引序号,获取指定序号中断信息(如果有多个),只有一个中断信息填0
返回成功返回中断号,失败返回负数
  • 对于gpio,可以通过gpio序号转为中断号
int gpio_to_irq(unsigned gpio)	/* 把gpio序号转换为中断号 */

1.3.3 中断注册

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)

  request_ir用于中断注册,同时函数内部会使能中断,不需手动再去使能。

参数含义
引用#include <linux/interrupt.h>
irq中断序号
handler中断处理回调函数,1.3.1节定义
flags中断类型,详细见下面描述
name中断名称,可以在"/proc/interrupts"文件查看
dev数据结构体,一般是设备数据结构,传递给回调函数第二个形参;设备共享中断线时(IRQF_SHARED),可以用来区分不同设备
返回成功返回0,失败返回负数,中断已存放返回-EBUSY

关于中断类型(flags),“interrupt.h”有相关定义

#define IRQF_TRIGGER_NONE	0x00000000		/* 无触发中断 */
#define IRQF_TRIGGER_RISING	0x00000001		/* 上升沿触发 */
#define IRQF_TRIGGER_FALLING	0x00000002	/* 下降沿触发 */
#define IRQF_TRIGGER_HIGH	0x00000004		/* 高电平触发 */
#define IRQF_TRIGGER_LOW	0x00000008		/* 电平触发 */#define IRQF_SHARED			0x00000080		/* 多个设备共享中断 */
#define IRQF_PROBE_SHARED	0x00000100	
#define __IRQF_TIMER		0x00000200
#define IRQF_PERCPU			0x00000400
#define IRQF_NOBALANCING	0x00000800
#define IRQF_IRQPOLL		0x00001000
#define IRQF_ONESHOT		0x00002000
#define IRQF_NO_SUSPEND		0x00004000
#define IRQF_FORCE_RESUME	0x00008000
#define IRQF_NO_THREAD		0x00010000
#define IRQF_EARLY_RESUME	0x00020000
#define IRQF_COND_SUSPEND	0x00040000

1.3.4 中断使能和失能

void disable_irq_nosync(unsigned int irq);	/* 失能中断,立即返回 */
bool disable_hardirq(unsigned int irq);
void disable_irq(unsigned int irq);			/* 失能中断,需等待中断执行完才返回 */
void disable_percpu_irq(unsigned int irq);
void enable_irq(unsigned int irq);			/* 使能中断 */

注:
调用“disable_irq”函数前,必须确保不会产生新中断,因为该函数需等待中断执行完才返回,如果有新中断一直产生,会导致阻塞。


1.3.5 中断释放

void free_irq(unsigned int, void *);

  设备退出时,必须释放中断。“free_irq”函数释放设备中断后,并会禁止设备中断,无需手动禁止。如果是共享中断,只有释放完最后一个设备才会禁止中断。


1.4 中断下半部实现

  linux内核提供了3种下半部实现方式,分别是soft tirq(软中断)、tasklet、work queue(工作队列),三种方式应用在不同的场合下。

  • 软中断用于重要场合,对执行时间要求比较高,倾向于提高系统性能
  • tasklet和工作队列用于大多数普通驱动
  • tasklet是在中断上下文执行,工作队列在内核线程执行,可以挂起(sleep)延迟处理。任务需挂起,用工作队列

1.4.1 软中断

  linux内核软中断用结构体“struct softirq_action”描述,位于“kernel/include/linux/interrupt.h”中,从原型看就是一个软中断处理回调函数指针,函数实体就是“下半部”处理的任务,由驱动工程师实现。

/* softirq mask and active fields moved to irq_cpustat_t in* asm/hardirq.h to get better cache usage.  KAO*/struct softirq_action
{void	(*action)(struct softirq_action *);
};

  “interrupt.h”枚举了常用软中断类型。

/* PLEASE, avoid to allocate new softirqs, if you need not _really_ highfrequency threaded job scheduling. For almost all the purposestasklets are more than enough. F.e. all serial device BHs etal. should be converted to tasklets, not to softirqs.*/enum
{HI_SOFTIRQ=0,			/* 最高优先级软中断 */TIMER_SOFTIRQ,			/* 定时器软中断 */NET_TX_SOFTIRQ,			/* 网络发送软中断 */NET_RX_SOFTIRQ,			/* 网络接收软中断 */BLOCK_SOFTIRQ,			/* 块操作软中断 */BLOCK_IOPOLL_SOFTIRQ,	/* 块IO轮询软中断 */TASKLET_SOFTIRQ,		/* tasklet软中断 */SCHED_SOFTIRQ,			/* 调度软中断 */HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on thenumbering. Sigh! */	/* 高精度定时器软中断 */RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */	/* RCU软中断 */NR_SOFTIRQS		/* 软中断总数 */
};

软中断使用步骤

1)注册软中断

void open_softirq(int nr, void (*action)(struct softirq_action *));
参数含义
nr软中断类型,interrupt.h中枚举类型
action软中断回调处理函数
返回

  软中断注册,必须采用“静态”方式,因为内核起来后会调用“softirq_init”初始化软中断。


2)触发软中断
  在上半部调用“raise_softirq”函数通知内核执行下半部。

void raise_softirq(unsigned int nr);
参数含义
nr软中断类型,interrupt.h中枚举类型
返回

1.4.2 tasklet

  tasklet本质是软中断,基于软中断封装实现的一种方式,tasklet的描述“struct tasklet_struct”结构体同样位于“kernel/include/linux/interrupt.h”中。

struct tasklet_struct
{struct tasklet_struct *next;	/* 链式存储,表示下一tasklet节点 */unsigned long state;			/* tasklet状态,TASKLET_STATE_SCHED表示被调度过程,TASKLET_STATE_RUN表示tasklet正在某个CPU上执行 */atomic_t count;					/* tasklet引用数,原子操作 */void (*func)(unsigned long);	/* 回调处理函数,下半部处理任务置于此 */unsigned long data;				/* 传递给回调函数fun的参数 */
};

count成员与tasklet状态相关,如果count等于0,tasklet处于enable状态,大于0则处于disable状态。

static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{atomic_inc(&t->count);smp_mb__after_atomic();
}static inline void tasklet_disable(struct tasklet_struct *t)
{tasklet_disable_nosync(t);	/* 自减 */tasklet_unlock_wait(t);smp_mb();
}static inline void tasklet_enable(struct tasklet_struct *t)
{smp_mb__before_atomic();atomic_dec(&t->count);		/* 自加 */
}

tasklet使用步骤

1)注册tasklet

  使用tasklet机制,首先需定义一个“struct tasklet_struct”,可以动态和静态定义,然后调用“tasklet_init”函数初始化。

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);
参数含义
t需初始化的tasklet结构体实体地址
func下半部回调处理函数
data传递给回调函数fun的参数
返回

  linux内核“interrupt.h”中封装了一个初始化的宏“DECLARE_TASKLET”,也可以直接调用该宏初始化,传入参数与“tasklet_init”一致。

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

2)触发调度
  在上半部调用“tasklet_schedule”函数通知内核执行下半部调度。

void tasklet_schedule(struct tasklet_struct *t)
参数含义
t需调度的tasklet结构体实体地址
返回

1.4.3 工作队列

  工作队列与前两种方式最大不同是,下半部任务由内核创建一个线程进行处理。内核线程有可能被其他线程抢占,因此工作队列允许睡眠或者重新调度。如果下半部任务实时性要求不高,允许睡眠,则选择工作队列;否则选择软中断或者tasklet。工作队列是没有优先级的,多个工作队列时,是按照FIFO的方式进行处理。


  工作队列的方式下,把下半部任务封装为工作项(work),linux内核用“struct work_strcut”结构体描述,位于“kernel/include/workqueue.h”中。

struct work_struct {atomic_long_t data;		/* 传递给回调函数fun的参数 */struct list_head entry;	/* 指针入口 */work_func_t func;		/* 回调处理函数,下半部处理任务置于此 */
#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};typedef void (*work_func_t)(struct work_struct *work);

  一系列工作项(work)组成工作队列(work queue),“workqueue_struct”结构体描述,原型位于“kernel/kernel/workqueue.c”中。

/** The externally visible workqueue.  It relays the issued work items to* the appropriate worker_pool through its pool_workqueues.*/
struct workqueue_struct {struct list_head	pwqs;		/* WR: all pwqs of this wq */struct list_head	list;		/* PR: list of all workqueues */struct mutex		mutex;		/* protects this wq */int			work_color;	/* WQ: current work color */int			flush_color;	/* WQ: current flush color */atomic_t		nr_pwqs_to_flush; /* flush in progress */struct wq_flusher	*first_flusher;	/* WQ: first flusher */struct list_head	flusher_queue;	/* WQ: flush waiters */struct list_head	flusher_overflow; /* WQ: flush overflow list */struct list_head	maydays;	/* MD: pwqs requesting rescue */struct worker		*rescuer;	/* I: rescue worker */int			nr_drainers;	/* WQ: drain in progress */int			saved_max_active; /* WQ: saved pwq max_active */struct workqueue_attrs	*unbound_attrs;	/* PW: only for unbound wqs */struct pool_workqueue	*dfl_pwq;	/* PW: only for unbound wqs */#ifdef CONFIG_SYSFSstruct wq_device	*wq_dev;	/* I: for sysfs interface */
#endif
#ifdef CONFIG_LOCKDEPstruct lockdep_map	lockdep_map;
#endifchar			name[WQ_NAME_LEN]; /* I: workqueue name */......
};

工作队列使用步骤

  工作队列可以使用linux系统创建的队列和用户自定义队列,队列可以设定为延迟执行和非延迟执行。


1)注册工作项

  使用工作队列机制,首先需定义一个“struct work_struct”工作项,可以动态和静态定义。


  • 非延工作迟项注册
#define INIT_WORK(_work, _func)						\__INIT_WORK((_work), (_func), 0)#define DECLARE_WORK(n, f)						\struct work_struct n = __WORK_INITIALIZER(n, f)

  • 延迟工作项注册
#define INIT_DELAYED_WORK(_work, _func)					\__INIT_DELAYED_WORK(_work, _func, 0)#define DECLARE_DELAYED_WORK(n, f)					\struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)
参数含义
_work待注册的工作项,静态注册
_func下半部回调处理函数
n待注册的工作指针,动态注册
返回

注:
使用系统工作队列时,只需注册工作任务即可。


2)使用自定义队列

  • 创建工作队列

  如果使用用户自定义的工作队列,则首先需创建一个工作队列。创建工作队列,首先是定义一个“struct workqueue_struct”工作队列指针,调用“create_singlethread_workqueue”宏创建工作队列。

#define create_singlethread_workqueue(name)				\alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM, name)
参数含义
name工作队列名称
返回工作队列首地址

例子:

struct workqueue_struct *pworkqueue = create_singlethread_workqueue("wq0");

  • 绑定自定义工作队列
/* 非延迟工作队列绑定 */
bool queue_work(struct workqueue_struct *wq,struct work_struct *work)
{return queue_work_on(WORK_CPU_UNBOUND, wq, work);
}/* 延迟工作队列绑定 */
bool queue_delayed_work(struct workqueue_struct *wq,struct delayed_work *dwork,unsigned long delay)
{return queue_delayed_work_on(WORK_CPU_UNBOUND, wq, dwork, delay);
}
参数含义
wq工作队列
work工作项
delay延时的执行的时钟节拍(非时间)
返回成功返回true

例子:

struct work_struct work;
struct workqueue_struct *pwrokqueue;void work_handle(struct work_struct *pw)
{
/* todo */
}INIT_WORK(work, work_handle);
pworkqueue = create_singlethread_workqueue("wq0");
queue_work(pwrokqueue, &work);	/* 绑定自定义工作队列 */

  • 释放工作队列
void destroy_workqueue(struct workqueue_struct *wq)
参数含义
wq工作队列
返回

3)触发调度

  • 触发非延迟工作项
bool schedule_work(struct work_struct *work)

  • 触发延迟工作项
bool schedule_delayed_work(struct delayed_work *dwork,unsigned long delay)
参数含义
dwork工作项
delay延迟时钟节拍数
返回成功返回true
	gq0{compatible = "gq0";gpios = <&gpio1, 10, GPIO_ACTIVE_LOW>;pinctrl-names = "default";pinctrl-0 = <&spi1_cs0_gpio>;		/* gpio模式 */interrupt-parent = <&gpio1>;	interrupts = <10, IRQ_TYPE_EDGE_BOTH>;	/* 上升沿和下降沿触发 */status = "okay";};spi1_cs0_gpio: spi1_cs0_gpio {rockchip,pins =<1 10 RK_FUNC_GPIO &pcfg_pull_none>,};

2. 中断驱动编写

  以gpio为例,编写一个gpio触发的中断驱动,并获取gpio状态值。使用的是GPIO1_B2端口。

在这里插入图片描述

2.1 实现方式

  • GPIO上升沿和下降沿触发中断
  • read函数通过等待队列挂起应用进程
  • 中断后触发同步信号唤醒进程读取IO状态

2.2 添加设备树

  GPIO1_B2引脚是复用引脚,可以复用为sp1的片选引脚(SP1_CSn0)。原设备树文件已添加 SPI1_CSn0的pin节点描述,在其他后增加GPIO属性描述。同时增加一个“gpioirq”的驱动节点信息。

  • pin设备树
/* 在rk3399.dtsi 中添加 */
spi1 {......spi1_cs0: spi1-cs0 {rockchip,pins =<1 10 RK_FUNC_2 &pcfg_pull_up>;};spi1_cs0_gpio: spi1_cs0_gpio {						/* 添加GPIO pin描述 */rockchip,pins =<1 10 RK_FUNC_GPIO &pcfg_pull_none>,};......}

  • 驱动节点设备树
/* 在rk3399-firefly-port.dtsi 中添加 */gq0{compatible = "gq0";gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;pinctrl-names = "default";pinctrl-0 = <&spi1_cs0_gpio>;			/* gpio模式 */interrupt-parent = <&gpio1>;	interrupts = <10 IRQ_TYPE_EDGE_BOTH>;	/* 上升沿和下降沿触发 */status = "okay";};

注:
设备数下的中断类型描述,位于“irq.h”中,与前面1.3.3节描述的中断类型值是一致的,只是名称不一样。
enum {
IRQ_TYPE_NONE = 0x00000000,
IRQ_TYPE_EDGE_RISING = 0x00000001,
IRQ_TYPE_EDGE_FALLING = 0x00000002,
IRQ_TYPE_EDGE_BOTH = (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING),
IRQ_TYPE_LEVEL_HIGH = 0x00000004,
IRQ_TYPE_LEVEL_LOW = 0x00000008,
IRQ_TYPE_LEVEL_MASK = (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH),
IRQ_TYPE_SENSE_MASK = 0x0000000f,
IRQ_TYPE_DEFAULT = IRQ_TYPE_SENSE_MASK,

};


2.3 设备数据结构

struct gpioirq_dev
{struct 	cdev 	dev;		/* 字符驱动 */dev_t			dev_id;		/* 设备ID */struct class 	*dev_class;	/* 设备类 */int 			gpio;		/* GPIO序号 */int				irq;		/* 中断序号 */wait_queue_head_t r_queue;	/* 等待队列 */bool			r_en;		/* 可读标识 */struct fasync_struct *r_sync;/* 内核通知应用信号 */
};

2.4 中断函数

static irqreturn_t gq0_irq_handle(int irq, void *dev_id)
{struct gpioirq_dev *p;p = (struct gpioirq_dev *)dev_id;p->r_en = true;wake_up_interruptible(&(p->r_queue));	/* 唤醒休眠进程 *//* 通知应用进程数据可读* SIGIO:信号类型* POLL_IN:普通数据可读*/kill_fasync(&p->r_sync, SIGIO, POLL_IN);	return IRQ_HANDLED;
}

2.5 状态读取函数

static ssize_t gq0_read(struct file *pfile, char __user *buf, size_t size, loff_t * offset) 
{ int ret = 0;struct gpioirq_dev *p;char level = 0;p = pfile->private_data;wait_event_interruptible(p->r_queue, p->r_en);	/* 进程休眠,等待中断 */level = gpio_get_value(p->gpio);ret = copy_to_user(buf, &level, 1);return ret; 
}

2.6 中断注册

static int gq0_probe(struct platform_device *pdev)  
{     struct device *dev; int ret = -1;dev_t	id = 0;struct device_node *nd;nd = pdev->dev.of_node;			/* 设备树节点 */if(nd == NULL){printk("get node faileed\n");return -1;}gq0.gpio = of_get_named_gpio(nd, "gpios", 0);	/* 获取GPIO */if(gq0.gpio < 0){printk("get gpio failed\n");return -1;}if (!gpio_is_valid(gq0.gpio)) {printk("gpio [%d] is invalid\n", gq0.gpio);return -1;}ret = gpio_request(gq0.gpio, "gq0");		/* 申请GPIO */if(ret < 0){printk("gpio request failed\n");return ret;}ret = gpio_direction_input(gq0.gpio);	//gq0.irq = gpio_to_irq(gq0.gpio);	/* 中断号映射 */gq0.irq = irq_of_parse_and_map(nd, 0);ret = request_irq(gq0.irq, gq0_irq_handle, gq0.irq_mode, "gq0", &gq0);/* 注册中断 */if(ret<0){printk("request gq0 irq failed\n");free_irq(gq0.irq, &gq0);gpio_free(gq0.gpio);return ret;}......
}

2.7 platform 驱动

static struct of_device_id of_gq0_ids[] = {{.compatible = "gpioirq"},	/* 与节点设备树“compatible ”属性一致 */{ }   };static struct platform_driver gq0_driver = { .driver   = { .owner    = THIS_MODULE, .name     = DEV_NAME, .of_match_table = of_gq0_ids,}, .probe 	  = gq0_probe, .remove   = gq0_remove, 
};module_platform_driver(gq0_driver); /* platform 驱动注册和注销 */

3. 源码

【1】https://github.com/Prry/rk3399


4. 参考

【1】https://blog.csdn.net/yhb1047818384/article/details/63687126

【2】http://www.wowotech.net/irq_subsystem/tasklet.html

【3】https://www.ibm.com/developerworks/cn/linux/l-cn-cncrrc-mngd-wkq/


http://chatgpt.dhexx.cn/article/mNxY1Uf1.shtml

相关文章

基于RK3399分析Linux系统下的CPU时钟管理 - 第3篇

1. 时钟系统结构 rockchip的时钟系统代码位于drivers/clk/rockchip&#xff0c;目录整体结构如下&#xff1a; ├── rockchip │ ├── clk.c---------------时钟系统注册 │ ├── clk-cpu.c-----------CPU调频 │ ├── clk-ddr.c-----------DDR调频 │ ├──…

基于RK3399+PID的手持稳定云台的设计与实现

手持稳定云台的主要作用是将外界环境因数引起的相机姿态变化进行隔离。如因操作者运动造成的机体震动、风阻力矩等&#xff0c;为了确保工作中相机的视轴始终保持期望的姿态不动。云台相机要拍摄出高质量的影像最重要的就是保证相机的视轴相对目标保持稳定。因此在相机拍摄的过…

RK3399学习

RK3399学习 韦东山rk3399&#xff1a;http://dev.t-firefly.com/forum-460-1.html firefly官网教程&#xff1a;http://wiki.t-firefly.com/zh_CN/Firefly-RK3399/started.html firefly官网3399资料&#xff1a;http://dev.t-firefly.com/forum-263-1.html 100ask 3399-pc教…

RK3399平台开发系列讲解(内核入门篇)1.53、platform平台设备

🚀返回专栏总目录 文章目录 一、设备配置-非设备树1.1、资源1.2、平台数据1.3、声明平台设备二、设备配置 - DTS沉淀、分享、成长,让自己和他人都能有所收获!😄 📢平台设备在内核中表示为struct platform_device的实例。 有两种方法可以把有关设备所需的资源(IRQ、DMA…

RK3399 Android7.1 编译

RK3399 Android7.1 编译 文章目录 RK3399 Android7.1 编译前言设置 Linux 编译环境安装 JDK可选- 更新默认的 Java 版本 安装所需的程序包(Ubuntu 14.04) 下载 Android SDK 前言 RK官网编译 Android搭建编译环境 设置 Linux 编译环境 使用的环境Linux 16.0.4 安装 JDK 如…

基于RK3399+5G的医用视频终端设计

当前在各种先进的信息通信技术的驱动下&#xff0c;医疗行业已呈现出信息化、移动化、智能化的发展趋势。特别是 5G 通信技术的落地应用推动了智慧医疗行业的 蓬勃发展&#xff0c;涌现出大量基于 5G 技术的医疗健康应用与服务&#xff0c;进一步融合了 5G 、 物联网与大数据…

RK3399平台开发系列讲解(PCI/PCI-E)PCIE相关配置说明

🚀返回专栏总目录 文章目录 一、DTS 配置二、menuconfig 配置三、cmdline 配置沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 本篇将介绍在使用 RK3399 平台 PCIE 时候的配置。 一、DTS 配置 ep-gpios = <&gpio3 13 GPIO_ACTIVE_HIGH>; 此项是设置 PCIe…

RK3399快速上手 | 02-rockchip rk3399 linux sdk的使用(编译内核、编译uboot)

更新时间更新内容2022-09-15增加内核编译方法2022-10-21增加uboot编译方法和sdk开发版配置链路分析一、sdk区别 瑞芯微提供了两套sdk,一套是通过官方git仓库释放,适合于项目使用,另一套是通过github释放,适合于爱好者。 本文中使用从瑞芯微官方释放的正式linux sdk 2.7版…

RK3399平台开发系列讲解(中断篇)中断控制器驱动初始化

🚀返回专栏总目录 文章目录 一、设备树源文件1.1、gic控制器节点1.2、timer节点二、中断控制器匹配表三、中断控制器初始化3.1、函数of_irq_init3.2、函数gicv3_of_init3.3、函数gic_init_bases沉淀、分享、成长,让自己和他人都能有所收获!😄 一、设备树源文件 ARM64架构…

RK3399平台开发系列讲解(内存篇)15.34、 Linux 进程内存布局

🚀返回专栏总目录 文章目录 一、抽象内存布局二、32位机器 Linux 进程内存布局三、64位机器 Linux 进程内存布局沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 我们一起来看下进程内部的虚拟内存布局,或者说单一进程是如何安排自己的各种数据的。 一、抽象内存布…

RK3399平台开发系列讲解(内核调试篇)2.50、嵌入式产品启动速度优化

平台内核版本安卓版本RK3399Linux4.4Android7.1🚀返回专栏总目录 沉淀、分享、成长,让自己和他人都能有所收获!😄 📢启动速度是嵌入式产品一个重要的性能指标,更快的启动速度会让客户有更好的使用体验,在某些方面还会节省能耗,因为可以直接关机而不需要休眠。 启动速…

钉钉F1 RK3399 咸鱼80元板子使用记录

1.简单介绍 12V电源&#xff0c;建议2A&#xff0c; 默认插电不开机&#xff0c;有大佬找到金属罩下的焊盘&#xff0c;短接可上电开机。 在usb旁边的旁边有个端子接口&#xff0c;短接就可以开机&#xff0c;建议找个一样大的接口接个开关&#xff0c;到目前为止还未测试需要…

RK3399平台开发系列讲解(网络篇)7.38、网卡驱动程序数据结构

平台内核版本安卓版本RK3399Linux4.4Android7.1🚀返回专栏总目录 文章目录 一、套接字缓冲区结构:sk_buff二、网络接口结构:net_device沉淀、分享、成长,让自己和他人都能有所收获!😄 📢处理网卡设备时需要使用两种数据结构。 struct sk_buff结构在include/linux/skb…

RK3399平台开发系列讲解(中断篇)中断控制器(Generic Interrupt Controller)

🚀返回专栏总目录 文章目录 一、GIC硬件的实现形态二、主要的功能块三、中断类型四、中断状态沉淀、分享、成长,让自己和他人都能有所收获!😄 📢外围设备不是把中断请求直接发给处理器,而是发给中断控制器,由中断控制器转发给处理器。ARM公司提供了一种标准的中断控制…

RK3399平台开发系列讲解(DMA篇)深刻理解DMA

🚀返回专栏总目录 文章目录 一、什么是DMA二、DMA的产生:背景三、理解 DMA:协处理器沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将带领大家深刻理解DMA。 一、什么是DMA DMA (Direct Memory Access) is used to copy data directly between devices and R…

RK3399——裸机大全

CSDN仅用于增加百度收录权重&#xff0c;排版未优化&#xff0c;日常不维护。请访问&#xff1a;www.hceng.cn 查看、评论。 本博文对应地址: https://hceng.cn/2018/08/16/RK3399——裸机大全/#more 以64位的RK3399为例&#xff0c;实现裸机的启动、中断、串口(printf移植)、…

RK3399平台开发系列讲解(CPU篇)CPUFreq 中央处理器频率调节技术

🚀返回专栏总目录 文章目录 一、CPUFreq组成二、设备树配置三、原理沉淀、分享、成长,让自己和他人都能有所收获!😄 📢中央处理器频率调节(Central Processing Unit frequency,CPUFreq)技术可以降低ARM芯片的功耗,例如在系统对任务压力较小时,通过调整处理器工作频…

RK3399中文简介

1.概述 RK3399是一种低功耗、高性能的处理器&#xff0c;可用于计算、个人移动互联网设备和其他智能设备应用程序。基于大。小架构&#xff0c;它集成了双核心Cortex-A72和四核Cortex-A53与单独的NEON协处理器。 许多嵌入式功能强大的硬件引擎为高端应用程序提供了优化的性能。…

RK3399平台入门到精通系列讲解 - 总目录

总目录 欢迎大家来到内核笔记的《RK3399平台开发入门到精通系列讲解》&#xff0c;开始前博主先列出RK3399平台学习的大纲&#xff0c;同时这也可以作为大家学习RK3399内核与安卓框架的参考。下面蓝字都是传送门&#xff0c;点击进入即可&#xff1a; 更新说明 此系列已更新…

网络渗透测试实验四 CTF实践

实验目的&#xff1a;通过对目标靶机的渗透过程&#xff0c;了解CTF竞赛模式&#xff0c;理解CTF涵盖的知识范围&#xff0c;如MISC、PPC、WEB等&#xff0c;通过实践&#xff0c;加强团队协作能力&#xff0c;掌握初步CTF实战能力及信息收集能力。熟悉网络扫描、探测HTTP web服…