OTG驱动分析(一)

简介: <span style="color:rgb(102, 102, 102);"></span><p>前一段时间弄了2个礼拜的OTG驱动调试,感觉精神疲惫啊。主要原因还是自己对OTG功能不了解造成的。现在终于完成但是对实质原理还有些模糊。所以自己重新总结一下。因为自己是菜鸟,所以用菜鸟的白话方式分析。高手滤过吧。 所谓OTG功能就是具备该功能的设备即可当主设备(host)去轮询别人,也可以当从设

前一段时间弄了2个礼拜的OTG驱动调试,感觉精神疲惫啊。主要原因还是自己对OTG功能不了解造成的。现在终于完成但是对实质原理还有些模糊。所以自己重新总结一下。因为自己是菜鸟,所以用菜鸟的白话方式分析。高手滤过吧。 所谓OTG功能就是具备该功能的设备即可当主设备(host)去轮询别人,也可以当从设备(device)去被别人轮~~(双性人?)。正所谓所有的产品和功能都是因为需求存在的,举个最简单的需求,原来MP3想传送一个歌曲都得通过电脑。现在只要两个MP3链接,其中一个MP3有OTG功能作为主设备(相当于电脑主机),然后另外一个是从设备就可以实现数据的传送了。 那么话说回来,具有OTG功能的设备如何确定自己是主还是从设备那。原来原来USB接口上有4个管脚,OTG功能有5个。原来4个分别是电 D+ D- 地。 现在增加了一个ID。这个ID线就决定了自己做主设备还是从设备。如果ID线是高则自己是从设备,反之是主设备。

 

 

下面开始分析代码。
 向平时一样定义platform_device资源等信息。 
定义platform_device结构
 static struct platform_device __maybe_unused dr_otg_device = 
{ .name = "fsl-usb2-otg", //设备的名称 日后匹配用 

.id = -1, //只有一个这样的设备 

.dev = { .release = dr_otg_release, 
.dma_mask = &dr_otg_dmamask, 
.coherent_dma_mask = 0xffffffff,
 },
 .resource = otg_resources, //设备的资源 看下面

 .num_resources = ARRAY_SIZE(otg_resources), 
}; 

定义platform_device下的struct resource设备资源结构
static struct resource otg_resources[] = { 
[0] = { 
.start = (u32)(USB_OTGREGS_BASE), //描述设备实体在cpu总线上的线性起始物理地址

.end = (u32)(USB_OTGREGS_BASE + 0x1ff), //描述设备实体在cpu总线上的线性结尾物理地址 

.flags = IORESOURCE_MEM, }, 
[1] = { 
.start = MXC_INT_USB_OTG, //中断号 

.flags = IORESOURCE_IRQ, }, 
};

 

定义平台设备私有数据,以后驱动要使用
static struct fsl_usb2_platform_data __maybe_unused dr_utmi_config = {
.name = "DR", 
.platform_init = usbotg_init, 
.platform_uninit = usbotg_uninit, 
.phy_mode = FSL_USB2_PHY_UTMI_WIDE, 
.power_budget = 500, /* via RT9706 */
.gpio_usb_active = gpio_usbotg_utmi_active, 
.gpio_usb_inactive = gpio_usbotg_utmi_inactive, 
.transceiver = "utmi", 
.wake_up_enable = _wake_up_enable, 
}; 
#define PDATA (&dr_utmi_config) 定义platform_device下的DEV设备下的平台私有数据(就是该设备私有的数据)


static inline void dr_register_otg(void) {
 PDATA->operating_mode = FSL_USB2_DR_OTG; //将模式更改(上面定义的时候定义的是FSL_USB2_PHY_UTMI_WIDE,不知道为什么开始不定义这个,可能是为了兼容) 

dr_otg_device.dev.platform_data = PDATA; //该设备的私有数据赋值,就是上面定义的dr_utmi_config 

if (platform_device_register(&dr_otg_device))
 printk(KERN_ERR "usb: can't register otg device\n");
else 
printk(KERN_INFO "usb: DR OTG registered\n"); 
} 

 

上面几个过程主要是完成了设备的注册。这个过程是: 
1.定义platform_device结构。 
2.定义platform_device下的struct resource设备资源结构 
3.定义platform_device下的DEV设备下的平台私有数据(就是该设备私有的数据) 
4.调用platform_device_register将platform_device结构
注册上面4个过程调用结束后,设备的信息就被注册到系统中,等待驱动的使用

 

下面分析驱动和设备的链接过程

定义platform_driver结构 
struct platform_driver fsl_otg_driver = { 
.probe = fsl_otg_probe, //定义处理函数,该函数在设备名字匹配到后调用,也就是发现该驱动对应的设备在系统中注册过。 

.remove = fsl_otg_remove, 
.driver = { 
.name = "fsl-usb2-otg", //通过该名字匹配开始注册进系统的设备 

.owner = THIS_MODULE, 
}, 
}; 
将platform_driver结构注册进系统,系统通过注册名字匹配该设备是否已经在系统中,如果在调用注册的probe = fsl_otg_probe函数 
static int __init fsl_usb_otg_init(void) 
{ 
printk(KERN_INFO DRIVER_DESC " loaded, %s\n", DRIVER_VERSION); 
return platform_driver_register(&fsl_otg_driver); 
}


 


调用fsl_otg_probe函数,函数参数platform_device *pdev,就是我们上面注册进系统的platform_device结构,现在由系统赋值调用fsl_otg_probe

 

static int __init fsl_otg_probe(struct platform_device *pdev)
{
    int status;
    struct fsl_usb2_platform_data *pdata;

    DBG("pdev=0x%p\n", pdev);

    if (!pdev)
        return -ENODEV;
/*
判断是否有设备自己的数据,就是检查我们上面定义的3的过程*/
    if (!pdev->dev.platform_data)
        return -ENOMEM;

    pdata = pdev->dev.platform_data;

    /* configure the OTG */
    status = fsl_otg_conf(pdev);
    if (status) {
        printk(KERN_INFO "Couldn't init OTG module\n");
        return -status;
    }

    /* start OTG */
    status = usb_otg_start(pdev);

    if (register_chrdev(FSL_OTG_MAJOR, FSL_OTG_NAME, &otg_fops)) {
        printk(KERN_WARNING FSL_OTG_NAME
         ": unable to register FSL OTG device\n");
        return -EIO;
    }

    create_proc_file();
    return status;
}


上面函数中调用了fsl_otg_conf,我们来看看他干了什么。

 

static int fsl_otg_conf(struct platform_device *pdev)
{
    int status;
    struct fsl_otg *fsl_otg_tc;
    struct fsl_usb2_platform_data *pdata;

    pdata = pdev->dev.platform_data;

    DBG();
/**************************************************************/

struct fsl_otg {
 struct otg_transceiver otg;
 struct otg_fsm fsm;
 struct usb_dr_mmap *dr_mem_map;
 struct delayed_work otg_event;

 /*used for usb host */
 struct work_struct work_wq;
 u8 host_working;

 int irq;
};

/**************************************************************/
    if (fsl_otg_dev)
        return 0;

    /* allocate space to fsl otg device */
    fsl_otg_tc = kzalloc(sizeof(struct fsl_otg), GFP_KERNEL);
    if (!fsl_otg_tc)
        return -ENODEV;

    INIT_DELAYED_WORK(&fsl_otg_tc->otg_event, fsl_otg_event);

    INIT_LIST_HEAD(&active_timers);
    status = fsl_otg_init_timers(&fsl_otg_tc->fsm);
    if (status) {
        printk(KERN_INFO "Couldn't init OTG timers\n");
        fsl_otg_uninit_timers();
        kfree(fsl_otg_tc);
        return status;
    }
    spin_lock_init(&fsl_otg_tc->fsm.lock);

    /* Set OTG state machine operations */

/**************************************************************/

static struct otg_fsm_ops fsl_otg_ops = {
 .chrg_vbus = fsl_otg_chrg_vbus,
 .drv_vbus = fsl_otg_drv_vbus,
 .loc_conn = fsl_otg_loc_conn,
 .loc_sof = fsl_otg_loc_sof,
 .start_pulse = fsl_otg_start_pulse,

 .add_timer = fsl_otg_add_timer,
 .del_timer = fsl_otg_del_timer,

 .start_host = fsl_otg_start_host,
 .start_gadget = fsl_otg_start_gadget,
};

/**************************************************************/
    fsl_otg_tc->fsm.ops = &fsl_otg_ops;

    /* initialize the otg structure */
    fsl_otg_tc->otg.label = DRIVER_DESC;
    fsl_otg_tc->otg.set_host = fsl_otg_set_host;
    fsl_otg_tc->otg.set_peripheral = fsl_otg_set_peripheral;
    fsl_otg_tc->otg.set_power = fsl_otg_set_power;
    fsl_otg_tc->otg.start_hnp = fsl_otg_start_hnp;
    fsl_otg_tc->otg.start_srp = fsl_otg_start_srp;

    fsl_otg_dev = fsl_otg_tc;

    /* Store the otg transceiver */

/***************************************************************/

int otg_set_transceiver(struct otg_transceiver *x)
{
 if (xceiv && x)
  return -EBUSY;
 xceiv = x;
 return 0;
}

该函数就是将struct otg_transceiver结构副给一个全局变量保存,供以后使用,以后会通过调用下面函数得到该结构

struct otg_transceiver *otg_get_transceiver(void)
{
 if (xceiv)
  get_device(xceiv->dev);
 return xceiv;
}

/***************************************************************/
    status = otg_set_transceiver(&fsl_otg_tc->otg);
    if (status) {
        printk(KERN_WARNING ": unable to register OTG transceiver.\n");
        return status;
    }

    return 0;
}

 

 


 

int usb_otg_start(struct platform_device *pdev)
{
    struct fsl_otg *p_otg;

/*获得otg_transceiver结构*/
    struct otg_transceiver *otg_trans = otg_get_transceiver();
    struct otg_fsm *fsm;
    volatile unsigned long *p;
    int status;
    struct resource *res;
    u32 temp;

/*获得设备的私有数据*/
    struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data;
/*使用container_of宏定义可以通过结构中一个变量的指针获得该结构首地址*/
    p_otg = container_of(otg_trans, struct fsl_otg, otg);
    fsm = &p_otg->fsm;

    /* Initialize the state machine structure with default values */
    SET_OTG_STATE(otg_trans, OTG_STATE_UNDEFINED);
    fsm->transceiver = &p_otg->otg;

    /* We don't require predefined MEM/IRQ resource index */

/*获得设备的资源,是在设备注册时结构体里面的内容*/
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res)
        return -ENXIO;

    /* We don't request_mem_region here to enable resource sharing
     * with host/device */

/*通过资源中获得的物理地址映射一个可以被驱动访问的虚拟地址指针*/
    usb_dr_regs = ioremap(res->start, sizeof(struct usb_dr_mmap));

/*将该指针保存到p_otg->dr_mem_map中*/
    p_otg->dr_mem_map = (struct usb_dr_mmap *)usb_dr_regs;
    pdata->regs = (void *)usb_dr_regs;

    /* request irq */

/*获得设备注册时候的中断并注册,在OTG ID发生变化时触发中断,然后调用注册的中断例程函数,函数后面分析*/
    p_otg->irq = platform_get_irq(pdev, 0);
    status = request_irq(p_otg->irq, fsl_otg_isr,
                IRQF_SHARED, driver_name, p_otg);
    if (status) {
        dev_dbg(p_otg->otg.dev, "can't get IRQ %d, error %d\n",
            p_otg->irq, status);
        iounmap(p_otg->dr_mem_map);
        kfree(p_otg);
        return status;
    }

    if (pdata->platform_init && pdata->platform_init(pdev) != 0)
        return -EINVAL;


    /* Export DR controller resources */

/**************************************************/

int otg_set_resources(struct resource *resources)
{
 otg_resources = resources;
 return 0;
}

otg_set_transceiver功能类似将设备资源保存到一个全局变量中

/**************************************************/
    otg_set_resources(pdev->resource);
/*开始配置USB寄存器*/
    /* stop the controller */
    temp = readl(&p_otg->dr_mem_map->usbcmd);
    temp &= ~USB_CMD_RUN_STOP;
    writel(temp, &p_otg->dr_mem_map->usbcmd);

    /* reset the controller */
    temp = readl(&p_otg->dr_mem_map->usbcmd);
    temp |= USB_CMD_CTRL_RESET;
    writel(temp, &p_otg->dr_mem_map->usbcmd);

    /* wait reset completed */
    while (readl(&p_otg->dr_mem_map->usbcmd) & USB_CMD_CTRL_RESET) ;

    /* configure the VBUSHS as IDLE(both host and device) */
    temp = USB_MODE_STREAM_DISABLE | (pdata->es ? USB_MODE_ES : 0);
    writel(temp, &p_otg->dr_mem_map->usbmode);

    /* configure PHY interface */
    temp = readl(&p_otg->dr_mem_map->portsc);
    temp &= ~(PORTSC_PHY_TYPE_SEL | PORTSC_PTW);
    switch (pdata->phy_mode) {
    case FSL_USB2_PHY_ULPI:
        temp |= PORTSC_PTS_ULPI;
        break;
    case FSL_USB2_PHY_UTMI_WIDE:
        temp |= PORTSC_PTW_16BIT;
        /* fall through */
    case FSL_USB2_PHY_UTMI:
        temp |= PORTSC_PTS_UTMI;
        /* fall through */
    default:
        break;
    }
    writel(temp, &p_otg->dr_mem_map->portsc);

    if (pdata->have_sysif_regs) {
        /* configure control enable IO output, big endian register */
        p = (volatile unsigned long *)(&p_otg->dr_mem_map->control);
        temp = *p;
        temp |= USB_CTRL_IOENB;
        *= temp;
    }

    /* disable all interrupt and clear all OTGSC status */
    temp = readl(&p_otg->dr_mem_map->otgsc);
    temp &= ~OTGSC_INTERRUPT_ENABLE_BITS_MASK;
    temp |= OTGSC_INTERRUPT_STATUS_BITS_MASK | OTGSC_CTRL_VBUS_DISCHARGE;
    writel(temp, &p_otg->dr_mem_map->otgsc);


    /*
     * The identification (id) input is FALSE when a Mini-A plug is inserted
     * in the devices Mini-AB receptacle. Otherwise, this input is TRUE.
     * Also: record initial state of ID pin
     */

    if (le32_to_cpu(p_otg->dr_mem_map->otgsc) & OTGSC_STS_USB_ID) {
        p_otg->otg.state = OTG_STATE_UNDEFINED;
        p_otg->fsm.id = 1;
    } else {
        p_otg->otg.state = OTG_STATE_A_IDLE;
        p_otg->fsm.id = 0;
    }

    DBG("initial ID pin=%d\n", p_otg->fsm.id);

    /* enable OTG ID pin interrupt */
    temp = readl(&p_otg->dr_mem_map->otgsc);
    temp |= OTGSC_INTR_USB_ID_EN;
    temp &= ~(OTGSC_CTRL_VBUS_DISCHARGE | OTGSC_INTR_1MS_TIMER_EN);
    writel(temp, &p_otg->dr_mem_map->otgsc);

    return 0;
}

 

下面分析下 中断例程函数

该函数就是判断ID的高低,也就是自己做主设备还是从设备

irqreturn_t fsl_otg_isr(int irq, void *dev_id)
{
    struct otg_fsm *fsm = &((struct fsl_otg *)dev_id)->fsm;
    struct otg_transceiver *otg = &((struct fsl_otg *)dev_id)->otg;
    u32 otg_int_src, otg_sc;
/*获得ID的变化信息*/
    otg_sc = le32_to_cpu(usb_dr_regs->otgsc);
    otg_int_src = otg_sc & OTGSC_INTSTS_MASK & (otg_sc >> 8);

    /* Only clear otg interrupts */
    usb_dr_regs->otgsc |= cpu_to_le32(otg_sc & OTGSC_INTSTS_MASK);

    /*FIXME: ID change not generate when init to 0 */
    fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0;
    otg->default_a = (fsm->id == 0);

    /* process OTG interrupts */
    if (otg_int_src) {
        if (otg_int_src & OTGSC_INTSTS_USB_ID) {
            fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0;
            otg->default_a = (fsm->id == 0);
            /* clear conn information */
            if (fsm->id)
                fsm->b_conn = 0;
            else
                fsm->a_conn = 0;

            if (otg->host)
                otg->host->is_b_host = fsm->id;
            if (otg->gadget)
                otg->gadget->is_a_peripheral = !fsm->id;
            VDBG("ID int (ID is %d)\n", fsm->id);

            if (fsm->id) {    /* switch to gadget *///从设备

/*schedule_delayed_work函数先停止主设备后打开从设备*/

/***************************************************/

schedule_delayed_work(&((struct fsl_otg *)
                            dev_id)->otg_event,
                            100);

函数就是延迟100秒调用otg_event,就是下面函数。

static void fsl_otg_event(struct work_struct *work)
{
 struct fsl_otg *og = container_of(work, struct fsl_otg, otg_event.work);
 struct otg_fsm *fsm = &og->fsm;

 if (fsm->id) {  /* switch to gadget */
  fsl_otg_start_host(fsm, 0);
  otg_drv_vbus(fsm, 0);
  fsl_otg_start_gadget(fsm, 1);
 }
}

/***************************************************/
                schedule_delayed_work(&((struct fsl_otg *)
                            dev_id)->otg_event,
                            100);
            } else {    /* switch to host *///主设备
                cancel_delayed_work(&
                         ((struct fsl_otg *)dev_id)->
                         otg_event);
                fsl_otg_start_gadget(fsm, 0);//停止从设备
                otg_drv_vbus(fsm, 1);
                fsl_otg_start_host(fsm, 1);//打开主
            }

            return IRQ_HANDLED;
        }
    }

    return IRQ_NONE;
}


int fsl_otg_start_host(struct otg_fsm *fsm, int on)
{
    struct otg_transceiver *xceiv = fsm->transceiver;
    struct device *dev;
    struct fsl_otg *otg_dev = container_of(xceiv, struct fsl_otg, otg);
    struct platform_driver *host_pdrv;
    struct platform_device *host_pdev;
    u32 retval = 0;
/*判断是否有主设备的驱动注册进系统*/
    if (!xceiv->host)
        return -ENODEV;
    dev = xceiv->host->controller;

/*找到主设备驱动的platform_driver结构,为下面的停止和恢复函数调用做准备*/
    host_pdrv = container_of((dev->driver), struct platform_driver, driver);
    host_pdev = to_platform_device(dev);

    /* Update a_vbus_vld state as a_vbus_vld int is disabled
     * in device mode
     */

    fsm->a_vbus_vld =
     (le32_to_cpu(usb_dr_regs->otgsc) & OTGSC_STS_A_VBUS_VALID) ? 1 : 0;
    if (on) {
        /* start fsl usb host controller */
        if (otg_dev->host_working)
            goto end;
        else {
            otg_reset_controller();
            VDBG("host on......\n");
            if (host_pdrv->resume) {
                retval = host_pdrv->resume(host_pdev);
                if (fsm->id) {
                    /* default-b */
                    fsl_otg_drv_vbus(1);
                    /* Workaround: b_host can't driver
                     * vbus, but PP in PORTSC needs to
                     * be 1 for host to work.
                     * So we set drv_vbus bit in
                     * transceiver to 0 thru ULPI. */

#if defined(CONFIG_ISP1504_MXC)
                    write_ulpi(0x0c, 0x20);
#endif
                }
            }

            otg_dev->host_working = 1;
        }
    } else {
        /* stop fsl usb host controller */
        if (!otg_dev->host_working)
            goto end;
        else {
            VDBG("host off......\n");
            if (host_pdrv->suspend) {
                retval = host_pdrv->suspend(host_pdev,
                            otg_suspend_state);
                if (fsm->id)
                    /* default-b */
                    fsl_otg_drv_vbus(0);
            }
            otg_dev->host_working = 0;
        }
    }
end:
    return retval;
}

可以看到最后设备是使用还是停止调用的函数分别是

host_pdrv->suspend

host_pdrv->resume

而上面两个指针的函数赋值是在主设备驱动中完成的。


int fsl_otg_start_gadget(struct otg_fsm *fsm, int on)
{
    struct otg_transceiver *xceiv = fsm->transceiver;
    struct device *dev;
    struct platform_driver *gadget_pdrv;
    struct platform_device *gadget_pdev;
/*判断是否有从设备驱动注册*/
    if (!xceiv->gadget || !xceiv->gadget->dev.parent)
        return -ENODEV;

    VDBG("gadget %s \n", on ? "on" : "off");
    dev = xceiv->gadget->dev.parent;
/*找到从设备驱动的platform_driver结构首地址,为下面调用其提供的功能函数做准备*/
    gadget_pdrv = container_of((dev->driver),
            struct platform_driver, driver);
    gadget_pdev = to_platform_device(dev);

    if (on)
        gadget_pdrv->resume(gadget_pdev);
    else
        gadget_pdrv->suspend(gadget_pdev, otg_suspend_state);

    return 0;
}

和上面主设备一样

到底是从设备停止还是恢复是调用
        gadget_pdrv-
>
resume(gadget_pdev);
        gadget_pdrv->suspend(gadget_pdev, otg_suspend_state);

上面两个函数的指针就是在从设备驱动注册时链接的。

上面部分就是 OTG功能的 OTG驱动部分。 OTG功能还要有做主设备使用的主设备驱动和做从设备的从设备驱动。

从上面代码分析我们归纳出流程:

分两个大部分:

一 设备的注册  其中包括

1.定义platform_device结构。 
2.定义platform_device下的struct resource设备资源结构 
3.定义platform_device下的DEV设备下的平台私有数据(就是该设备私有的数据) 
4.调用platform_device_register将platform_device结构

二 OTG驱动的注册 其中包括

1.struct platform_driver fsl_otg_driver 结构的注册

2.匹配到有设备存在时调用的PORE函数,对设备进行初始化设置和功能函数的绑定

3.完成中断函数的绑定和中断例程的注册。

 

经过上面的处理后,只要OTG ID的变化就会触发中断,调用中断例程函数,决定是调用主设备还是从设备驱动。 而主设备和从设备驱动和OTG调用的链接是分别在主从设备驱动中完成的。后面我们介绍主从设备驱动中会介绍到。

 在文章的最后想起来这次调OTG遇见的问题,分享给大家希望大家有帮助。我调试OTG时,开始将OTG编译到内核中。(Y)。结果插入U盘没有反应。后来发现原来我加入内核后,主设备驱动的先OTG设备驱动被执行,造成主设备函数和OTG功能的链接出现问题。(应该是OTG先初始化 然后从和主设备驱动链接。)后来我使用模块方式编译OTG功能。按照先载入OTG后载入从和主设备。(insmod方式),结果OTG就可以使用了。 后来通过降低主设备的优先级方式,把OTG编译进内核,然后因为主设备优先级低所以最后被调用。 也就是在主设备注册那使用
late_initcall(ehci_hcd_init);代替//module_init(ehci_hcd_init);。这样主设备的优先级就低于设备驱动的优先级就在驱动加载完加载了。 但是总感觉这样不是很合理的方式,如果有朋友有更好的办法请指教。

相关文章
|
1月前
|
传感器 芯片
PCF8574芯片介绍及驱动方法
PCF8574芯片介绍及驱动方法
158 0
|
1月前
【STM32】通过RTThread驱动W25QXXX
【STM32】通过RTThread驱动W25QXXX
|
8月前
|
存储 机器人 芯片
嵌入式 STM32 步进电机驱动,干货满满,建议收藏
嵌入式 STM32 步进电机驱动,干货满满,建议收藏
嵌入式 STM32 步进电机驱动,干货满满,建议收藏
|
10月前
|
传感器 数据采集 SDN
STM32(HAL库)驱动AD8232心率传感器
STM32(HAL库)驱动AD8232心率传感器
|
10月前
|
存储 传感器 物联网
STM32(HAL)驱动RFID模块(ATS522)
STM32(HAL)驱动RFID模块(ATS522)
|
11月前
CMOS摄像头驱动分析-i2c驱动
CMOS摄像头驱动分析-i2c驱动
137 0
|
11月前
|
传感器 Linux
总线驱动---IIC驱动(下)
总线驱动---IIC驱动
65 0
|
11月前
|
传感器 算法 Linux
总线驱动---IIC驱动(上)
总线驱动---IIC驱动
96 0
|
11月前
|
存储 Linux
uvc驱动中的v4l2
uvc驱动中的v4l2
95 0
|
传感器 Linux
linux驱动——dht11温湿度传感器驱动(5.4版本内核)
linux驱动——dht11温湿度传感器驱动(5.4版本内核)
463 0

热门文章

最新文章