1. 程式人生 > >tty初探—uart驅動框架分析(二)uart_add_one_port

tty初探—uart驅動框架分析(二)uart_add_one_port

    在前面的一篇文章中,我們分析了一個 uart_driver 的向上註冊過程,主要是 tty 的一些東西,知道了 tty 註冊了一個字元裝置驅動,我們在使用者空間 open 時將呼叫到 uart_port.ops.startup ,在使用者空間 write 則呼叫 uart_port.ops.start_tx ,還知道了如何 read 資料等等。但是,這些都是核心幫我們實現好的,在真正的驅動開發過程中幾乎不涉及那些程式碼的修改移植工作,真正需要我們觸碰的是 uart_port 這個結構體,它真正的對應於一個物理的串列埠。

    其實,真正需要我們做的工作就是 分配一個 uart_port 結構,然後 uart_add_one_port 。分析過 s3c2440 uart 的驅動程式碼之後,我發現,這麼一個簡單的目標簡直就是經歷了山路十八彎。

    先說一下大體的思路,uart_port 的註冊過程是基於 platform 平臺裝置驅動模型,device 側提供 3 個串列埠的硬體資訊,並註冊到 platform_bus_type 中去。然後 driver 也註冊到 platform_bus_type 時,就會根據名字進行匹配,從而呼叫 driver->probe 函式,在 probe 函式裡進行 uart_add_one_port 。思路也是很簡單的,複雜在 s3c2440 註冊 device 之前的工作扯了太多東西。

    先秀個最終分析的圖:


一、Linux 啟動過程回憶

    在 uboot 啟動核心的時候,核心剛剛啟動我們就看到串列埠各種資訊就輸出來了,也就是說串列埠驅動的初始化工作是在 Linux 啟動過程中一個比較靠前的位置。核心啟動的時候首先會去判斷 cpu id 是否支援,接著判斷是否支援uboot 傳遞進來的單板 Id ,然後 start_kernel -》setup_arch 進行一系列的初始化工作,其中必然包含串列埠相關初始化。

核心中所有支援的單板都用 MACHINE_START 和 MACHINE_END 來定義

MACHINE_START(MINI2440, "FriendlyARM Mini2440 development board")
	.phys_io	= S3C2410_PA_UART,
	.io_pg_offst	= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
	.boot_params	= S3C2410_SDRAM_PA + 0x100,

	.init_irq	= s3c24xx_init_irq,
	.map_io		= mini2440_map_io,
	.init_machine	= mini2440_machine_init,
	.timer		= &s3c24xx_timer,
MACHINE_END
    但是,裡面的這些函式是何時被呼叫的,呼叫的先後順序是怎樣的,我們需要分析 Linux 的啟動流程才能知道,資訊量還是比較大的,在前面的一篇文章中我分析過了

請參考:http://blog.csdn.net/lizuobin2/article/details/51779064

    如果你自己分析一遍的話,呼叫先後順序應該是這樣的:

    start_kernel -》setup_arch -》 map_io -》 init_irq -》 timer -》 init_machine -》 s3c_arch_init -》 s3c24xx_serial_modinit -》s3c2440_serial_init

    後面三個函式是通過類似於 module_init 等被組織進核心裡去的放在一個特殊的段裡,核心啟動到一定時候就去把這個段裡的每一個函式取出來去呼叫,也是與串列埠相關的,分析過程就不再贅述了。

二、platform device 的註冊之路

    分析出了整個的串列埠驅動的初始化、設定、註冊流程,問題就簡單多了,挨個函式分析便是。

static void __init mini2440_map_io(void)
{
	s3c24xx_init_io(mini2440_iodesc, ARRAY_SIZE(mini2440_iodesc));
	s3c24xx_init_clocks(12000000);
	s3c24xx_init_uarts(mini2440_uartcfgs, ARRAY_SIZE(mini2440_uartcfgs));
}
    能一看瞅出來的,外部晶振的頻率 12M ,如果我們在移植其它單板的時候不是,記得修改。
void __init s3c24xx_init_io(struct map_desc *mach_desc, int size)
{
	....
	s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids));
}
static struct cpu_table *cpu;
void __init s3c_init_cpu(unsigned long idcode,
			 struct cpu_table *cputab, unsigned int cputab_size)
{
	cpu = s3c_lookup_cpu(idcode, cputab, cputab_size);
	cpu->map_io();
}

static struct cpu_table * __init s3c_lookup_cpu(unsigned long idcode,
						struct cpu_table *tab,
						unsigned int count)
{
	for (; count != 0; count--, tab++) {
		if ((idcode & tab->idmask) == tab->idcode)
			return tab;
	}

	return NULL;
}
static struct cpu_table cpu_ids[] __initdata = {
	{
		.idcode		= 0x32440000,
		.idmask		= 0xffffffff,
		.map_io		= s3c244x_map_io,
		.init_clocks	= s3c244x_init_clocks,
		.init_uarts	= s3c244x_init_uarts,
		.init		= s3c2440_init,
		.name		= name_s3c2440
	},
};
    上邊四段程式碼費盡周折,只為呼叫 cpu_ids 數組裡的 s3c244x_map_io 函式。
void __init s3c244x_map_io(void)
{
	/* register our io-tables */

	iotable_init(s3c244x_iodesc, ARRAY_SIZE(s3c244x_iodesc));

	/* rename any peripherals used differing from the s3c2410 */

	s3c_device_sdi.name  = "s3c2440-sdi";
	s3c_device_i2c0.name  = "s3c2440-i2c";
	s3c_device_nand.name = "s3c2440-nand";
	s3c_device_usbgadget.name = "s3c2440-usbgadget";
}
    也是醉醉的,竟然跟串列埠毫無關係。下面看 s3c24xx_init_uarts
void __init s3c24xx_init_uarts(struct s3c2410_uartcfg *cfg, int no)
{
	(cpu->init_uarts)(cfg, no);
}
    呵,前邊的工作果然也不是完全白做的,至少幫我們找到了 cpu ,那麼就是呼叫 s3c244x_init_uarts 咯
void __init s3c244x_init_uarts(struct s3c2410_uartcfg *cfg, int no)
{
	s3c24xx_init_uartdevs("s3c2440-uart", s3c2410_uart_resources, cfg, no);
}
    繼續往下看之前,我們先看一下引數 cfg , no ,s3c2410_uart_resources
s3c24xx_init_uarts(mini2440_uartcfgs, ARRAY_SIZE(mini2440_uartcfgs));</span>
static struct s3c2410_uartcfg mini2440_uartcfgs[] __initdata = {
	[0] = {
		.hwport	     = 0,
		.flags	     = 0,
		.ucon	     = 0x3c5,
		.ulcon	     = 0x03,
		.ufcon	     = 0x51,
	},
/* 此處略去了 1、2 兩個串列埠的資訊 */
};
struct s3c24xx_uart_resources s3c2410_uart_resources[] __initdata = {
	[0] = {
		.resources	= s3c2410_uart0_resource,
		.nr_resources	= ARRAY_SIZE(s3c2410_uart0_resource),
	},
/* 此處略去了 1、2 串列埠的資訊 */
};
static struct resource s3c2410_uart0_resource[] = {
	[0] = {
		.start = S3C2410_PA_UART0,
		.end   = S3C2410_PA_UART0 + 0x3fff,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = IRQ_S3CUART_RX0,
		.end   = IRQ_S3CUART_ERR0,
		.flags = IORESOURCE_IRQ,
	}
};
    萬事俱備,開始構建 device
struct platform_device *s3c24xx_uart_src[4] = {
	&s3c24xx_uart_device0,
	&s3c24xx_uart_device1,
	&s3c24xx_uart_device2,
	&s3c24xx_uart_device3,
};
static struct s3c2410_uartcfg uart_cfgs[CONFIG_SERIAL_SAMSUNG_UARTS];
/* 填充平臺裝置的過程,未註冊 */
void __init s3c24xx_init_uartdevs(char *name, struct s3c24xx_uart_resources *res,
				  struct s3c2410_uartcfg *cfg, int no)
{
	struct platform_device *platdev;
	struct s3c2410_uartcfg *cfgptr = uart_cfgs;
	struct s3c24xx_uart_resources *resp;
	int uart;
	/* 將 mini2440_uartcfgs 數組裡的引數拷貝到 cfgptr */
	memcpy(cfgptr, cfg, sizeof(struct s3c2410_uartcfg) * no);

	for (uart = 0; uart < no; uart++, cfg++, cfgptr++) {
		
		/* 從 s3c24xx_uart_src 數組裡取出平臺裝置 */
		platdev = s3c24xx_uart_src[cfgptr->hwport];
		
		/* 獲得對應的 resource ,物理暫存器和中斷 */
		resp = res + cfgptr->hwport;

		/* 將 s3c24xx_uart_src 的平臺裝置 放到 平臺裝置陣列 s3c24xx_uart_devs */
		s3c24xx_uart_devs[uart] = platdev;
		
		/* 設定名字 資源 */
		platdev->name = name;
		platdev->resource = resp->resources;
		platdev->num_resources = resp->nr_resources;
		/* 設定平臺數據 mini2440_uartcfgs 數組裡的東西 */
		platdev->dev.platform_data = cfgptr;
	}

	nr_uarts = no;
}
    至此,device 構建設定完畢,等待註冊:

    1、3 個串列埠的 device 存放在 s3c24xx_uart_devs 數組裡,後邊肯定會從數組裡取出來註冊。

    2、3 個串列埠的 device 的名字都是 “s3c2440-uart”。

    3、3 個串列埠的 device 資原始檔裡存放好了 io 實體地址,Irq 等資訊。

    4、3 個串列埠的 device 資源資料。
    移植過程中可能需要修改的檔案:mini2440_uartcfgs 、s3c2410_uart0_resource 、s3c24xx_uart_src 還有那個晶振頻率。

    s3c_arch_init 函式中,將 device 註冊到 platform_bus_type

static int __init s3c_arch_init(void)
{
	int ret;

	ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);
	return ret;
}

三、uart_driver 的註冊

    注意,是 uart_driver 的註冊,是上一篇文章講的過程,並不是對應於平臺裝置的平臺驅動。為什麼在這個時候註冊 uart_driver,因為如果先註冊平臺裝置的 driver 的話,那麼在probe函式裡 uart_add_one_port ,uart_prot 沒地方註冊!!因此,要先註冊 uart_driver ,簡單貼下程式碼,不在分析。

static struct uart_driver s3c24xx_uart_drv = {
	.owner		= THIS_MODULE,
	.dev_name	= "s3c2410_serial",
	.nr		= CONFIG_SERIAL_SAMSUNG_UARTS,
	.cons		= S3C24XX_SERIAL_CONSOLE,
	.driver_name	= S3C24XX_SERIAL_NAME,
	.major		= S3C24XX_SERIAL_MAJOR,
	.minor		= S3C24XX_SERIAL_MINOR,
};
static int __init s3c24xx_serial_modinit(void)
{
	int ret;

	ret = uart_register_driver(&s3c24xx_uart_drv);
	if (ret < 0) {
		printk(KERN_ERR "failed to register UART driver\n");
		return -1;
	}

	return 0;
}
附上上一篇文章的地址:http://blog.csdn.net/lizuobin2/article/details/51773305

四、platform driver 的註冊以及 probe 函式
static int s3c2440_serial_probe(struct platform_device *dev)
{
	dbg("s3c2440_serial_probe: dev=%p\n", dev);
	return s3c24xx_serial_probe(dev, &s3c2440_uart_inf);
}

static struct platform_driver s3c2440_serial_driver = {
	.probe		= s3c2440_serial_probe,
	.remove		= __devexit_p(s3c24xx_serial_remove),
	.driver		= {
		.name	= "s3c2440-uart",
		.owner	= THIS_MODULE,
	},
};

s3c24xx_console_init(&s3c2440_serial_driver, &s3c2440_uart_inf);

static int __init s3c2440_serial_init(void)
{
	return s3c24xx_serial_init(&s3c2440_serial_driver, &s3c2440_uart_inf);
}
    將驅動註冊到 platform_bus_type ,此時會遍歷 platform_bus_type 的 deivce 連結串列,取出 device 進行名字比較,我們前邊註冊的三個device的名字是一樣的,沒關係 Linux 允許這樣做,每次匹配到一個都呼叫一次 Probe 函式。
static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
	[0] = {
		.port = {
			.lock		= __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
			.iotype		= UPIO_MEM,
			.irq		= IRQ_S3CUART_RX0,
			.uartclk	= 0,
			.fifosize	= 16,
			.ops		= &s3c24xx_serial_ops,/* 底層的操作函式 */
			.flags		= UPF_BOOT_AUTOCONF,
			.line		= 0,
		}
	},
	/* 此處略去了兩個串列埠的資訊 */
};
int s3c24xx_serial_probe(struct platform_device *dev,
			 struct s3c24xx_uart_info *info)
{
	struct s3c24xx_uart_port *ourport;
	int ret;
/* 取出 uart_port  */
	ourport = &s3c24xx_serial_ports[probe_index];
	
	probe_index++;
/* 對 uart_port 進一步設定 */
	ret = s3c24xx_serial_init_port(ourport, info, dev);
/* 將 uart_port 註冊到 uart_driver */
	uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
	
	platform_set_drvdata(dev, &ourport->port);

	ret = device_create_file(&dev->dev, &dev_attr_clock_source);

	ret = s3c24xx_serial_cpufreq_register(ourport);

	return 0;

}
static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
				    struct s3c24xx_uart_info *info,
				    struct platform_device *platdev)
{
	struct uart_port *port = &ourport->port;
	struct s3c2410_uartcfg *cfg;
	struct resource *res;
	int ret;

	cfg = s3c24xx_dev_to_cfg(&platdev->dev);

	/* setup info for port */
	port->dev	= &platdev->dev;
	ourport->info	= info;

	/* copy the info in from provided structure */
	ourport->port.fifosize = info->fifosize;
<span style="white-space:pre">	</span>/* 設定時鐘 */
	port->uartclk = 1;

	/* sort our the physical and virtual addresses for each UART */

	res = platform_get_resource(platdev, IORESOURCE_MEM, 0);
<span style="white-space:pre">	</span>/* 設定實體地址,虛擬地址 */
	port->mapbase = res->start;
	port->membase = S3C_VA_UART + res->start - (S3C_PA_UART & 0xfff00000);
	ret = platform_get_irq(platdev, 0);
	if (ret < 0)
		port->irq = 0;
	else {
		port->irq = ret;/* 設定中斷號 */
		ourport->rx_irq = ret;
		ourport->tx_irq = ret + 1;
	}
	
	ret = platform_get_irq(platdev, 1);


	ourport->clk	= clk_get(&platdev->dev, "uart");


	/* reset the fifos (and setup the uart) */
	s3c24xx_serial_resetport(port, cfg);
	return 0;
}
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
	struct uart_state *state;
	struct tty_port *port;
	int ret = 0;
	struct device *tty_dev;

	BUG_ON(in_interrupt());

	if (uport->line >= drv->nr)
		return -EINVAL;

	state = drv->state + uport->line;
	port = &state->port;

	mutex_lock(&port_mutex);
	mutex_lock(&port->mutex);
	if (state->uart_port) {
		ret = -EINVAL;
		goto out;
	}
<span style="white-space:pre">	</span>/* 將 uart_prot 繫結到 uart_driver 對應的 state */
	state->uart_port = uport;
	state->pm_state = -1;

	uport->cons = drv->cons;
	uport->state = state;

	/*
	 * If this port is a console, then the spinlock is already
	 * initialised.
	 */
	if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
		spin_lock_init(&uport->lock);
		lockdep_set_class(&uport->lock, &port_lock_key);
	}
/* 實際呼叫 port->ops->config_port(port, flags) 稍後再看 */
	uart_configure_port(drv, state, uport);

	/*
	 * 上一篇文章中,我們提到tty註冊了一個字元裝置 “ttySAC ”
	 * 那麼,我們平時看到的 “ttySAC0”“ttySAC1”等就是在這裡註冊的
	 */
	tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);
	if (likely(!IS_ERR(tty_dev))) {
		device_init_wakeup(tty_dev, 1);
		device_set_wakeup_enable(tty_dev, 0);
	} else
		printk(KERN_ERR "Cannot register tty device on line %d\n",
		       uport->line);

	/*
	 * Ensure UPF_DEAD is not set.
	 */
	uport->flags &= ~UPF_DEAD;

 out:
	mutex_unlock(&port->mutex);
	mutex_unlock(&port_mutex);

	return ret;
}

struct device *tty_register_device(struct tty_driver *driver, unsigned index,
				   struct device *device)
{
	char name[64];
	dev_t dev = MKDEV(driver->major, driver->minor_start) + index;

	if (index >= driver->num) {
		printk(KERN_ERR "Attempt to register invalid tty line number "
		       " (%d).\n", index);
		return ERR_PTR(-EINVAL);
	}

	if (driver->type == TTY_DRIVER_TYPE_PTY)
		pty_line_name(driver, index, name);
	else
		tty_line_name(driver, index, name);

	return device_create(tty_class, device, dev, NULL, name);
}
<span style="font-family:SimSun;font-size:18px;">static void tty_line_name(struct tty_driver *driver, int index, char *p)
{
	sprintf(p, "%s%d", driver->name, index + driver->name_base);
}
   tty_driver->name == "ttySAC",在此基礎上加上 uart_port.line ,就組成了具體串列埠的裝置節點的名字,例如“ttySAC0”。

    分析到這裡,完了麼?沒有,還有一個非常重要的東西沒有分析呢,那就是底層的操作函式。

static struct uart_ops s3c24xx_serial_ops = {
	.pm		= s3c24xx_serial_pm,
	.tx_empty	= s3c24xx_serial_tx_empty,
	.get_mctrl	= s3c24xx_serial_get_mctrl,
	.set_mctrl	= s3c24xx_serial_set_mctrl,
	.stop_tx	= s3c24xx_serial_stop_tx,
	.start_tx	= s3c24xx_serial_start_tx,
	.stop_rx	= s3c24xx_serial_stop_rx,
	.enable_ms	= s3c24xx_serial_enable_ms,
	.break_ctl	= s3c24xx_serial_break_ctl,
	.startup	= s3c24xx_serial_startup,
	.shutdown	= s3c24xx_serial_shutdown,
	.set_termios	= s3c24xx_serial_set_termios,
	.type		= s3c24xx_serial_type,
	.release_port	= s3c24xx_serial_release_port,
	.request_port	= s3c24xx_serial_request_port,
	.config_port	= s3c24xx_serial_config_port,
	.verify_port	= s3c24xx_serial_verify_port,
};

    這麼多的函式,如果讓我們自己來實現,那相比真得頭都大了。一般晶片廠家會幫我們搞得吧。其他的不分析了,分析一個 startup 函式,因為我們在使用者空間 open 的時候會呼叫它,那麼必然有一些初始化的工作。
static int s3c24xx_serial_startup(struct uart_port *port)
{
	struct s3c24xx_uart_port *ourport = to_ourport(port);
	int ret;

	dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)\n",
	    port->mapbase, port->membase);

	rx_enabled(port) = 1;

	ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
			  s3c24xx_serial_portname(port), ourport);

	if (ret != 0) {
		printk(KERN_ERR "cannot get irq %d\n", ourport->rx_irq);
		return ret;
	}

	ourport->rx_claimed = 1;

	dbg("requesting tx irq...\n");

	tx_enabled(port) = 1;

	ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
			  s3c24xx_serial_portname(port), ourport);

	if (ret) {
		printk(KERN_ERR "cannot get irq %d\n", ourport->tx_irq);
		goto err;
	}

	ourport->tx_claimed = 1;

	dbg("s3c24xx_serial_startup ok\n");

	/* the port reset code should have done the correct
	 * register setup for the port controls */
	if (port->line == 2) {
		s3c2410_gpio_cfgpin(S3C2410_GPH(6), S3C2410_GPH6_TXD2);
		s3c2410_gpio_pullup(S3C2410_GPH(6), 1);
		s3c2410_gpio_cfgpin(S3C2410_GPH(7), S3C2410_GPH7_RXD2);
		s3c2410_gpio_pullup(S3C2410_GPH(7), 1);
	}


	return ret;

 err:
	s3c24xx_serial_shutdown(port);
	return ret;
}
    主要工作是註冊了兩個中斷,傳送中斷,接收中斷,來看看一個和我們上篇文章的猜測是否一致。
static irqreturn_t
s3c24xx_serial_rx_chars(int irq, void *dev_id)
{
	..../* 呼叫線路規程的...和上篇文章一致 */
	tty_flip_buffer_push(tty);

 out:
	return IRQ_HANDLED;
}
static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
{
	....

	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
		uart_write_wakeup(port);

	if (uart_circ_empty(xmit))
		s3c24xx_serial_stop_tx(port);

 out:
	return IRQ_HANDLED;
}
void uart_write_wakeup(struct uart_port *port)
{
	struct uart_state *state = port->state;
	/*
	 * This means you called this function _after_ the port was
	 * closed.  No cookie for you.
	 */
	BUG_ON(!state);
	tasklet_schedule(&state->tlet);/* 也是一致的 */
}