QQ登录

只需一步,快速开始

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 4875|回复: 5

虚拟平台嵌入式系统开源项目包括eCos RTEMS OKL4

[复制链接]
发表于 2011-4-2 11:29:33 | 显示全部楼层 |阅读模式
1. 嵌入式实验室的创建
本人是一个嵌入式系统的爱好者,移植和应用过多种嵌入式系统。工作以来主要从事WinCE,Linux和Android的开发。
一直有一个梦想,希望像我一样的嵌入式系统爱好和工作者,能够在没有硬件的条件下去了解和开发这样的系统。
现在,自由的操作系统层出不穷,对移植和开发的文档却依然晦涩难懂。我希望我们能在这样的虚拟的平台上(比如我现在专注的
mini2440 QEMU)把我们的每一次移植都能够图文并茂的通过docbook 记录在案,这将能更大的提升自由软件文档的阅读效果。
我们不专注于系统本身,但是我们专注于这样的虚拟应用。我们会不遗余力的把我们的文档和代码贡献给任何人。
当然也会提供给我们移植的操作系统的组织。在这样的环境中,我希望大家和我能继续共同成长。
虽然我已经过了而立之年,但是我感觉我依旧浑身充满了力量。
我建立的自由软件项目,名称是eMBosLab, 在未来的日子里,我将把我仅剩的力量投入进来。
让更多的人能够通过这样一个平台,学习甚至研究嵌入式系统,更轻松的投入到工作中。
我还会和朋友一起通过docbook的方式,记录我们的每一次成长历程。
项目主页:
https://sourceforge.net/projects/emboslab/
代码管理方式:
大部分代码采用GIT来管理
http://emboslab.git.sourceforge.net/git/gitweb-index.cgi
一些操作系统如果用hg,或者svn,我们会延续他
http://emboslab.hg.sourceforge.net/hgweb/emboslab
2. eMBosLab RTEMS:(具体细节请参考雪松的blog)
http://blog.csdn.net/coolbacon
更新内容
RTEMS 增加DM9000网卡支持查询和中断方式,网络程序测试OK
RTEMS 修正PLL bug, ticker 运行正常
RTEMS for emboslab 增加mini2440 qemu BSP
下载方法
git clone git://emboslab.git.sourceforge.net/gitroot/emboslab/rtems-4.9.5-emboslab

3. eMBosLab eCos:更新内容:

20110328 23:00
通过ecosconfig add net_drivers自动增加网卡驱动的QEMU依赖关系。
20110321 18:00
增加MINI2440 BSP源代码,包括FLASH驱动,DM9000网卡驱动极其LCD支持
20110321 10:00
eCos for emboslab 合并了yaffs和nand 模块

下载方法
hg clone hg://emboslab.hg.sourceforge.net/hgweb/emboslab/ecos-emboslab

4.eMBosLab Docs:更新内容:
20110328 23:00
docbook模板已经建好,后续的部分文档会采用英文配合图形的方式。
20110322 12:00
从ecos中的文档中,剥离出相对简单的docbook应用架构作为后续文档编写的体系结构。
                           可以直接生成html和pdf A4页面的文档
下载方法:
git clone git://emboslab.git.sourceforge.net/gitroot/emboslab/docbook-emboslab

5.网站介绍
这是主页,为了能用git和hg, 我选择了sourceforge平台
https://sourceforge.net/projects/emboslab/


这是所有的源代码:

目前已经把RTEMS for QEMU MINI2440放在了上面,希望能有更多的人参与。
这将是一个自由的技术学习和交流平台。
source code address
http://emboslab.git.sourceforge.net/git/gitweb-index.cgi


网页上也能看到我在RTEMS做的所有工作。


[ 本帖最后由 rickleaf 于 2011-4-11 09:01 编辑 ]
 楼主| 发表于 2011-4-2 11:31:23 | 显示全部楼层

ecos在mini2440 qemu的网络功能测试

终于可以方便的用mini2440 qemu的网络功能了,
大家参考一下雪松的这篇帖子
http://blog.csdn.net/coolbacon/archive/2011/03/16/6252938.aspx
既然可以这样用了,我重新整理了mini2440的redboot
1. 根据qemu对dm9000模拟的限制,对dm9000驱动程序做了新的修改


2. ecos的看家绝技,gdb调试应用程序
根据雪松的修改,现在可以用arm-eabi-gdb 的target remote来做ecos 应用程序的单步调试了
3. ecos的httpd 在mini2440 qemu的测试





[ 本帖最后由 rickleaf 于 2011-4-11 08:57 编辑 ]
回复

使用道具 举报

 楼主| 发表于 2011-4-2 11:33:51 | 显示全部楼层

Microwindows 及触摸屏实现

1. 修复MINI2440 QEMU的BUG开始的我用ecos里面仅有的两个ARM下面的touch 驱动来做一个中断测试,
都快疯了,没有任何反应。
后来看到RT-Thread的例子可以正常运行,于是才想看看他们呢对MINI2440 QEMU的修改。

真的要感谢所有自由组织的无私奉献。

果然,MINI2440的QEMU采用的是S3C2410的TOUCH接口,里面关于PenDown和PenUP的寄存器位都木有。

代码已经上传到了emboslab git 库中qemu-mini2440的ecos-emboslab 分支,这里还是简要介绍一下。

--------------------------------- hw/s3c2410.c ---------------------------------
index d1e4c92..bdf8a73 100644
@@ -1630,6 +1630,14 @@ static void s3c_adc_tick(void *opaque)
         qemu_mod_timer(s->tst, qemu_get_clock(vm_clock) +
                         (ticks_per_sec >> 5));
     }
/*
这个是QEMU实现ARM中断方式的机制
可以这样理解:如果对QEMU的窗口做Penup的时候,触发一个专断给CPU。
*/
+    else
+    {
+        if (((s->ts & 3) == 3) && (s->ts & (1<<) && (s->enable))
+            qemu_irq_raise(s->tcirq);
+
+
qemu_mod_timer(s->tst, qemu_get_clock(vm_clock) +

+                        (ticks_per_sec >> 5));
+    }
}

static void s3c_adc_event(void *opaque,
@@ -1689,7 +1697,7 @@ static void s3c_adc_write(void *opaque, target_phys_addr_t addr,
         break;

     case S3C_ADCTSC:
-        s->ts = value & 0xff;
+        s->ts = value & 0x1ff; /*增加了Pendown和Penup的控制位*/

         break;

     case S3C_ADCDLY:

2. 实现触摸屏驱动(尚有BUG)目前官方的触屏驱动有两个,我们看下
ricky@ricky-laptop:ecos-emboslab$ cd packages/devs/touch/arm/
ricky@ricky-laptop:arm$ ls
aaed2000  ipaq
如果让Microwindows支持Touch只要实现这样的一个标准的Touch驱动就可以了。
在Microwindows的部分调用这个标准的ecos字符形设备就可以了。
不过从代码看来,ecos并没有对Touch驱动有严格的API定义,所以在网上你能看到很多种实现方法。
既然Microwindows用了字符形的设备,那么我们也就按照这个架构实现一下吧。
我们先看一下ecos的字符形设备是什么样子的,具体的请看ecos的参考手册(如何生成最新的参考手册,我会在另一篇博客中讲述)
CHAR_DEVIO_TABLE(mini2440_ts_handlers,
                 NULL,                                   // Unsupported write() function
                 ts_read,
                 ts_select,
                 ts_get_config,
                 ts_set_config);

CHAR_DEVTAB_ENTRY(mini2440_ts_device,
                  CYGDAT_DEVS_TOUCH_MINI2440_NAME, /*这个是字符形设备的描述符,在CDL文件中定义"/dev/ts"和Linux的驱动文件很相似 */
                  NULL,                                   // Base device name
                  &mini2440_ts_handlers,
                  ts_init,
                  ts_lookup,  /*重点看这个函数:当设备被open的时候这个callback函数会被调用*/
                  NULL);                                  // Private data pointer
关于如何实现更好的touch架构,本人还在研究。
现在的方案是
在设备初始化的时候配置硬件资源和终端设置,
在设备被打开,也就是ts_lookup里面创建中断响应函数,把转化好的touch 数据放到队列中等待上层(Microwindows)
来读取。
看下初始化部分
static bool      
ts_init(struct cyg_devtab_entry *tab)
{
    cyg_uint32 _dummy;
    HAL_WRITE_UINT32(ADCCON,S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(9));
    HAL_WRITE_UINT32(ADCDLY,50000);
    HAL_WRITE_UINT32(ADCTSC,WAIT4INT(0));  /*这里就是使能,PenDownUP终端*/

    //HAL_WRITE_UINT32(INTMSK, BIT_ALLMSK);
    HAL_READ_UINT32(SUBSRCPND, _dummy);
    _dummy |= BIT_SUB_TC;
    _dummy |= BIT_SUB_ADC;
    HAL_WRITE_UINT32(SUBSRCPND, _dummy);

   
    cyg_drv_interrupt_acknowledge(CYGNUM_HAL_INTERRUPT_ADC);
    cyg_selinit(&ts_select_info);
    /* install interrupt handler */
    HAL_READ_UINT32(INTSUBMSK, _dummy);
    _dummy &= ~BIT_SUB_ADC;
    _dummy &= ~BIT_SUB_TC;
    HAL_WRITE_UINT32(INTSUBMSK, _dummy);
    HAL_INTERRUPT_UNMASK(CYGNUM_HAL_INTERRUPT_ADC);
    return true;
}
/*设备打开函数,创建了ADC中断的挂钩函数cyg_mini2440_ts_isr和cyg_mini2440_ts_dsr*/
    if (!_is_open) {
        _is_open = true;
        
       cyg_drv_interrupt_create(CYGNUM_HAL_INTERRUPT_ADC,
                             0,
                             (CYG_ADDRWORD)0,
                             cyg_mini2440_ts_isr,
                             cyg_mini2440_ts_dsr,
                             &ts_thread_handle,
                             &ts_thread_data);
       cyg_drv_interrupt_attach(ts_thread_handle);
       cyg_drv_interrupt_unmask(CYGNUM_HAL_INTERRUPT_ADC);
    }
    return ENOERR;
}
罗嗦两句,ecos在创建的时候就参考了很多linux的风格,比如我们看到上面的isr和dsr很像Linux对中断处理的上半和下半。
具体的实现机理本人也在研究呢,目前位置我只是知道如何应用。
isr中希望用户能快速的响应终端,在isr结束的时候往往有下面的代码
   cyg_drv_interrupt_acknowledge(CYGNUM_HAL_INTERRUPT_ADC);
上面的这行主要是在函数返回前开启这个中断
   return CYG_ISR_HANDLED | CYG_ISR_CALL_DSR;
后面这个有两个含义,第一个是告诉系统这个中断我已经知道了,做了我应该做的事情。第二个含义就是红色的部分会在函数返回之后
出发这个终端的DSR处理程序。
那么这个touch驱动到底需要还是不需要DSR呢,本人下不了结论。
本人是做Linux的,知道Linux一般的input driver一般用到后半的多是处理report机制,
为了不把问题复杂化,我们现让硬件能动起来,欢迎其他人给我建议
2440的Touch中断处理暂时比较简单
if (res& (1 << 10))/*ADC的采用的后要继续出发PenDownUP中断*/
   {
       //diag_printf("ADC Interrupt\n");
       HAL_READ_UINT32(SUBSRCPND, reg);
       reg |= BIT_SUB_ADC;
       HAL_WRITE_UINT32(SUBSRCPND, reg);
      
       x = read_ts_x();
       y = read_ts_y();
      
       lastX = x;  lastY = y;
      
       //diag_printf("X = %x, Y = %x\n", x, y);
       HAL_WRITE_UINT32(ADCTSC,WAIT4INT(1));

   }
   if (res& (1 << 9)){ /*PenDownUp中断记录Touch的按下和抬起,同时出发ADC的采样*/
       //diag_printf("TS Interrupt\n");
      
       HAL_READ_UINT32(SUBSRCPND, reg);
       reg |= BIT_SUB_TC;
       HAL_WRITE_UINT32(SUBSRCPND, reg);

       HAL_READ_UINT32(ADCDAT0, data0);
       HAL_READ_UINT32(ADCDAT1, data1);
      
     
       updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
      
       if(updown)
       {            
           x = read_ts_x();
            y = read_ts_y();
            //diag_printf("X = %x, Y = %x\n", x, y);
        //diag_printf("pen_down#######################################################################\n");
            if ((x < X_THRESHOLD) || (y < Y_THRESHOLD)) {
                // Ignore 'bad' samples
                 }
            lastX = x;  lastY = y;
            HAL_WRITE_UINT32(ADCTSC, S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST);
        HAL_READ_UINT32(ADCCON, reg);
            reg |= S3C2410_ADCCON_ENABLE_START;
            HAL_WRITE_UINT32(ADCCON, reg);
            pen_down = true;
       }
       else
       {
               x = lastX;
            y = lastY;
            HAL_WRITE_UINT32(ADCTSC,WAIT4INT(0));
            pen_down = false;
        //diag_printf("pen_up#######################################################################\n");
       }
   }

   /*把合理的数据处理后放入一个events数组中,等待上层来读取*/         
   if (num_events < MAX_EVENTS) {
            num_events++;
            ev = &_events[_event_put++];
            if (_event_put == MAX_EVENTS) {
                _event_put = 0;
            }
            ev->button_state = pen_down ? 0x04 : 0x00;
            ev->xPos = x;
            ev->yPos = y;
            if (ts_select_active) {
                ts_select_active = false;
                cyg_selwakeup(&ts_select_info);
            }
        }
回复

使用道具 举报

 楼主| 发表于 2011-4-2 11:34:47 | 显示全部楼层

续Microwindow和触屏

3. 修正eCos自带Microwindows的ecos_app错误目前发现的一个问题是,ecos之前曾经采纳了一个牛的patch,他应用HAL_TABLE实现了基于Microwindows的APP定义方式。
很巧妙,那么什么是HAL_TABLE呢,我们可以理解为为了实现更好的表的管理的一种机制,可以把我们要存放的表通过宏
放到内存中的某个位置,大家可以在google上找一下,ecos在这个部分应用还是比较有特点的。
尤其是在二维表的处理上有很多特别的应用,redboot的一些命令就是例子
ecos_mw_app.h
typedef void fun(CYG_ADDRWORD);
typedef struct _mw_app_entry {
    char         *name;
    fun          *entry;
    int          prio;
    fun          *init;
    cyg_handle_t t;
    cyg_thread   t_obj;
    char         stack[STACKSIZE];
} CYG_HAL_TABLE_TYPE _mw_app_entry_t;

#define _mw_app(_name_,_id_,_pri_,_init_)               \
externC void _id_##_thread(CYG_ADDRWORD data);          \
_mw_app_entry_t _mw_app_##_pri_##_##_id_           \
   CYG_HAL_TABLE_QUALIFIED_ENTRY(_mw_apps,_pri_) =      \
     { _name_, _id_##_thread, _pri_, _init_};

#define ECOS_MW_STARTUP_PRIORITY 11
#define ECOS_MW_NANOX_PRIORITY   (ECOS_MW_STARTUP_PRIORITY+1)
#define ECOS_MW_KND_PRIORITY     (ECOS_MW_STARTUP_PRIORITY+2)
#define ECOS_MW_NANOWM_PRIORITY  (ECOS_MW_STARTUP_PRIORITY+4)
#define ECOS_MW_APP_PRIORITY     (ECOS_MW_STARTUP_PRIORITY+5)
ecos_app.c
CYG_HAL_TABLE_BEGIN( __MW_APP_TAB__, _mw_apps );
CYG_HAL_TABLE_END( __MW_APP_TAB_END__, _mw_apps );
extern struct _mw_app_entry __MW_APP_TAB__[], __MW_APP_TAB_END__;
为什么提到这个呢,到目前位置我不能十分的确定是这个机制有问题还是QEMU本身对于内存管理的问题。
如果我在Microwindows访问我的设备文件,就是open我们之前创建的/dev/ts这个时候貌似用HAL_TABLE存放的UI线程信息就读不到了。
如果可以打message,可以看到不能创建我们要的Microwindows的一些线程,当然也就不能看到画面了。
比较简单的做法就是参考ecos之前的代码,或者干脆去看官方Microwindows的代码,用一个数组去存放线程信息。
struct nx_thread _threads[] = {
    { "System startup", startup_thread,    11 },
    { "Nano-X server",  nanox_thread,      12 },
    { "Nano-WM",        nanowm_thread,     14 },
    /*{ "Nano-KBD",       nxkbd_thread,      13 },*/
#ifdef USE_NXSCRIBBLE
    { "Scribble",       nxscribble_thread, 20 },
#endif
#ifdef USE_LANDMINE
    { "Landmine",       landmine_thread,   19 },
#endif
#ifdef USE_NTETRIS
    { "Nano-Tetris",    ntetris_thread,    18 },
#endif
#ifdef USE_WORLD
    { "World Map",      world_thread,      21 },
#endif
    { "demo",            demo_thread,      22 },
};
接下来你就看到Microwindows了,进入下一个环节。
4. Show 图咯
回复

使用道具 举报

 楼主| 发表于 2011-4-2 11:36:49 | 显示全部楼层

RTEMS网卡驱动移植

我把雪松的帖子也转过来。
鼓励大家看他的原帖子,他是这个专家。
http://blog.csdn.net/coolbacon/archive/2011/03/22/6269551.aspx
这两天抽了个空,在rickleaf移植的 qemu mini2440 的rtems 4.9.5 bsp 上做了一些修改:
1.原来mini2440的 bsp是基于 gp32的bsp修改,复用了部分的gp32代码。cpu类型实际上使用的是s3c2410。这不利于对mini2440的修改。所以,增加了新cpu型号s3c2440,并将mini2440修改成独立的bsp。
如果有朋友在bootstrap -p 和 bootstrap 时,嫌生成configure 的时间太长,建议将libbsp下和libcpu下不使用的bsp和cpu型号删除。以增加开发效率。
2.s3c2410 cpu的PLL部分和s3c2440的 PLL 还是有些许差别,造成rtems的tick不准,已经修改。
3.支持dm9000的网络。
代码请安装版本管理工具 git,使用命令:
git clone git://emboslab.git.sourceforge.net/gitroot/emboslab/rtems-4.9.5-emboslab
下载最新的 rtems 4.9.5 在 qemu mini2440 上的移植。
请键入以下命令编译rtems 4.9.5 关于 qemu mini2440 bsp:
cd rtems-4.9.5-emboslab
mkdir mini2440
cd mini2440
../configure --target=arm-rtems4.9 --disable-posix --enable-networking --enable-rtemsbsp=mini2440 --prefix=/opt/rtems-4.9
make all install
编译一下hello_world_c 和ticker等示例吧,运行这些示例的方法请参考其本博的他关于mini2440的博文。
从官方下载network-demos-4.9.4.tar.bz2
tar  xjvf network-demos-4.9.4.tar.bz2
cd network-demos-4.9.4
gedit networkconfig.h (需要修改一下改文件,下面有列出)
export RTEMS_MAKEFILE_PATH=/opt/rtems-4.9/arm-rtems4.9/mini2440/
cd http
make
arm-rtems4.9-objcopy -O binary o-optimize/http.exe /tftproot/image.bin
进入qemu文件夹:
./mini2440/mini2440_start.sh
启动qemu,
在 mini2440# 提示符下输入:
tftp 30000100 image.bin
go 30000100

[ 本帖最后由 rickleaf 于 2011-4-11 09:00 编辑 ]
回复

使用道具 举报

 楼主| 发表于 2011-4-2 11:37:38 | 显示全部楼层

RTEMS网卡驱动移植(续)

  • ping 10.0.0.100 -s 1000

  • 使用浏览器查看网页

  • 查看CPU使用率(第一个超文本链接)

  • 最后,贴上networkconfig.h文件内容:
view plaincopy to clipboardprint?

  • /*
  • * Network configuration -- LOOPBACK ONLY!!!
  • *
  • * See one of the other networkconfig.h files for an
  • * example of a system that includes a real NIC and
  • * the loopback interface.
  • *
  • ************************************************************
  • * EDIT THIS FILE TO REFLECT YOUR NETWORK CONFIGURATION     *
  • * BEFORE RUNNING ANY RTEMS PROGRAMS WHICH USE THE NETWORK! *
  • ************************************************************
  • *
  • *  $Id: networkconfig.h,v 1.14 2008/08/20 22:16:28 joel Exp $
  • */
  • #ifndef _RTEMS_NETWORKCONFIG_H_
  • #define _RTEMS_NETWORKCONFIG_H_
  • /* #define RTEMS_USE_BOOTP */
  • #include <bsp.h>
  • #include <rtems/dhcp.h>
  • #define RTEMS_USE_BOOTP
  • /*
  • * Loopback interface
  • */
  • extern
    int rtems_bsdnet_loopattach();
  • static
    struct rtems_bsdnet_ifconfig netdriver_config1 = {
  •   RTEMS_BSP_NETWORK_DRIVER_NAME,                    /* name */
  •   RTEMS_BSP_NETWORK_DRIVER_ATTACH,  /* attach function */
  •   NULL,                     /* No more interfaces */
  •   "10.0.0.100",              /* IP address */
  •   "255.255.255.0",              /* IP net mask */
  •   NULL,                     /* Driver supplies hardware address */
  •   0,                        /* Use default driver parameters */
  •   0,                        /* default efficiency multiplier */
  •   0,                        /* default udp TX socket buffer size */
  •   0,                        /* default udp RX socket buffer size */
  •   0,                        /* default tcp TX socket buffer size */
  •   0,                        /* default tcp RX socket buffer size */
  • };
  • /*
  • * Default network interface
  • */
  • static
    struct rtems_bsdnet_ifconfig netdriver_config = {
  •   "lo0",                    /* name */
  •   rtems_bsdnet_loopattach,  /* attach function */
  •   &netdriver_config1,                     /* No more interfaces */
  •   "127.0.0.1",              /* IP address */
  •   "255.0.0.0",              /* IP net mask */
  •   NULL,                     /* Driver supplies hardware address */
  •   0,                        /* Use default driver parameters */
  •   0,                        /* default efficiency multiplier */
  •   0,                        /* default udp TX socket buffer size */
  •   0,                        /* default udp RX socket buffer size */
  •   0,                        /* default tcp TX socket buffer size */
  •   0,                        /* default tcp RX socket buffer size */
  • };
  • /*
  • * Network configuration
  • */
  • struct rtems_bsdnet_config rtems_bsdnet_config = {
  •   &netdriver_config,
  •   NULL,                /* do not use bootp */
  •   0,                   /* Default network task priority */
  •   1024 * 1024,         /* Default mbuf capacity */
  •   1024 * 1024,         /* Default mbuf cluster capacity */
  •   "rtems",             /* Host name */
  •   "nodomain.com",      /* Domain name */
  •   "127.0.0.1",         /* Gateway */
  •   "127.0.0.1",         /* Log host */
  •   {"127.0.0.1" },      /* Name server(s) */
  •   {"127.0.0.1" },      /* NTP server(s) */
  •   0,                   /* sb_efficiency */
  •   0,                   /* udp_tx_buf_size */
  •   0,                   /* udp_rx_buf_size */
  •   0,                   /* tcp_tx_buf_size */
  •   0                    /* tcp_rx_buf_size */
  • };
  • /*
  • * For TFTP test application
  • */
  • #if (defined (RTEMS_USE_BOOTP))
  • #define RTEMS_TFTP_TEST_HOST_NAME "BOOTP_HOST"
  • #define RTEMS_TFTP_TEST_FILE_NAME "BOOTP_FILE"
  • #else
  • #define RTEMS_TFTP_TEST_HOST_NAME "XXX.YYY.ZZZ.XYZ"
  • #define RTEMS_TFTP_TEST_FILE_NAME "tftptest"
  • #endif
  • /*
  • * For NFS test application
  • *
  • * NFS server/path to mount and a directory to ls once mounted
  • */
  • #define RTEMS_NFS_SERVER      "192.168.1.210"
  • #define RTEMS_NFS_SERVER_PATH "/home"
  • #define RTEMS_NFS_LS_PATH     "/mnt/nfstest"
  • #endif /* _RTEMS_NETWORKCONFIG_H_ */

enjoy it!
(本文原创,转载请注明出处,谢谢)

[ 本帖最后由 rickleaf 于 2011-4-11 09:02 编辑 ]
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

GMT+8, 2024-3-29 07:18 , Processed in 0.092186 second(s), 15 queries .

© 2021 Powered by Discuz! X3.5.

快速回复 返回顶部 返回列表