linux下的几种时钟和定时器机制

linux下的几种时钟和定时器机制,第1张

1. RTC(Real Time Clock)所有PC都有RTC. 它和CPU和其他芯片独立。它在电脑关机之后还可以正常运行。RTC可以在IRQ8上产生周期性中断. 频率在2Hz--8192HZ.Linux只是把RTC用来获取时间和日期. 当然它允许进程通过对/dev/rtc设备来对它进行编程。Kernel通过0x70和0x71 I/O端口来访问RTC。 2. TSC(Time Stamp Counter)80x86上的微处理器都有CLK输入针脚. 从奔腾系列开始. 微处理器支持一个计数器. 每当一个时钟信号来的时候. 计数器加1. 可以通过汇编指令rdtsc来得到计数器的值。通过calibrate_tsc可以获得CPU的频率. 它是通过计算大约5毫秒里tsc寄存器里面的增加值来确认的。或者可以通过cat /proc/cpuinfo来获取cpu频率。tsc可以提供比PIT更精确的时间度量。 3. PIT(Programmable internval timer)除了RTC和TSC. IBM兼容机提供了PIT。PIT类似微波炉的闹钟机制. 当时间到的时候. 提供铃声. PIT不是产生铃声. 而是产生一种特殊中断. 叫定时器中断或者时钟中断。它用来告诉内核一个间隔过去了。这个时间间隔也叫做一个滴答数。可以通过编译内核是选择内核频率来确定。如内核频率设为1000HZ,则时间间隔或滴答为1/1000=1微秒。滴答月短. 定时精度更高. 但是用户模式的时间更短. 也就是说用户模式下程序执行会越慢。滴答的长度以纳秒形式存在tick_nsec变量里面。PIT通过8254的0x40--0x43端口来访问。它产生中断号为IRQ 0.下面是关于pIT里面的一些宏定义:HZ:每秒中断数。CLOCK_TICK_RATE:值是1,193,182. 它是8254芯片内部振荡器频率。LATCH:代表CLOCK_TICK_RATE和HZ的比率. 被用来编程PIT。setup_pit_timer()如下:spin_lock_irqsave(&i8253_lock, flags)outb_p(0x34,0x43)udelay(10)outb_p(LATCH &0xff, 0x40)udelay(10)outb (LATCH >>8, 0x40)spin_unlock_irqrestore(&i8253_lock, flags)  4. CPU Local Timer最近的80x86架构的微处理器上的local apic提供了cpu local timer.他和pit区别在于它提供了one-shot和periodic中断。它可以使中断发送到特定cpu。one-shot中断常用在实时系统里面。

static int __init ohci_hcd_mod_init(void)

{

platform_driver_register(&ohci_hcd_s3c2410_driver)

}

其实真正注册的是ohci_hcd_s3c2410_driver这个驱动。那我们来看一下这个结构体的具体值。

static struct platform_driver ohci_hcd_s3c2410_driver= {

.probe = ohci_hcd_s3c2410_drv_probe,

.remove = ohci_hcd_s3c2410_drv_remove,

.shutdown = usb_hcd_platform_shutdown,

.driver = {

.owner = THIS_MODULE,

.name = "s3c2410-ohci",

},

}

那我们一一来看上述的每一个函数的实现。

2.1 hcd 探测

函数很简单其实现功能的是usb_hcd_s3c2410_probe函数。

static int ohci_hcd_s3c2410_drv_probe(structplatform_device *pdev)

{

returnusb_hcd_s3c2410_probe(&ohci_s3c2410_hc_driver, pdev)

}

ohci_s3c2410_hc_driver提供了对于ohci的 *** 作集。对于这些函数在后面的学习中去看,在此不加扩展。我们将下面的函数剔除枝叶留其主干。

static int usb_hcd_s3c2410_probe (const structhc_driver *driver,

struct platform_device *dev)

{

structusb_hcd *hcd = NULL

int retval

#if !defined(CONFIG_ARCH_2410)

usb_host_clk_en() --使能clk

#endif

s3c2410_usb_set_power(dev->dev.platform_data,1, 1)

s3c2410_usb_set_power(dev->dev.platform_data,2, 1)

hcd =usb_create_hcd(driver, &dev->dev, "s3c24xx") --创建一个hcd

hcd->rsrc_start= dev->resource[0].start--获取物理地址

hcd->rsrc_len = dev->resource[0].end -dev->resource[0].start + 1

request_mem_region(hcd->rsrc_start,hcd->rsrc_len, hcd_name)

clk =clk_get(&dev->dev, "usb-host")

s3c2410_start_hc(dev,hcd)

hcd->regs= ioremap(hcd->rsrc_start, hcd->rsrc_len)

ohci_hcd_init(hcd_to_ohci(hcd))

retval = usb_add_hcd(hcd,dev->resource[1].start, IRQF_DISABLED)

return 0

}

对于usb的电源管理,我们暂时不看,不看不代表不重要,电源管理是很重要的。

那依次来看上面的函数。usb_create_hcd创建和初始化一个hcd结构体。

s3c2410_start_hc启动hc。这里有一个很奇怪的结构体就是struct s3c2410_hcd_info,在s3c6410中并没有看到该结构体的赋值。也许有人对此很困惑,该结构体做什么用的。那我们来看该结构体的真正面目。

struct s3c2410_hcd_info {

structusb_hcd *hcd --保存该hcd_info所属的hcd

structs3c2410_hcd_portport[2]--两个端口。

void(*power_control)(intport, int to)--电源控制

void(*enable_oc)(structs3c2410_hcd_info *, int on)

void(*report_oc)(structs3c2410_hcd_info *, int ports)

}

在usb-host.txt中对其功能进行了说明,就是一对函数,使能过流检测和控制端口电源状态。

power_control:使能或禁止端口电源

enable_oc:使能或禁止端口过流检测

report_oc:当端口存在过流,则会调用该函数。

static void s3c2410_start_hc(structplatform_device *dev, struct usb_hcd *hcd)

{

structs3c2410_hcd_info *info = dev->dev.platform_data

clk_enable(clk)

if (info !=NULL) { --在s3c6410中该info为空。

info->hcd = hcd

info->report_oc= s3c2410_hcd_oc

if(info->enable_oc != NULL) {

(info->enable_oc)(info,1)

}

}

}

初始化ohci_hcd

static void ohci_hcd_init(structohci_hcd *ohci)

{

ohci->next_statechange= jiffies

spin_lock_init(&ohci->lock)

INIT_LIST_HEAD(&ohci->pending)

}

初始化并注册usb_hcd

完成通用hcd的初始化和注册,在这里同时完成中断的申请和注册。

int usb_add_hcd(struct usb_hcd *hcd,unsigned intirqnum, unsigned long irqflags)

{

int retval

structusb_device *rhdev

hcd->authorized_default= hcd->wireless? 0 : 1 --判断是否为无线

set_bit(HCD_FLAG_HW_ACCESSIBLE,&hcd->flags)--设置HW_ACCESSIBLE旗标

if ((retval =hcd_buffer_create(hcd)) != 0) { --开辟hcd的缓冲区

returnretval

}

if ((retval =usb_register_bus(&hcd->self)) <0)

gotoerr_register_bus

if ((rhdev =usb_alloc_dev(NULL, &hcd->self, 0)) == NULL) {

retval= -ENOMEM

gotoerr_allocate_root_hub

}

rhdev->speed= (hcd->driver->flags &HCD_USB2) ? USB_SPEED_HIGH :USB_SPEED_FULL--指定根hub的speed

hcd->self.root_hub= rhdev

device_init_wakeup(&rhdev->dev,1)

if(hcd->driver->reset &&(retval = hcd->driver->reset(hcd))<0) {--为NULL

gotoerr_hcd_driver_setup

}

if(device_can_wakeup(hcd->self.controller)

&&device_can_wakeup(&hcd->self.root_hub->dev))

dev_dbg(hcd->self.controller,"supports USB remote wakeup\n")

if(hcd->driver->irq) { --中断处理

if(irqflags &IRQF_SHARED)

irqflags&= ~IRQF_DISABLED

snprintf(hcd->irq_descr,sizeof(hcd->irq_descr), "%s:usb%d",

hcd->driver->description,hcd->self.busnum)

request_irq(irqnum,&usb_hcd_irq, irqflags,hcd->irq_descr, hcd)--申请中断线

}

hcd->irq= irqnum

} else {

hcd->irq= -1;

}

hcd->driver->start(hcd) --调用start为 ohci_s3c2410_start

rhdev->bus_mA= min(500u, hcd->power_budget)

register_root_hub(hcd)); --注册root hub

retval =sysfs_create_group(&rhdev->dev.kobj, &usb_bus_attr_group)

if (retval<0) {

gotoerror_create_attr_group

}

if(hcd->uses_new_polling &&hcd->poll_rh)

usb_hcd_poll_rh_status(hcd)

returnretval

}

那一一来看上面的函数,学习内核就要有打破砂锅问到底的精神,唯有知道那背后的种种风光,才能领略那种种风采。闲话不说,继续!

记住下面结构体中flag的值。那就看这几个宏定义是什么意思。

#defineHCD_MEMORY 0x0001 --hc的寄存器使用memory映射

#defineHCD_LOCAL_MEM 0x0002 --hc使用local memory

#defineHCD_USB11 0x0010 --usb1.1

#defineHCD_USB20x0020 --usb2.0

static const struct hc_driver ohci_s3c2410_hc_driver=

{

.flags =HCD_USB11 | HCD_MEMORY,

};

为hcd分配缓冲池,当hc需要使用DMA内存分配器。

int hcd_buffer_create(struct usb_hcd *hcd)

{

charname[16]

int i, size

if(!hcd->self.controller->dma_mask &&

!(hcd->driver->flags &HCD_LOCAL_MEM))

return 0

--#define HCD_BUFFER_POOLS 4

我们查看pool_max其实是一个全局数组。如果需要开辟的缓冲区更大的话,直接采用分配page的函数。

static const size_tpool_max[HCD_BUFFER_POOLS] = {

32,128,512,PAGE_SIZE/ 2

}

for (i = 0i<HCD_BUFFER_POOLSi++) {

size = pool_max[i]

if(!size)

continue

snprintf(name,sizeof name, "buffer-%d", size)

hcd->pool[i] = dma_pool_create(name,hcd->self.controller,size, size, 0)

if(!hcd->pool [i]) {

hcd_buffer_destroy(hcd)

return-ENOMEM

}

}

return 0

}

dma_pool_create创建一个DMA池(生成一个dma_pool,并没有分配相应空间,真正分配物理内存将在dma_pool_alloc()总实现)。

下面的函数是usb_bus注册,对于该函数也许很难理解。不过参照网上http://www.sudu.cn/info/html/edu/20080425/301909.html的说明,估计会好理解很多。

每个主机控制器拥有一个USB系统,称为一个USB总线。USBD支持多个主机控制器,即多个USB总线。当每增加一个主机控制器时,会给他分配一个usb_bus结构。USBD动态安装和卸载主机驱动。主机驱动安装时,他的初始化函数一方面完成主机控制器硬件的设置和初始化工作,另一方面调用usb_alloc_bus和usb_register_bus来将自己注册到USBD中去,供USB子系统访问。

static int usb_register_bus(struct usb_bus *bus)

{

int result =-E2BIG

int busnum

mutex_lock(&usb_bus_list_lock)

busnum =find_next_zero_bit (busmap.busmap, USB_MAXBUS, 1)

--用busmap来存储主机驱动,一个bit位代表一个主机驱动

if (busnum >=USB_MAXBUS) {

return result

}

set_bit (busnum,busmap.busmap)

bus->busnum = busnum

bus->dev =device_create(usb_host_class, bus->controller, MKDEV(0, 0),bus,"usb_host%d", busnum)

--在usb_host类下创建一个usb_host设备。

list_add(&bus->bus_list, &usb_bus_list)

mutex_unlock(&usb_bus_list_lock)

usb_notify_add_bus(bus)

return 0

}

就是把串口波特率提上去,硬件环境呢,就是采用飞凌的TE2440-II(比较古老了,大家勿喷) *** 作系统是linux2.6.28,大家都知道,正常情况下,Linux下串口波特率最高到115200,因为我们特殊需要的原因,需要把波特率提高到至少460800,当然最理想的结果就是波特率达到921600,大的背景就是这个样子了。

然后先考究硬件,看看在硬件上到底能不能满足我们的要求,主控芯片S3C2440,在UART一章说在系统时钟下,波特率最高可达115200,然后注释中说如果Pclk达到60M,可以实现921600,我就按他说的,将主频提高,顺便将pclk提高到了60M,发现921600根本实现不了,230400波特率虽然能通,但是错误率很高,根本无法用,然后我又尝试着将Pclk提高到了70M,通过这种饮鸩止渴的方式,波特率可以提高到230400并且稳定传输,但是更高的波特率则无法实现,而Pclk不能无限提高,因为我们开发板还连接了触摸屏,在Pclk70M的情况下,触摸屏经常重启,说明这个方案不可行,所以就pass掉了,下面简单说一下我怎么更改的系统时钟Fclk,Hclk,Pclk。这三个时钟的关系以及计算方法我就不赘述了,我主要参考博客http://blog.csdn.NET/dong_zhihong/article/details/8469269进行修改

1)首先找到bootloader中 INC文件夹下的Option.inc文件,打开以后,找到如下代码段,这段代码就是主频400M时对应的M,P和S值设置,需要更改主频的话更改其中相应的数值几个(后来我发现,其实这个地方不改也行,因为最终起作用的是第二步)

[ FCLK = 400000000

CLKDIV_VAL EQU5

1:4:8

M_MDIV EQU

127 127

M_PDIV EQU

2 2

[ CPU_SEL = 32440001

M_SDIV EQU

1 2440A

|

M_SDIV EQU

0 2440X

]

]

2)找到u2440mon.c,然后在main()函数中找到如下代码,修改case2中的mpll_val = (92<<12)|(1<<4)|(1)这一行(为啥修改这一行?因为在这个switch代码有个j=2),其中三个数分别代表M,P,S。这才是决定主频的关键。

switch(j) {

case 0:

//240

key = 14

mpll_val = (112<<12)|(4<<4)|(1)

break

case 1:

//320

key = 14

mpll_val = (72<<12)|(1<<4)|(1)

break

case 2:

//400

key = 14

mpll_val = (92<<12)|(1<<4)|(1)

break

case 3:

//420!!!

key = 14

mpll_val = (97<<12)|(1<<4)|(1)

break

default:

key = 14

mpll_val = (92<<12)|(1<<4)|(1)

break

}

3)然后再 2440lib.c文件中,找到 ChangeClockDivider()函数,这个函数是控制分频比的,代码如下,这两个一个控制h_div,一个控制p_div。其中case 18: hdivn=2break这一行控制H分频,具体怎么改可以参考手册。

switch(hdivn_val) {

case 11: hdivn=0break

case 12: hdivn=1break

case 13:

case 16: hdivn=3break

case 14:

case 18: hdivn=2break

}

switch(pdivn_val) {

case 11: pdivn=0break

case 12: pdivn=1break

}

只需以上三步,就可以更改系统主频以及分频比,得到自己想要的Fclk和Pclk。

然后再说说我把上一个方案否定了以后,再仔细阅读芯片手册,发现串口的时钟源可以有三种方式获得:pclk,fclk/n,exclk,而且手册上说采用外部时钟的话,可以做到更高的波特率,但是这需要更改硬件,从指定那个引脚引入一个时钟,然后还要更改驱动程序,所以放弃了,所以只剩下一个路可以走,就是采用fclk/n的方式作为串口的时钟源,因为fclk频率很高,所以时钟源提高了,就可以把波特率提上来。然后就开始看linux内核源代码,因为串口的驱动早就集成到了linux内核之中,然后我就跳进了一个大坑。

其实串口本身的驱动并不复杂,如果裸机开发的话我感觉不难(强调一下,这个串口的裸机开发我没有做过,请做过的人不要喷我),因为串口被封装到了linux系统中,并且是层层封装,最终被封装成了tty的形式,所以我就从tty的驱动看起,抽丝剥茧,从里面寻找蛛丝马迹,

首先发现了s3c2440.c这个文件,通过调试得知,初始化的时候调用了其中的s3c2440_serial_init()函数,刚开始以为在这个文件中就这个函数有用,其实后来才知道,这个文件中的s3c2440_serial_getsource()和s3c2440_serial_setsource()在驱动中多次被调用。

然后考虑到,在上位机设置波特率的时候,调用的是系统函数cfsetispeed(),后经调试得知,这个函数调用了Samsung.c这个文件中的s3c24xx_serial_set_termios()这个函数,所有与串口相关的配置都与这个函数有关,因此锁定了方向,只要从这个函数中找到与波特率以及时钟源相关的语句,更改成我想要的即可,而这个函数又调用了很多子函数,但真正与波特率及时钟源相关的函数就是如下几句

/*

* Ask the core to calculate the divisor for us.

*/

baud = uart_get_baud_rate(port, termios, old, 0, 115200*8)

if (baud == 38400 &&(port->flags &UPF_SPD_MASK) == UPF_SPD_CUST)

quot = port->custom_divisor

else

quot = s3c24xx_serial_getclk(port, &clksrc, &clk, baud)

/* check to see if we need to change clock source */

if (ourport->clksrc != clksrc || ourport->baudclk != clk) {

s3c24xx_serial_setsource(port, clksrc)

if (ourport->baudclk != NULL &&!IS_ERR(ourport->baudclk)) {

clk_disable(ourport->baudclk)

ourport->baudclk = NULL

}

clk_enable(clk)

ourport->clksrc = clksrc

ourport->baudclk = clk

}

其中,uart_get_baud_rate()函数用于计算出上位机程序到设置的波特率的值,经我调试得知,上位机波特率从2400到921600都可以被准确的计算出来;所以这个函数跳过,然后看最后那个if语句,这个语句的作用是产看目前的时钟源是否与设置的时钟源相同,如果不相同,则按照设置的时钟源进行更改,这里面还涉及linux下的关于管理时钟的一个结构体clk结构体,参照博客http://blog.chinaunix.Net/uid-26583794-id-3208153.html以及http://wenku.baidu.com/view/13b4c686b9d528ea80c77904.html我找到了linux下的mach-smdk2440.c这个文件,这个文件中定义了串口所用的clk结构体,这也是linux系统启动时对串口的初始化配置结构体都在这,但是我更改过这个地方,让他初始化配置是首选fclk作为串口的时钟源,但是我发现这并没有效果,所以继续寻找中。

这样就剩下一个函数可以考虑了,s3c24xx_serial_getclk(),进入这个函数你会发现,这个函数是对串口时钟及波特率一个全面的配置,进入这个函数中,就有个结构体tmp_clksrc,这个结构体很关键,他的内容如下:

static struct s3c24xx_uart_clksrc tmp_clksrc = {

.name = "pclk",

.min_baud

= 0,

.max_baud

= 0,

.divisor

= 1,

}

从这个名字中就可以看出,它把串口的时钟源内定成为了pclk,这也是罪魁祸首,但是当我把name更改为fclk时,整个系统就无法启动了,包括前面说的更改mach-smdk2440.c中初始化配置,也是无法启动,后来在配置串口是做了一个判断,当波特率低于200000时,才有系统源配置不变,当波特率高于200000时,不在采用tmp_clksrc这个结构体,而是采用我自己定义的一个结构体,当然就是把name改成fclk,发现虽然只是能够更改 里面部分参数的时钟源,而正在的时钟源还是pclk,说明我的更改根本么有生效,由于这个linux调用太庞杂了,我就抱着试试看的态度,也是没有办法的办法,在配置完串口时钟的代码之后,添加了如下几行代码,直接更改S3C2440的寄存器,我知道这样做是很不“道德”的,而且很容易引起系统混乱,但是我只是这么试试,没想到还真的有用。

在 samsung.c文件中添加

if (baud >= 200000)

{

printk("baud >= 200000 @-------------samsung.c\n")

__raw_writel(0x1fc5,S3C24XX_VA_UART0 + S3C2410_UCON)

__raw_writel(0x0fc5,S3C24XX_VA_UART1 + S3C2410_UCON)

__raw_writel(0x8fc5,S3C24XX_VA_UART2 + S3C2410_UCON)

__raw_writel(32,S3C24XX_VA_UART0 + S3C2410_UCON+0x24)//保证控制台的波特率还是115200用于显示

__raw_writel(3,S3C24XX_VA_UART1 + S3C2410_UCON+0x24)//921600

//__raw_writel(3,S3C24XX_VA_UART1 + S3C2410_UCON+0x24)

}

上面这段代码经我多次试验得到的,因为一开始用的系统主时钟fclk为400M,这样算出来UBRDIV1分频应该为3,但是这样的话错误率比较高,还是导致无法传输,至此我终于明白手册上为什么说pclk在60M 可以实现921600了,因为用60M时钟计算的话,分频UBRDIV1为3.069,最接近整数3,所以在这个错误率下可以实现921600的波特率传输,所以我将系统时钟fclk设置为420M,其中MDIV=97,PDIV=1,SDIV=1,而ucon0=0x1fc5,ucon1=0x0fc5,ucon2=0x8fc5,这样n=1+6=7,所以串口的时钟源为fclk/n=60M,可以得到精确的921600波特率,所以实现我刚开始的目标,其实要实现其他的波特率也可以,比如460800,计算后主时钟fclk(尽量算出的分频UBRDIV1最贴近整数),然后就可以实现了。

在这还有个小想法,提高串口波特率,还可以使用USB转串口,因为USB转串口可以实现921600,而linux中以及集成了USB转串口的驱动,只需要在调用串口的那个open函数中改为调用USB转串口的节点即可,当然,这个方案我没有试,因为我们就一个USB口,而且还被占用了,所以希望有需要的朋友可以试一下。


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/yw/6139188.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-03-16
下一篇2023-03-16

发表评论

登录后才能评论

评论列表(0条)

    保存