Uart Subsystem

Reference https://www.kernel.org/doc/Documentation/serial/driver Introduction 波特率115200,bps每秒传输的bit数。 每一位1/115200秒,传输1byte需要10位(start, data, stop),那么每秒能传11520byte。 115200,8n1。8:data,n:校验位不用,1:停止位。 TTY体系中设备节点的差别 不关心终端是真实的还是虚拟的,都可以通过/dev/tty找到当前终端。 /dev/console 内核的打印信息可以通过cmdline来选择打印到哪个设备。 console=ttyS0 console=tty console=ttyS0时,/dev/console就是ttyS0 console=ttyN时,/dev/console就是/dev/ttyN console=tty时,/dev/console就是前台程序的虚拟终端 console=tty0时,/dev/console就是前台程序的虚拟终端 console有多个取值时,使用最后一个取值来判断。 /dev/tty 和/dev/tty0区别 /dev/tty表示当前进程的控制终端,也就是当前进程与用户交互的终端。 /dev/tty0则是当前所使用虚拟终端的一个别名 Linux串口应用编程 https://digilander.libero.it/robang/rubrica/serial.htm struct termios options; open("/dev/ttyS1", O_RDWR | O_NOCTTY | O_NDELAY)// O_NOCTTY: 不用作控制终端 O_NDELAY: 使I/O变成非阻塞模式 fcntl(fd, F_SETFL, 0): //读数据时,没有数据则阻塞等待 fcntl(fd, F_SETFL, FNDELAY): //读数据时不等待,没有数据就返回0 /* c_cflag: Control Options */ options.c_cflag |= (CLOCAL | CREAD); // 必须打开 Enable the receiver and set local mode options.c_cflag &= ~CSIZE; /* Mask the character size bits */ options....

2023-05-15 · 3 min

GPIO Subsystem

https://zhuanlan.zhihu.com/p/41942876 push pull推挽输出 推挽输出的最大特点是可以真正能真正的输出高电平和低电平,在两种电平下都具有驱动能力。 open drain开漏输出 open source开集输出 这两种输出的原理和特性基本是类似的,区别在于一个是使用MOS管,其中的"漏"指的就是MOS管的漏极;另一个使用三极管,其中的"集"指的就是MOS三极管的集电极。这两者其实都是和推挽输出相对应的输出模式,由于使用MOS管的情况较多,很多时候就用"开漏输出"这个词代替了开漏输出和开集输出。 开漏、开集输出最主要的特性就是高电平没有驱动能力,需要借助外部上拉电阻才能真正输出高电平。 Kernel doc: General Purpose Input/Output (GPIO) GPIO Driver Interface Controller Drivers: gpio_chip struct gpio_chip 抽象gpio controller。 gpiochip_add_data() or devm_gpiochip_add_data()接口用来注册gpio controller。gpiochip_remove()释放gpio controller。 gpiochip_is_request()在gpio controller driver中用于检测某个gpio是否被其他chip占用,没占用返回NULL,占用返回request时传入的string。 GPIO electrical configuration gpio_chip的.set_config回调用于设置: Debouncing Single-ended modes (open drain/open source) Pull up and pull down resistor enablement 这些属性可以在dts中指定,include/dt-bindings/gpio/gpio.h GPIO_PUSH_PULL GPIO_LINE_OPEN_SOURCE GPIO_OPEN_DRAIN … 可以设置为gpiochip_generic_config()会调用到pinctrl_gpio_set_config()->ops->pin_config_set GPIO drivers providing IRQs gpiod_to_irq(); // 传入gpio_desc,返回gpio的irq number(软件映射的,不是irq hw id) gpio_chip_hwgpio(); gc->to_irq(); rts_gpio_to_irq(); irq_linear_revmap(); Cascaded GPIO irqchips CHAINED CASCADED GPIO IRQCHIPS:挺多soc上是这种做法,打开CONFIG_GPIOLIB_IRQCHIP设置girq->parent_handler。gpio controller注册过程中通过irq_set_chained_handler设置中断处理函数,因此在中断处理函数中需要chained_irq_enter,chained_irq_exit。相当于级联中断处理器的做法。 static irqreturn_t foo_gpio_irq(int irq, void *data) /// 中断处理函数 chained_irq_enter(....

2023-05-12 · 2 min

CMA&DMA

Reserved-memory 参考:http://www.wowotech.net/memory_management/cma.html https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841683/Linux+Reserved+Memory?view=blog /Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt 定义了 no-map 属性的,不会自动映射到虚拟地址,需要自行在 driver 中映射。 // dts reserved: buffer@0x38000000 { no-map; reg = <0x38000000 0x08000000>; }; /* Get reserved memory region from Device-tree */ np = of_parse_phandle(dev->of_node, "memory-region", 0); rc = of_address_to_resource(np, 0, &r); lp->paddr = r.start; lp->vaddr = memremap(r.start, resource_size(&r), MEMREMAP_WB); 定义"shared-dma-pool" 就可以创建 DMA memory pool,使用 DMA engine API 了。of_reserved_mem_device_init 中会帮我们创建映射。(DMA 在 of_reserved_mem_device_init 阶段会进行 memremap 同上,这个 remap 不是直接映射的方式)。 // dts reserved: buffer@0 { compatible = "shared-dma-pool"; no-map; reg = <0x0 0x70000000 0x0 0x10000000>; }; /* Initialize reserved memory resources */ rc = of_reserved_mem_device_init(dev); /* Allocate memory */ dma_set_coherent_mask(dev, 0xFFFFFFFF); lp->vaddr = dma_alloc_coherent(dev, ALLOC_SIZE, &lp->paddr, GFP_KERNEL); log:...

2023-05-10 · 4 min

Pinctrl Subsystem

/sys/kernel/debug/pinctrl 其他驱动调用pinctrl子系统: #include <linux/pinctrl/consumer.h> static struct pinctrl *xxx_pinctrl; struct pinctrl_state *default_state = NULL; // dts pinctrl-0 = <&state1> pinctrl-1 = <&state2> 1. /* 获取pin control state holder 的句柄 */ pinctrl = devm_pinctrl_get(dev); 2. /* 得到名字为state1和state2对应的pin state */ **struct** pinctrl_state * turnon_tes = pinctrl_lookup_state(pinctrl, "state1"); **struct** pinctrl_state * turnoff_tes = pinctrl_lookup_state(pinctrl, "state2"); 3. pinctrl_select_state(pinctrl, turnon_tes)。 devm_pinctrl_get(struct device *dev) //返回一个pinctrl句柄 pinctrl_get(struct device *dev) find_pinctrl(struct device *dev) // 查看是否device core已经创建了该pinctrl句柄 create_pinctrl(struct device *dev, struct pinctrl_dev *pctldev) pinctrl_dt_to_map(struct pinctrl *p, struct pinctrl_dev *pctldev) //从设备树中获取信息保存到pinctrl_map结构体中 for (state = 0; ; state++) { propname = kasprintf(GFP_KERNEL, "pinctrl-%d", state); //查找pinctrl-0,1,2属性 //size保存了pinctrl-0中phandle的个数,比如有的节点pinctrl-0 = <&x1, &x2> prop = of_find_property(np, propname, &size); list = prop->value; //list保存了phandle列表 //保存pinctrl-names index为state的name,比如pinctrl-names = "default" ret = of_property_read_string_index(np, "pinctrl-names", state, &statename); if (ret < 0) // 如果没有定义pinctrl-names属性,那么我们将pinctrl-0 pinctrl-1 pinctrl-2……中的那个ID取出来作为state name statename = prop->name + strlen("pinctrl-"); for (config = 0; config < size; config++) { phandle = be32_to_cpup(list++); np_config = of_find_node_by_phandle(phandle); // 找到pinctrl-x=<&x1, &x2>中的config节点 dt_to_map_one_config(p, pctldev, statename, np_config); np_pctldev = of_node_get(np_config); np_pctldev = of_get_next_parent(np_pctldev); // 找到pinctrl controller节点 pctldev = get_pinctrl_dev_from_of_node(np_pctldev); //找到pinctrl_dev //调用底层的callback函数处理pin configuration node。 ops->dt_node_to_map(pctldev, np_config, &map, &num_maps); ....

2023-05-10 · 2 min

GDB

Stepping step(s)/stepi(si) + <n>: 执行n行c/assembly 代码, 会跳进函数。 next(n)/nexti(ni) + <n>: 执行n行c/assembly 代码,不会跳进函数。 Running run/r: run code 直到遇到breakpoint。程序跑完接着run会restart程序。 continue/c: 继续执行。 finish: run code直到当前函数return。 advance <location>: run code直到指令到达。和设置breakpoint然后 Breakpoints break <location>: 设置断点,location可以是内存地址"*0x7c00"或者名称"mon_backtrace", "monitor.c:71" break <location> if <condition>: 只有condition满足的时候才会break。 cond <number> <condition>: 给某个断点增加condition。 Watchpoints watch <expression>: 表达式值发生改变时,会停止执行指令。 watch -l <address>: 内存地址的值发生改变时,会停止执行指令。 rwatch [-l] <expression>: 表达式值被read后,会停止执行指令。 Examining x: 打印内存的原始数据,x/x十六进制,x/i汇编。 print/p: 打印C expression。 x/s <内存地址>或p (char*)<内存地址>: 打印字符串。 p $rax: 打印某个寄存器值。 info registers: 打印所有寄存器。 info frame: 打印当前栈帧。...

2023-05-08 · 2 min

Time Subsystem

CONFIG_GENERIC_CLOCKEVENTS:新的时间子系统 以下选项三选一: CONFIG_HZ_PERIODIC:无论何时,都启用用周期性的tick,即便是在系统idle的时候。 CONFIG_NO_HZ_IDLE:在系统idle的时候,停掉周期性tick。会同时enable NO_HZ_COMMON。 CONFIG_NO_HZ_FULL:即便在非idle的状态下,也就是说cpu上还运行在task,也可能会停掉tick。会同时enable NO_HZ_COMMON。 CONFIG_HIGH_RES_TIMERS:高精度timer。 如果配置了高精度timer,或者配置了NO_HZ_COMMON的选项,那么一定需要配置CONFIG_TICK_ONESHOT,表示系统支持支持one-shot类型的tick device。 sysfs接口 cd /sys/bus/clocksource/devices/clocksource0 cat current_clocksource: 查看当前的clocksource cat available_clocksource: 查看可用的clocksource echo xxx > current_clocksource: 设置clocksource cd /sys/bus/clockevents/devices/clockevent0 cat current_device: 查看当前的clockevent Clocksource Clockevent

2023-05-08 · 1 min

Interrupt Subsystem

IRQ domain http://www.wowotech.net/linux_kenrel/irq-domain.html 1. 向系统注册irq domain interrupt controller初始化的过程中,注册irq domain irq_domain_add_linear(struct device_node *of_node, unsigned int size, const struct irq_domain_ops *ops, void *host_data) 2. 为irq domain创建映射 在各个硬件外设的驱动初始化过程中,创建HW interrupt ID和IRQ number的映射关系。 方法1:irq_create_mapping(struct irq_domain *host, irq_hw_number_t hwirq); 比如drivers/clocksource/timer-riscv.c中irq_create_mapping(domain, RV_IRQ_TIMER);直接将hw id(RV_IRQ_TIMER)传入, 创建hw id和irq number的映射。 irq_create_mapping(domain, hwirq); irq_create_mapping_affinity(); irq_domain_alloc_descs(); // 创建hw id和irq number的映射 irq_domain_associate(); domain->ops->map(); //调用到interrupt controller的map函数 方法2:irq_of_parse_and_map. 需要在设备树中指定hw id。 比如drivers/irqchip/irq-realtek-plic.c中irq_of_parse_and_map irq_of_parse_and_map(struct device_node *dev, int index); of_irq_parse_one(dev, index, &oirq); // 解析设备树 irq_create_of_mapping(&oirq); irq_create_fwspec_mapping(); irq_create_mapping(domain, hwirq); // 最终还是调用到irq_create_mapping 方法3:外设driver中直接platform_get_irq...

2023-05-08 · 3 min

uCore_Chapter1 应用程序与基本执行环境

Notes make build # 仅编译 make run # 编译+运行qemu make run LOG=trace # 其他选项可以看Makefile make clean # rm build/ make debug # 编译+运行gdb调试 每次在make run之前,尽量先执行make clean以删除缓存,特别是在切换ch分支之后。 退出 qemu 的方法 如果是正常推出,uCore 会自动关闭 qemu,但如果 os 跑飞了,我们不能通过 Ctrl + C 来推出。此时可以先按下 Ctrl+A ,再按下 X 来退出 Qemu。 ch1 流程 // entry.S _entry la sp, boot_stack_top //设置堆栈 call main // main.c clean_bss(); printf("hello wrold!\n"); consputc(); console_putchar(int c); sbi_call(SBI_CONSOLE_PUTCHAR, c, 0, 0); 怎么用gdb调试?`file kernel`然后? Makefile流程分析 根据make run 打印的信息:...

2023-04-25 · 1 min

uCore_Chapter2 批处理系统

make -C user clean # 在os目录,相当于cd user;make clean;cd .. make clean # 或者在user目录 git checkout ch2 make user BASE=1 CHAPTER=2 make run make test BASE=1 # make test 会完成 make user 和 make run 两个步骤(自动设置 CHAPTER) 流程 main(); printf("hello wrold!\n"); trap_init(); // 设置中断/异常处理地址 w_stvec((uint64)uservec & ~0x3); //把uservec地址传入,uservec在trampoline.S中定义 asm volatile("csrw stvec, %0" : : "r"(x)); // 设置stvec CSR loader_init(); run_next_app(); load_app(); usertrapret(trapframe, (uint64)boot_stack_top); w_sepc(trapframe->epc); r_sstatus(); w_sstatus(); userret(); sret // 返回到sepc中的值,0x80400000第一个app 应用程序系统调用ecall进入内核异常处理过程 // 进入应用程序 exit(MAGIC); syscall(SYS_exit, code); __syscall1(n, long(1234)); __asm_syscall("r"(a7), "0"(a0)); ecall //通过ecall 进入uservec uservec usertrap(); // ld t0, 16(a0) jr t0 r_scause(); csrr %0, scause // 应用层调用了ecall指令,所以scause自动被设置为8 syscall(); //在uservec中应用层传入eid到寄存器a7,这里读a7来判断是什么system call id = trapframe->a7; usertrapret(); // 这里回到第九行一样,循环,执行第二个应用程序 分析下uservec,注意:这里只是把stvec设置为uservec地址,并不会执行uservec下的代码,要等U mode的中断/异常到来时才会从uservec开始执行。...

2023-04-25 · 1 min

U-Boot启动流程

Uboot编译流程 https://blog.csdn.net/ooonebook/article/details/53000893 编译生成的文件: 具体可以参考Uboot Makefile u-boot Makefile u-boot.cfg: $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf $(@) cfg: u-boot.cfg prepare2: prepare3 outputmakefile cfg prepare1: prepare2$(version_h) $(timestamp_h) $(dt_h) $(env_h) include/config/auto.conf archprepare: prepare1 scripts_basic prepare0: archprepare prepare:prepare0 scripts: scripts_basic scripts_dtc include/config/auto.conf $(u-boot-dirs): prepare scripts $(sort $(u-boot-init) $(u-boot-main)): $(u-boot-dirs) u-boot-init := $(head-y) u-boot-main := $(libs-y) u-boot-keep-syms-lto := keep-syms-lto.o u-boot.lds: $(LDSCRIPT) prepare u-boot: $(u-boot-init) $(u-boot-main) $(u-boot-keep-syms-lto) u-boot.lds u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb u-boot-nodtb.bin: u-boot dts/dt.dtb: u-boot u-boot.srec: u-boot u-boot.bin: u-boot-dtb.bin u-boot.sym: u-boot System....

2023-04-19 · 4 min