慢慢欣赏linux之串口驱动代码分析 - 基于arm64 4.19.x版本

root@linux:~# uname -a
Linux bogon 4.19.115 #25 SMP PREEMPT Fri Oct 15 10:46:25 CST 2021 aarch64 aarch64 aarch64 GNU/Linux
uart@28001000 {
		compatible = "arm,pl011", "arm,primecell"; // 看到"arm,primecell"会将platform device转换成smba device
		reg = <0x00000000 0x28001000 0x00000000 0x00001000>;
		baud = <0x0001c200>;
		reg-shift = <0x00000002>;
		reg-io-width = <0x00000004>;
		interrupts = <0x00000000 0x00000007 0x00000004>;
		clocks = <0x00000003 0x00000003>;
		clock-names = "uartclk", "apb_pclk";
		status = "ok";
};

int __init pl011_init(void)
{
	printk(KERN_INFO "Serial: AMBA PL011 UART driver\n");
	return amba_driver_register(&pl011_driver);
	==>struct amba_driver pl011_driver = {
		.drv = {
			.name	= "uart-pl011",
			.pm	= &pl011_dev_pm_ops,
			.suppress_bind_attrs = IS_BUILTIN(CONFIG_SERIAL_AMBA_PL011),
		},
		.id_table	= pl011_ids,
		==>const struct amba_id pl011_ids[] = {
			{
				.id	= 0x00041011,
				.mask	= 0x000fffff,
				.data	= &vendor_arm,
				==>struct vendor_data vendor_arm = {
					.reg_offset		= pl011_std_offsets,
					==>u16 pl011_std_offsets[REG_ARRAY_SIZE] = {
						[REG_DR] = UART01x_DR,
						[REG_FR] = UART01x_FR,
						[REG_LCRH_RX] = UART011_LCRH,
						[REG_LCRH_TX] = UART011_LCRH,
						[REG_IBRD] = UART011_IBRD,
						[REG_FBRD] = UART011_FBRD,
						[REG_CR] = UART011_CR,
						[REG_IFLS] = UART011_IFLS,
						[REG_IMSC] = UART011_IMSC,
						[REG_RIS] = UART011_RIS,
						[REG_MIS] = UART011_MIS,
						[REG_ICR] = UART011_ICR,
						[REG_DMACR] = UART011_DMACR,
					};
					.ifls			= UART011_IFLS_RX4_8|UART011_IFLS_TX4_8,
					.fr_busy		= UART01x_FR_BUSY,
					.fr_dsr			= UART01x_FR_DSR,
					.fr_cts			= UART01x_FR_CTS,
					.fr_ri			= UART011_FR_RI,
					.oversampling		= false,
					.dma_threshold		= false,
					.cts_event_workaround	= false,
					.always_enabled		= false,
					.fixed_options		= false,
					.get_fifosize		= get_fifosize_arm,
				};
			},
			{
				.id	= 0x00380802,
				.mask	= 0x00ffffff,
				.data	= &vendor_st,
			},
			{
				.id	= AMBA_LINUX_ID(0x00, 0x1, 0xffe),
				.mask	= 0x00ffffff,
				.data	= &vendor_zte,
			},
			{ 0, 0 },
		};

		.probe		= pl011_probe,	// 后面详细描述
		.remove		= pl011_remove,
	};
	=>int amba_driver_register(struct amba_driver *drv)
	{
		drv->drv.bus = &amba_bustype;
		==>struct bus_type amba_bustype = {
			.name		= "amba",
			.dev_groups	= amba_dev_groups,
			.match		= amba_match,
			=>int amba_match(struct device *dev, struct device_driver *drv)
			{
				struct amba_device *pcdev = to_amba_device(dev);
				struct amba_driver *pcdrv = to_amba_driver(drv);

				/* When driver_override is set, only bind to the matching driver */
				if (pcdev->driver_override)
					return !strcmp(pcdev->driver_override, drv->name);

				return amba_lookup(pcdrv->id_table, pcdev) != NULL;
				=>const struct amba_id *amba_lookup(const struct amba_id *table, struct amba_device *dev)
				{
					int ret = 0;

					while (table->mask) {
						ret = (dev->periphid & table->mask) == table->id;
						if (ret)
							break;
						table++;
					}

					return ret ? table : NULL;
				}
			}
			.uevent		= amba_uevent,
			.dma_configure	= platform_dma_configure,
			.pm		= &amba_pm,
		};

	#define SETFN(fn)	if (drv->fn) drv->drv.fn = amba_##fn
		SETFN(probe);
		==>drv->drv.probe = amba_probe
		/* amba_probe 是 amba_driver和amba_device match后调用,
			我们只看到了amba_drivers的注册,而amba_devices如何通过platform_devices生成,
			可以通过参考文档1了解
		 */
		==>int amba_probe(struct device *dev)
		{
			struct amba_device *pcdev = to_amba_device(dev);
			struct amba_driver *pcdrv = to_amba_driver(dev->driver);
			const struct amba_id *id = amba_lookup(pcdrv->id_table, pcdev);
			int ret;

			do {
				ret = of_clk_set_defaults(dev->of_node, false);

				ret = dev_pm_domain_attach(dev, true);

				ret = amba_get_enable_pclk(pcdev);

				pm_runtime_get_noresume(dev);
				pm_runtime_set_active(dev);
				pm_runtime_enable(dev);

				ret = pcdrv->probe(pcdev, id);
				=>pl011_probe
				
				if (ret == 0)
					break;

				pm_runtime_disable(dev);
				pm_runtime_set_suspended(dev);
				pm_runtime_put_noidle(dev);

				amba_put_disable_pclk(pcdev);
				dev_pm_domain_detach(dev, true);
			} while (0);

			return ret;
		}

		SETFN(remove);
		SETFN(shutdown);

		return driver_register(&drv->drv);
	}
}

int pl011_probe(struct amba_device *dev, const struct amba_id *id)
{
	struct vendor_data *vendor = id->data;	// id 实际为 pl011_ids
	struct uart_amba_port *uap = devm_kzalloc(&dev->dev, sizeof(struct uart_amba_port), GFP_KERNEL);
	int portnr = pl011_find_free_port();
	=>int pl011_find_free_port(void)
	{
		int i;

		for (i = 0; i < ARRAY_SIZE(amba_ports); i++)
			if (amba_ports[i] == NULL)	// static struct uart_amba_port *amba_ports[UART_NR]; // #define UART_NR 14
				return i;
		return -EBUSY;
	}
	
	uap->clk = devm_clk_get(&dev->dev, NULL);

	uap->reg_offset = vendor->reg_offset;
	uap->vendor = vendor;
	uap->fifosize = vendor->get_fifosize(dev);
	uap->port.iotype = vendor->access_32b ? UPIO_MEM32 : UPIO_MEM;
	uap->port.irq = dev->irq[0];
	uap->port.ops = &amba_pl011_pops;
	==>const struct uart_ops amba_pl011_pops = {
		.tx_empty	= pl011_tx_empty,
		.set_mctrl	= pl011_set_mctrl,
		.get_mctrl	= pl011_get_mctrl,
		.stop_tx	= pl011_stop_tx,
		.start_tx	= pl011_start_tx,
		.stop_rx	= pl011_stop_rx,
		.enable_ms	= pl011_enable_ms,
		.break_ctl	= pl011_break_ctl,
		.startup	= pl011_startup,
		.shutdown	= pl011_shutdown,
		.flush_buffer	= pl011_dma_flush_buffer,
		.set_termios	= pl011_set_termios,
		.type		= pl011_type,
		.release_port	= pl011_release_port,
		.request_port	= pl011_request_port,
		.config_port	= pl011_config_port,
		.verify_port	= pl011_verify_port,
	#ifdef CONFIG_CONSOLE_POLL
		.poll_init     = pl011_hwinit,
		.poll_get_char = pl011_get_poll_char,
		.poll_put_char = pl011_put_poll_char,
	#endif
	};
	
	ret = pl011_setup_port(&dev->dev, uap, &dev->res, portnr);
	=>int pl011_setup_port(struct device *dev, struct uart_amba_port *uap, struct resource *mmiobase, int index)
	{
		void __iomem *base = devm_ioremap_resource(dev, mmiobase);

		index = pl011_probe_dt_alias(index, dev);

		uap->old_cr = 0;
		uap->port.dev = dev;
		uap->port.mapbase = mmiobase->start;
		uap->port.membase = base;
		uap->port.fifosize = uap->fifosize;
		uap->port.flags = UPF_BOOT_AUTOCONF;
		uap->port.line = index;

		amba_ports[index] = uap;

		return 0;
	}
	amba_set_drvdata(dev, uap);
	=>#define amba_set_drvdata(d,p)	dev_set_drvdata(&d->dev, p)
		=>inline void dev_set_drvdata(struct device *dev, void *data)
		{
			dev->driver_data = data;
		}
	
	return pl011_register_port(uap);
	=>int pl011_register_port(struct uart_amba_port *uap)
	{
		int ret;

		/* Ensure interrupts from this UART are masked and cleared */
		pl011_write(0, uap, REG_IMSC);
		pl011_write(0xffff, uap, REG_ICR);

		if (!amba_reg.state) {	// 驱动只需要注册一次,设备树定义了4个设备,需要初始化4个设备
			ret = uart_register_driver(&amba_reg); // 以uart_driver为中心,向上注册
			=>int uart_register_driver(struct uart_driver *drv)
			{
				/*
				 * Maybe we should be using a slab cache for this, especially if
				 * we have a large number of ports to handle.
				 */
				// 按照最大串口数分配, 驱动与设备是1对多的关系
				drv->state = kcalloc(drv->nr, sizeof(struct uart_state), GFP_KERNEL);

				struct tty_driver *normal = alloc_tty_driver(drv->nr);

				drv->tty_driver = normal;

				normal->driver_name	= drv->driver_name;
				normal->name		= drv->dev_name;
				normal->major		= drv->major;
				normal->minor_start	= drv->minor;
				normal->type		= TTY_DRIVER_TYPE_SERIAL;
				normal->subtype		= SERIAL_TYPE_NORMAL;
				normal->init_termios	= tty_std_termios;
				normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
				normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
				normal->flags		= TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
				normal->driver_state    = drv;
				tty_set_operations(normal, &uart_ops);

				/*
				 * Initialise the UART state(s).
				 */
				for (i = 0; i < drv->nr; i++) {
					struct uart_state *state = drv->state + i;
					struct tty_port *port = &state->port;

					tty_port_init(port);
					=>void tty_port_init(struct tty_port *port)
					{
						memset(port, 0, sizeof(*port));
						tty_buffer_init(port);
						init_waitqueue_head(&port->open_wait);
						init_waitqueue_head(&port->delta_msr_wait);
						mutex_init(&port->mutex);
						mutex_init(&port->buf_mutex);
						spin_lock_init(&port->lock);
						port->close_delay = (50 * HZ) / 100;
						port->closing_wait = (3000 * HZ) / 100;
						port->client_ops = &tty_port_default_client_ops;
						==>const struct tty_port_client_operations tty_port_default_client_ops = {
							.receive_buf = tty_port_default_receive_buf,
							.write_wakeup = tty_port_default_wakeup,
						};
						kref_init(&port->kref);
					}
					port->ops = &uart_port_ops;
					==>const struct tty_port_operations uart_port_ops = {
						.carrier_raised = uart_carrier_raised,
						.dtr_rts	= uart_dtr_rts,
						.activate	= uart_port_activate,
						.shutdown	= uart_tty_port_shutdown,
					};
				}

				retval = tty_register_driver(normal);
				=>int tty_register_driver(struct tty_driver *driver)
				{
					int error;
					int i;
					dev_t dev;
					struct device *d;

					if (!driver->major) {
						error = alloc_chrdev_region(&dev, driver->minor_start,
										driver->num, driver->name);
						if (!error) {
							driver->major = MAJOR(dev);
							driver->minor_start = MINOR(dev);
						}
					} else {
						dev = MKDEV(driver->major, driver->minor_start);
						error = register_chrdev_region(dev, driver->num, driver->name);
					}

					if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {	// 也不走这个流程
						error = tty_cdev_add(driver, dev, 0, driver->num);
					}

					mutex_lock(&tty_mutex);
					list_add(&driver->tty_drivers, &tty_drivers);	// 注册
					mutex_unlock(&tty_mutex);

					if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {	// 不走这个流程
						for (i = 0; i < driver->num; i++) {
							d = tty_register_device(driver, i, NULL);
						}
					}
					proc_tty_register_driver(driver);
					driver->flags |= TTY_DRIVER_INSTALLED;
					return 0;
				}
			}
		}
		==>static struct uart_driver amba_reg = {
			.owner			= THIS_MODULE,
			.driver_name		= "ttyAMA",
			.dev_name		= "ttyAMA",
			.major			= SERIAL_AMBA_MAJOR,	// #define SERIAL_AMBA_MAJOR	204
			.minor			= SERIAL_AMBA_MINOR,	// #define SERIAL_AMBA_MINOR	64
			.nr			= UART_NR,					
			.cons			= AMBA_CONSOLE,
		};

		ret = uart_add_one_port(&amba_reg, &uap->port);
		=>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;
			int num_groups;

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

			mutex_lock(&port_mutex);
			mutex_lock(&port->mutex);

			/* Link the port to the driver state table and vice versa */
			atomic_set(&state->refcount, 1);
			init_waitqueue_head(&state->remove_wait);
			state->uart_port = uport;
			uport->state = state;

			state->pm_state = UART_PM_STATE_UNDEFINED;
			uport->cons = drv->cons;
			uport->minor = drv->tty_driver->minor_start + uport->line;
			uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name, drv->tty_driver->name_base + uport->line);

			/*
			 * 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);
			}
			=>#define uart_console(port) ((port)->cons && (port)->cons->index == (port)->line)
			
			if (uport->cons && uport->dev)
				of_console_check(uport->dev->of_node, uport->cons->name, uport->line);

			tty_port_link_device(port, drv->tty_driver, uport->line);
			=>void tty_port_link_device(struct tty_port *port, struct tty_driver *driver, unsigned index)
			{
				driver->ports[index] = port;
			}
			
			uart_configure_port(drv, state, uport);
			=>void uart_configure_port(struct uart_driver *drv, struct uart_state *state, struct uart_port *port)
			{
				unsigned int flags = 0;

				/*
				 * Now do the auto configuration stuff.  Note that config_port
				 * is expected to claim the resources and map the port for us.
				 */
				if (port->flags & UPF_AUTO_IRQ)
					flags |= UART_CONFIG_IRQ;
				if (port->flags & UPF_BOOT_AUTOCONF) {
					if (!(port->flags & UPF_FIXED_TYPE)) {
						port->type = PORT_UNKNOWN;
						flags |= UART_CONFIG_TYPE;
					}
					port->ops->config_port(port, flags);
				}

				if (port->type != PORT_UNKNOWN) {
					unsigned long flags;

					uart_report_port(drv, port);

					/* Power up port for set_mctrl() */
					uart_change_pm(state, UART_PM_STATE_ON);

					/*
					 * Ensure that the modem control lines are de-activated.
					 * keep the DTR setting that is set in uart_set_options()
					 * We probably don't need a spinlock around this, but
					 */
					spin_lock_irqsave(&port->lock, flags);
					port->ops->set_mctrl(port, port->mctrl & TIOCM_DTR);
					spin_unlock_irqrestore(&port->lock, flags);

					/*
					 * If this driver supports console, and it hasn't been
					 * successfully registered yet, try to re-register it.
					 * It may be that the port was not available.
					 */
					if (port->cons && !(port->cons->flags & CON_ENABLED))
						register_console(port->cons);	// 一个大函数

					/*
					 * Power down all ports by default, except the
					 * console if we have one.
					 */
					if (!uart_console(port))
						uart_change_pm(state, UART_PM_STATE_OFF);
				}
			}


			port->console = uart_console(uport);

			num_groups = 2;
			if (uport->attr_group)
				num_groups++;

			uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups), GFP_KERNEL);
			
			uport->tty_groups[0] = &tty_dev_attr_group;
			if (uport->attr_group)
				uport->tty_groups[1] = uport->attr_group;

			/*
			 * Register the port whether it's detected or not.  This allows
			 * setserial to be used to alter this port's parameters.
			 */
			tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver, uport->line, uport->dev, port, uport->tty_groups);
			=>struct device *tty_port_register_device_attr_serdev(struct tty_port *port,
					struct tty_driver *driver, unsigned index,
					struct device *device, void *drvdata,
					const struct attribute_group **attr_grp)
			{
				struct device *dev;

				tty_port_link_device(port, driver, index);
				=>void tty_port_link_device(struct tty_port *port, struct tty_driver *driver, unsigned index)
				{
					driver->ports[index] = port;
				}

				dev = serdev_tty_port_register(port, device, driver, index);
				=>struct device *serdev_tty_port_register(struct tty_port *port, struct device *parent, struct tty_driver *drv, int idx)
				{
					struct serdev_controller *ctrl = serdev_controller_alloc(parent, sizeof(struct serport));
					struct serport *serport = serdev_controller_get_drvdata(ctrl);
					int ret;

					serport->port = port;
					serport->tty_idx = idx;
					serport->tty_drv = drv;

					ctrl->ops = &ctrl_ops;
					==>const struct serdev_controller_ops ctrl_ops = {
						.write_buf = ttyport_write_buf,
						.write_flush = ttyport_write_flush,
						.write_room = ttyport_write_room,
						.open = ttyport_open,
						.close = ttyport_close,
						.set_flow_control = ttyport_set_flow_control,
						.set_parity = ttyport_set_parity,
						.set_baudrate = ttyport_set_baudrate,
						.wait_until_sent = ttyport_wait_until_sent,
						.get_tiocm = ttyport_get_tiocm,
						.set_tiocm = ttyport_set_tiocm,
					};

					port->client_ops = &client_ops;
					==>const struct tty_port_client_operations client_ops = {
						.receive_buf = ttyport_receive_buf,
						.write_wakeup = ttyport_write_wakeup,
					};
					
					port->client_data = ctrl;

					ret = serdev_controller_add(ctrl);

					dev_info(&ctrl->dev, "tty port %s%d registered\n", drv->name, idx);
					return &ctrl->dev;
				}


				return tty_register_device_attr(driver, index, device, drvdata, attr_grp);
				=>struct device *tty_register_device_attr(struct tty_driver *driver,
								   unsigned index, struct device *device,
								   void *drvdata,
								   const struct attribute_group **attr_grp)
				{
					char name[64];
					dev_t devt = MKDEV(driver->major, driver->minor_start) + index;
					struct ktermios *tp;
					struct device *dev;
					int retval;

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

					dev = kzalloc(sizeof(*dev), GFP_KERNEL);

					dev->devt = devt;
					dev->class = tty_class;
					dev->parent = device;
					dev->release = tty_device_create_release;
					dev_set_name(dev, "%s", name);
					dev->groups = attr_grp;
					dev_set_drvdata(dev, drvdata);

					dev_set_uevent_suppress(dev, 1);

					retval = device_register(dev);

					if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {	// 走这个流程
						/*
						 * Free any saved termios data so that the termios state is
						 * reset when reusing a minor number.
						 */
						tp = driver->termios[index];
						if (tp) {
							driver->termios[index] = NULL;
							kfree(tp);
						}

						retval = tty_cdev_add(driver, devt, index, 1);	// 注册设备节点
						if (retval)
							goto err_del;
					}

					dev_set_uevent_suppress(dev, 0);
					kobject_uevent(&dev->kobj, KOBJ_ADD);	// 用户态创建设备节点并且加入到sys文件系统

					return dev;
				}
			}
			
			if (likely(!IS_ERR(tty_dev))) {
				device_set_wakeup_capable(tty_dev, 1);
			}

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

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

			return ret;
		}


		return ret;
	}

	
}

参考文档

1 linux设备驱动(20)设备树详解4-kernel解析dts
https://www.cnblogs.com/xinghuo123/p/12977093.html

2 tty驱动框架分析
https://blog.csdn.net/qq_37600027/article/details/84504042

3 Linux设备模型之tty驱动架构分析
https://blog.csdn.net/pan0755/article/details/51693178/

4 浅析usb转serial串口设备在linux内核中枚举创建及生成tty设备的全过程
http://blog.chinaunix.net/uid-23869969-id-2654949.html

5 linux的串口驱动分析
https://www.cnblogs.com/chd-zhangbo/p/5410336.html

6 Linux驱动之串口(UART)
https://www.cnblogs.com/big-devil/p/8590050.html

7 linux设备模型之uart驱动架构分析
http://www.eepw.com.cn/article/201610/305916.htm

8 ARM平台AMBA总线uart驱动和console初始化
https://blog.csdn.net/vince_/article/details/104660642

9 linux pl011串口简述
https://blog.csdn.net/flfihpv259/article/details/53759069

10 ARM AMBA 外围设备 的datasheet
https://www.cnblogs.com/pengdonglin137/articles/12179834.html

11 PrimeCell UART (PL011) Technical Reference Manual
https://developer.arm.com/documentation/ddi0183/g/Babhjagh

<think>好的,用户的问题是关于在编译Linux 4.19-rc3内核后,使用U-Boot引导时卡在“Starting kernel ...”的情况,特别是针对mini2440开发板。我需要一步步分析可能的原因和解决方案。 首先,回忆一下这个问题的常见原因。卡在“Starting kernel ...”通常意味着内核启动过程中遇到了严重错误,可能无法正确初始化硬件或跳转到内核入口点。可能的原因包括内核配置错误、设备树(DTB)问题、U-Boot参数设置不当、内核镜像格式或加载地址错误,以及串口驱动或早期控制台配置问题。 接下来,需要具体针对mini2440的情况来考虑。mini2440是基于ARM9的处理器,属于较旧的硬件,可能有一些特定的配置需求。例如,内核配置中需要包含对S3C2440处理器的支持,以及相关的驱动串口驱动、NAND Flash驱动等。同时,设备树是否正确生成和加载也是关键,因为较新的内核版本可能依赖设备树来配置硬件。 然后,检查U-Boot的环境变量,尤其是bootcmd和bootargs。确保bootargs中的控制台参数正确指定了串口设备和波特率,比如console=ttySAC0,115200。另外,内核镜像的加载地址是否正确,是否与U-Boot的加载地址一致,例如是否从0x30008000启动。 内核镜像的格式也是一个需要注意的点。旧版U-Boot可能需要使用uImage格式,而不是zImage,因为uImage包含U-Boot的头部信息。需要确认生成的是否为正确的uImage,并且使用mkimage工具处理过。 此外,串口驱动的兼容性问题可能导致控制台输出无法显示,进而看起来像是内核卡住。需要确保内核配置中启用了正确的串口驱动,比如S3C2440的串口驱动,并且设备树中正确配置了串口节点。 可能的解决步骤包括:检查内核配置,确保处理器和必要驱动启用;验证设备树的正确性;检查U-Boot环境变量;确认内核镜像格式和加载地址;尝试启用早期调试选项,如earlyprintk和earlycon;检查硬件连接,如串口线是否正确。 最后,如果以上步骤无法解决,可能需要查阅社区或类似问题的经验,看看是否有特定的补丁或配置调整适用于mini2440和这个内核版本的组合。可能需要启用更多的调试选项,甚至逐步跟踪内核启动过程,以确定具体的故障点。</think>针对Linux 4.19-rc3内核在mini2440开发板上U-Boot引导卡在"Starting kernel..."的问题,以下是系统性排查指南: --- ### 一、常见原因分析 1. **内核配置问题** - 缺少S3C2440处理器支持 - 未启用关键驱动(如NAND Flash控制器、DM9000网卡) - 控制台串口驱动未正确配置(`CONFIG_SERIAL_S3C24XX=y`) 2. **设备树(DTB)问题** - 未生成或加载正确的设备树二进制文件(如`s3c2440-mini2440.dtb`) - 设备树中串口/内存节点配置错误 3. **U-Boot参数错误** - 内核加载地址不正确(应匹配`CONFIG_S3C2440_PHYS_OFFSET=0x30000000`) - `bootargs`未正确设置控制台参数: ```bash setenv bootargs console=ttySAC0,115200 root=/dev/mtdblock3 rootfstype=jffs2 ``` 4. **镜像格式问题** - U-Boot可能需要`uImage`而非`zImage` - 未使用`mkimage`添加U-Boot头: ```bash mkimage -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -n Linux-4.19.0 -d zImage uImage ``` --- ### 二、关键调试步骤 1. **验证U-Boot环境变量** ```bash # 确认加载地址与内核配置一致 setenv loadaddr 0x30008000 # 确认bootm命令参数 setenv bootcmd 'nand read ${loadaddr} 0x60000 0x300000; bootm ${loadaddr}' ``` 2. **启用早期调试输出** 在内核配置中: ```bash CONFIG_DEBUG_LL=y # 启用低级调试 CONFIG_EARLY_PRINTK=y # 早期控制台输出 CONFIG_DEBUG_UNCACHED=y # 避免缓存问题 ``` 在设备树中添加: ```dts chosen { bootargs = "earlycon=s3c2410,0x50000000 console=ttySAC0,115200"; }; ``` 3. **检查内存映射** - 确认内核`phys_offset`与U-Boot一致 - 验证`CONFIG_PAGE_OFFSET=0xC0000000`(ARM32标准配置) --- ### 三、硬件兼容性注意 1. **NAND Flash分区表** ```bash # 典型mini2440分区布局 setenv mtdparts mtdparts=nand:256k@0(bootloader),128k(params),5m(kernel),-(rootfs) ``` 2. **串口连接验证** - 确认使用开发板上的`ttySAC0`(非ttyS0) - 检查波特率是否匹配(115200 8N1) --- ### 四、测试建议 1. **最小化内核测试** ```bash # 编译仅含基本功能的内核 make menuconfig # 禁用模块化:取消CONFIG_MODULES # 仅保留以下驱动: CONFIG_ARM_MINI2440=y CONFIG_MTD_NAND_S3C2410=y CONFIG_FB_S3C2410=y ``` 2. **使用JTAG调试** 如果仍无法启动,可通过OpenOCD+JTAG: ```bash halt arm7_9 sw_bkpts enable load_image uImage 0x30008000 resume 0x30008000 ``` --- ### 五、已知补丁需求 该内核版本可能需要针对mini2440的补丁: ```bash # 应用S3C24XX DMA修复补丁 git apply 0001-ARM-s3c24xx-fix-dma-subsystem-initialization.patch ``` 建议参考mini2440社区维护的分支: [https://github.com/legacy-embedded/linux/tree/mini2440](https://github.com/legacy-embedded/linux/tree/mini2440) --- 通过以上步骤逐步排查,80%的问题可定位到设备树配置或U-Boot参数错误。若仍无法解决,建议捕获启动时的JTAG调试信息或使用示波器检测CPU复位信号。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值