找回密码
 加入计匠网
搜索
热搜: BIOS ACPI CPU Windows
查看: 24152|回复: 3

谁帮详细解释所有发给ec的60/64命令号码和意义

[复制链接]
发表于 2008-7-14 00:04:00 | 显示全部楼层 |阅读模式
bini殇,能帮详细解释一下bios post过程中的所有发给ec的60/64命令号码和意义吗?数据量比较大,截不住。
发表于 2008-7-14 12:09:38 | 显示全部楼层
转个别人发的,不知道LZ能不能用上     v# ]. S# N8 k! z. |) s2 }

$ r) p2 N; `% o* e* E. x4.2.7 Command* f+ b4 H% o4 ~

, \* \. n. X2 L+ d9 g: j% }/ v通过8042芯片,可以:
' V! h9 J$ @2 @$ L3 z& E向8042芯片发布命令(通过64h),并通过60h读取命令的返回结果(如果有的话),或通过60h端口写入命令所需的数据(如果需要的话)。
# L8 f8 m4 F( c) |# U) x" n读取Status Register的内容(通过64h);' N0 m/ T+ j& F5 v& R5 l
向8048发布命令(通过60h);  z" Q- @3 Z7 t- }2 |5 @, R5 o
读取来自于Keyboard的数据(通过60h)。这些数据包括Scan Code(由按键和释放键引起的),对8048发送的命令的确认字节(ACK)及回复数据。8 y2 P% J9 Z5 S
( Z. q9 c- e; q
再次强调一遍,Command(命令)分为发送给8042芯片的命令和发送给8048的命令。它们是不相同的,并且使用的端口也是不相同的(分别为64h和60h)。0 S  e, a6 R# u8 V# i3 a
; u" l) q; f& G1 H9 m6 Q

' ]2 \# Q% ]+ v. R" g+ A
3 g7 D9 k( s3 t4 X- D- w2 m  Q3 T    * 64h端口(读操作)# |) ^1 S3 M& d- v; r' H- R
, O* h6 M" J7 {, @

. E, ^% G' L; Q$ m' f1 R对64h端口进行读操作,会读取Status Register的内容。
5 m- U* ]2 ]( v) j+ d6 V" p' Y) X8 z
4 O& d0 C" ?6 [2 Y! J, Pinb %0x64
' T" m% j4 \2 m1 E8 J1 k: f执行这个指令之后,AL寄存器中存放的就是Status Register的内容。+ }7 ^$ E" h& Y4 a* V  }
8 p; q$ [6 f. d9 ]
    * 64h端口(写操作); r9 {5 H, {& B( V' Z9 Q( D

+ u) H4 N5 H! ?向64h端口写入的字节,被认为是对8042芯片发布的命令(Command):
5 o% T  C( }3 ~. r' @" kŸ
9 d, z5 _0 f; x0 I. |写入的字节将会被存放在Input Register中;$ f# |: M5 E1 L( T
Ÿ. [# g, {. O* e; t4 n
同时会引起Status Register的Bit-3自动被设置为1,表示现在放在Input Register中的数据是一个Command,而不是一个Data;
% f. u( J( U2 ]& |4 H% S7 Y; |2 m7 \Ÿ/ Z  X: x( l5 H% i
在向64h端口写某些命令之前必须确保键盘是被禁止的,因为这些被写入的命令的返回结果将会放到Output Register中,而键盘如果不被禁止,则也会将数据放入到Output Register中,会引起相互之间的数据覆盖;
' G1 r2 K" n/ K6 L* X2 u( aŸ8 B3 P, a( {+ V0 {' [
在向64h端口写数据之前必须确保Input Register是空的(通过判断Status Register的Bit-1是否为0)。
; i. z0 ~5 g& F4 ?
/ |5 q' }; R* h) o7 O  |! Z6 x& C+ L! Pvoid wait_input_empty(void)) T8 `2 l4 R# ?5 n
{
$ S: I5 m1 {2 u% ~4 K! U0 f. Q7 Q   char __b;- @! ]2 X/ T" i- l
+ R! f) h$ }% Y$ N8 r
   do{
- L2 i$ |! W! I' m/ Z; v     __b = inb(0x64);
1 H4 k8 E# B9 Z! l$ i/ p$ d   }while(!(__b&0x02));  F6 a' ?$ }* O3 Z& O: }
}
4 Q" R$ O3 ^$ N' p4 A/ h4 w& t( Y2 K
; ?4 p8 f6 A) n4 rvoid disable_keyboard(void)
  ]! y7 B9 i, H{7 c* U/ p- @/ Q
   wait_input_empty();
+ ^; D, _- ~3 ?! [6 H  d2 A   outb(0x64, 0xAD);
2 I! K2 N, B+ L}[size=+0]  [  w/ R" [8 K2 @2 t

7 D1 V8 F# o2 D$ n4 e9 I! N4 ^    * 60h端口(读操作): v) n9 V  |" o( C
- a3 t% i; c$ Y2 U) C% \5 ^5 c
对60h端口进行读操作,将会读取Output Register的内容。Output Register的内容可能是:
5 c5 ]4 w/ J7 c# x% dŸ           来自于8048的数据。这些数据包括Scan Code,对8048发送的命令的确认字节(ACK)及回复数据。
; C! c  ~1 w! f0 L8 Q5 SŸ           通过64h端口对8042发布的命令的返回结果。
7 T+ r( z4 [5 p# Z% q" Y# {( N$ h4 Q, g! p$ G$ x: z$ k: A% Q9 N, d/ `
在向60h端口读取数据之前必须确保Output Register中有数据(通过判断Status Register的Bit-0是否为1)。
- ]6 Z5 Z; Q1 M, R3 q# dvoid wait_output_full(void)
" \! C8 B' h$ a9 C. n5 r2 ?8 K# Z{
" ^$ V% h+ D- A9 ?/ M1 H   char __b;
+ E$ t, _4 E; f# t9 v3 y- r3 O6 K/ C, B- ^- f# T$ W
   do{  O: K8 M  Z% K. y  `
     __b = inb(0x64);
: I7 u  s( A' h$ Q9 W% A   }while(__b&0x01);+ K1 x5 c3 M- N9 p& @9 M6 e8 v$ A) P
}
) J, M4 T' ]+ ~9 E; _- t% H! N& G1 l; ]( J, P: O0 @
unsigned char read_output(void)
1 a8 M% d5 g9 s( U/ C/ P{
* x. u+ n) x2 B( K1 l: E* t/ ]   wait_output_full();2 h7 a, G; @6 z
   return inb(0x60);
) [4 A" \4 E5 u; u}0 h( _  W6 r- C; k/ M  x3 k

; ?& [* Q3 E8 ^- T  B    * 60h端口(写操作)& g; A6 }! R6 g" \; ]4 Z) c- ^
- R7 D8 C) W7 _8 M0 F1 C" Z" R) f
向60h端口写入的字节,有两种可能:
& R" ~  |, K# D8 C8 k1.如果之前通过64h端口向8042芯片发布的命令需要进一步的数据,则此时写入的字节就被认为是数据;% r( k1 [1 t* F& x: H
2.否则,此字节被认为是发送给8048的命令。
" v) I+ H) o. e  d: `+ y2 L' i8 P2 K* d% f  b! [$ R
在向60h端口写数据之前,必须确保Input Register是空的(通过判断Status Register的Bit-1是否为0)。: V# |- @3 q7 L
! r5 q$ p% r5 l+ ]1 p* z
# q) E' W* P8 k9 G1 y
[size=+0]4.2.7.1 发给8042的命令+ j7 H' A; q+ l+ z9 `
) f. u7 }! Q1 z0 a; |
    * 20h 2 \# s/ R, p! \

/ B! s7 T) l+ j7 H0 G- o准备读取8042芯片的Command Byte;其行为是将当前8042 Command Byte的内容放置于Output Register中,下一个从60H端口的读操作将会将其读取出来。
' u0 V2 B% }- q+ e% Z3 e6 \  X' K& D1 ^% E% `; |9 X9 _" Z
; @0 _1 l- r( d
unsigned char read_command_byte(void); Z9 w1 ^" _/ N: P( m6 S
{
& R6 P9 H+ W- p% U+ ]- G( u   wait_input_empty();+ x& b7 l# U$ `, G
   outb(0x64,0x20);
4 _) \& T. M0 p" k- |   wait_output_full();
9 o1 n9 h. l( [% j' x   return inb(0x60);   
' c4 q& x  I- |4 c6 p}. V. d0 u2 b% i& A. ?
7 j8 ^5 F! \; k2 V0 c2 u2 o5 ?# O
    * 60h( @# T5 ^, L% X

6 D2 R" h1 g! V) j' V准备写入8042芯片的Command Byte;下一个通过60h写入的字节将会被放入Command Byte。
: T  ^0 _" ]/ V4 |2 a; Y4 T) v0 k/ m/ p6 A* P5 t* q; q2 _! L
/ ]$ `. o& Z/ `7 S- K9 v
void write_command_byte(unsigned char command_byte)
8 F& k1 f/ O& ]6 ?, `{
9 p5 h# L# A) F   wait_input_empty();, ?4 L7 S! E; ~8 |! a
   outb(0x64,0x60);: L; q0 @/ A2 U
   wait_input_empty();% I8 F" p- L3 O. I, z
   outb(0x60,command_byte);: p. ^  t/ ^8 {) B2 ?( C9 ]
}
& N2 s; j+ C$ O% G. v
; k4 W4 Y) u4 P1 W. j$ l! H2 l* _7 M+ v
    * A4h
- I# Z! |7 b0 V9 R( P# a( B2 X" u6 N/ {
' l+ x; |; C$ w( Y4 P测试一下键盘密码是否被设置;测试结果放置在Output Register,然后可以通过60h读取出来。测试结果可以有两种值:FAh=密码被设置;F1h=没有密码。
( D, W- P& |5 M) b( j, I' hbool is_set_password(void)# L0 g/ I+ `5 @) a& q$ f
{9 `1 s* b8 Y3 I# s8 F
   wait_input_empty();. ]) N8 ]) J7 }2 p
   outb(0x64,0xA4);
! ?3 p) e! w. o: Q3 U   wait_output_full();5 w! H0 C. s. N+ r/ N% w, O5 e
   return inb(0x60)==0xFA?true:false;   5 V, a8 m5 ]0 {$ j, J
}
! w6 T; Q% u8 C8 f+ E- \
; W# R- d8 T+ A    * A5h* k7 p' W. V5 O8 `% _$ M8 P
9 `) w/ F( B$ R
设置键盘密码。其结果被按照顺序通过60h端口一个一个被放置在Input Register中。密码的最后是一个空字节(内容为0)。7 d$ W) c1 N8 P1 w2 L9 N1 ?0 ~
void set_password(unsigned char* password)
) N, r$ o, I+ w{
- s7 e+ c( ]0 P7 I5 \9 p( D" a   char* p = password;. i1 P' X" r4 P  |( T  ]- V7 l" @
% Q/ L) C' T( T( b0 D0 _8 ]
   if(p == NULL)1 P: ?4 B# n( ?; R2 c
      return;
: F/ n0 W% M! S( g9 N( t% n) _# h% r
/ L; y1 p0 Z: ?+ ^" W   wait_input_empty();1 e  A' t3 p- K% q; c
   outb(0x64,0xA5);( k4 o7 v6 D5 h2 c# E
' ]' ?9 W+ M* S% {) }3 n* r, o
   do{! f8 U/ _, E) K. R" r0 n- ^9 @
      wait_input_empty();" J4 F( J8 }% k) ]/ e1 H6 R. o# u' f
      outb(0x60, *p);
- g' L, N4 U9 I( f- x   }while(*p++ != 0);! j; [, A  i$ x8 \: ^( u
}- k) r- q6 p) u0 t+ z0 g# ]

5 {. C: v$ I/ B$ Q$ Q% s    * A6h7 m. v: U- t$ P  q6 N

2 m0 L0 s; ^+ _9 H) c( }2 `让密码生效。在发布这个命令之前,必须首先使用A5h命令设置密码。4 o0 D  P, w1 ]/ B7 Y
void enable_password(void)7 N+ G( v) e8 o; w
{6 r4 y  g. T  ^
   if(!is_set_password()): j% i) L  `+ T( s* P
      return;+ T$ z/ }8 j" o, a% G; n" a* F2 G

7 h: b5 I. F" s2 v% _  S   wait_input_empty();0 t" p1 R, o: G
   outb(0x64,0xA6);   % t. C; t' _1 c
}" l' D2 @$ t) G
' |& s" E- t+ b! f0 K
    * AAh
' E+ ^7 E4 V+ K9 _! A7 ]1 Z4 K8 X& u
自检。诊断结果放置在Output Register中,可以通过60h读取。55h=OK。
, g1 G4 [9 Y' j4 K# {4 V7 K% _7 ]5 I8 U

4 \& f% k/ ]# N) `' _( [/ p- V  Bbool is_test_ok(void)
0 e8 B  `+ \5 `4 w' V! x{6 \( H" p! j! I" ^1 v- X( O' t2 z
   wait_input_empty();4 Z9 t* _, P2 \
   outb(0x64,0xAA);, v8 z1 P5 f( T4 m

( K5 H  l! P- T' e! J* B
( c" d5 ~; V1 F7 [/ a* q" D. g   wait_output_full();
1 R+ c; Y- K; u/ y9 G, w   return inb(0x60)==0x55?true:false;   4 O+ y# _. |6 w
}
3 l# i& B3 S. f% G" r5 S% C  f9 b5 K; G1 i, R+ D+ c. f

4 L/ P! m( r; D1 Z- Y0 i    * ADh
- s2 `" e9 k, v
- U, |/ I1 Z$ F' X禁止键盘接口。Command Byte的bit-4被设置。当此命令被发布后,Keyboard将被禁止发送数据到Output Register。
9 u# a, U2 ?9 r) R) D  \void disable_keyboard(void)7 w" k1 ~0 L9 \# w+ L- {
{8 ]4 {% l4 |0 V) b
   wait_input_empty();6 ]9 a, s/ ^2 h6 c; l' s& R( E3 i
   outb(0x64,0xAD);
2 K1 G* X' z' |$ \$ J
7 X2 h+ c( J) O- H}: U5 h8 ^6 M3 b5 W/ Y. {; o. B' }' v

: u7 u# K, T0 ~% |7 ^0 j1 C    * AEh. _! p# j; N3 t' S6 ~! Y3 `6 b! Y
& b: a) v- ?9 M; t/ I- A  x2 |9 f
打开键盘接口。Command Byte的bit-4被清除。当此命令被发布后,Keyboard将被允许发送数据到Output Register。
  S  F3 c5 n/ Q3 [$ h/ [1 bvoid enable_keyboard(void)
+ T6 T( e" Q9 Y$ m3 m. x6 Y{3 s' b% |. w" ?( @' Y% T
   wait_input_empty();
% Z# L6 E$ e; E" q$ F1 ]2 \   outb(0x64,0xAE);
3 F9 l) Y4 G, k6 K) b8 m( s7 G/ G/ B9 |, v
}- h- ^( X# ]7 l& C. ?
. q* c9 `1 D( U( F
    * C0h. s: c  n5 C3 z. r+ c# D

% V4 N' |8 L2 q, {准备读取Input Port。Input Port的内容被放置于Output Register中,随后可以通过60h端口读取。' U, g" G0 g3 Y0 Y
unsigned char read_input_port(void)
4 y% T" y5 O0 w& \3 Z{& B/ B6 O# A# `+ ~7 D$ H; M$ Z
   wait_input_empty();
! q, ?) v* I( A* P; P   outb(0x64,0xC0);  x3 l. }0 ]0 \6 W$ f/ T, J' I

$ s% U) h" m! W; b! v; H8 g   wait_output_full();% ?, P) B# g, w$ m  L9 u
6 Z+ T& e$ _9 D$ L+ R1 \& c
   return inb(0x60);: C3 T' W3 b4 Q5 z
}" U3 [3 j( ?0 G* i0 Z

% @+ B/ z! [0 K0 [9 n7 Q    * D0h
9 D( z& l+ B3 O9 R: j
( Q; p5 C, y9 Y准备读取Outport端口。结果被放在Output Register中,随后通过60h端口读取出来。% ~& o  |9 N3 n* o/ n: `) b8 f! M! n
unsigned char read_output_port(void)
) X* [0 j+ o( L) T- `{
# p6 R0 }4 Y1 @! b   wait_input_empty();
7 _0 W* D. ?) X# X   outb(0x64,0xD0);6 O$ H9 N4 q. J, {9 }* ~' f
0 U! K& P+ E; `9 q; ?0 ^) [
   wait_output_full();* B& Q0 Q5 l$ A  B" U

  m% ^( `4 d* R2 C   return inb(0x60);
5 B, |! W; h' l) z$ }, t4 |, ]; L}6 v9 D( K5 Z6 m
7 g7 f* A8 I1 l( J. _
    * D1h
0 q: ]: H/ E1 |" D  _" h) F- g8 N
- g7 g2 z$ ?. A) E准备写Output端口。随后通过60h端口写入的字节,会被放置在Output Port中。3 H( y4 }; h2 \6 A+ [1 N
void write_output_port(unsigned char __c)
1 U- U. x! b# f1 o# Q$ I5 n) O8 F{
  A! s# N: K% J# x; S   wait_input_empty();
5 u6 f/ r2 l5 v, F   outb(0x64,0xD1);0 e, f9 m0 F+ J1 V% I# S

( {  j' g5 g5 W! K" r   wait_input_empty();
; V. z* T7 o* {: `0 e   outb(0x60,__c);7 o4 F- X0 ]! w3 S0 f
( T0 s8 e& Y4 G. P0 m9 }# ^
}* E8 T" F3 @. E3 r

( G# u9 g0 s5 {, ~' r1 H" k
! c2 ]0 S0 G/ F" X9 {    * D2h
' Y& w- @5 \8 J6 T) E7 e0 G
/ k6 `& N0 `' ]% V7 x* e, f准备写数据到Output Register中。随后通过60h写入到Input Register的字节会被放入到Output Register中,此功能被用来模拟来自于Keyboard发送的数据。如果中断被允许,则会触发一个中断。
+ C! {0 W* z$ C! y2 Y& Q, T! S# s; Ovoid put_data_to_output_register(unsigned char __data)3 O* a1 H" d6 p
{# q" e. y2 {/ |$ r/ b) E
   wait_input_empty();* S( t7 @& l0 W2 _7 T
   outb(0x64,0xD2);
2 l: e8 P. \) c5 O; F* Y/ F/ h* ^) b+ x5 e* g3 V# a4 ?/ J% H: R
   wait_input_empty();0 [$ V% H5 d* C5 R" |8 S* a
   outb(0x60,__c);. E8 p& x9 P  S
}
: M( o; Y/ o! f+ k6 u) L/ h* h, C( S8 l% o6 v: _$ \
4.2.7.2 发给8048的命令
' r. x( Z* u, i+ [) c, W" _1 J1 G" D- X
, Z* t" O, o2 E  E" W
    * EDh7 Y7 n. U( X; S- P
$ |' u! O; j: Y9 E7 Z
设置LED。Keyboard收到此命令后,一个LED设置会话开始。Keyboard首先回复一个ACK(FAh),然后等待从60h端口写入的LED设置字节,如果等到一个,则再次回复一个ACK,然后根据此字节设置LED。然后接着等待。。。直到等到一个非LED设置字节(高位被设置),此时LED设置会话结束。+ ^9 ~0 M2 p2 K' @9 v: y# f  D

; H$ A/ L/ v4 s, \! N+ }    * EEh
* Z( u: \) T; Z/ l+ T
- _& x$ Z, Z' s8 V# @! h! H8 y诊断Echo。此命令纯粹为了检测Keyboard是否正常,如果正常,当Keyboard收到此命令后,将会回复一个EEh字节。
5 }+ l& m* v- T1 d# s- T. P1 E& _3 E5 I! b7 j) t- O- T
    * F0h
. W) K0 [7 f/ P+ U' E: V" L; j* t* r- z6 Q$ B. W  p
选择Scan code set。Keyboard系统共可能有3个Scan code set。当Keyboard收到此命令后,将回复一个ACK,然后等待一个来自于60h端口的Scan code set代码。系统必须在此命令之后发送给Keyboard一个Scan code set代码。当Keyboard收到此代码后,将再次回复一个ACK,然后将Scan code set设置为收到的Scan code set代码所要求的。  J# d( ?- E) o9 B5 D2 L9 o1 k
2 K! Y) D* q! m  z$ S4 T
    * F2" `8 V1 ^; Y& J# ~4 Q$ Z, S3 S
! q5 W* U+ S5 y' c! }2 [+ i
读取Keyboard ID。由于8042芯片后不仅仅能够接Keyboard。此命令是为了读取8042后所接的设备ID。设备ID为2个字节,Keyboard ID为83ABh。当键盘收到此命令后,会首先回复一个ACK,然后,将2字节的Keyboard ID一个一个回复回去。/ O# E  y. X; C9 U+ T/ H

; y3 c3 [/ H: A4 c8 B: }$ t    * F3h
+ {2 G+ L  s1 ^. @/ Z* `5 J1 O& X! K0 I+ h4 u3 |
设置Typematic Rate/Delay。当Keyboard收到此命令后,将回复一个ACK。然后等待来自于60h的设置字节。一旦收到,将回复一个ACK,然后将Keyboard Rate/Delay设置为相应的值。3 l9 @8 N) G# r0 w+ L, t2 \' ~

9 S5 U3 Z6 B* |$ u1 g, n7 T; N& s    * F4h9 H# u7 N* s0 P& _# j7 t

6 K9 X5 r- Z- _7 Y; T( r0 l" X# |" f6 w; k清理键盘的Output Buffer。一旦Keyboard收到此命令,将会将Output buffer清空,然后回复一个ACK。然后继续接受Keyboard的击键。
3 W9 f% F2 T- @. K- i+ k7 u
! L: m, ~& j2 Q6 W: @    * F5h
8 U( [# H9 q8 c- d8 j/ ?* B" J+ x% k/ C6 }/ _. M, ~
设置默认状态(w/Disable)。一旦Keyboard收到此命令,将会将Keyboard完全初始化成默认状态。之前所有对它的设置都将失效——Output buffer被清空,Typematic Rate/Delay被设置成默认值。然后回复一个ACK,接着等待下一个命令。需要注意的是,这个命令被执行后,键盘的击键接受是禁止的。如果想让键盘接受击键输入,必须Enable Keyboard。
; [* Y. s* Z$ E7 E3 G( m$ O4 t6 W3 g0 H  X0 |8 t
    * F6h
+ }  p% d7 d4 c. p4 ^1 E
: t% u2 P; c2 f设置默认状态。和F5命令唯一不同的是,当此命令被执行之后,键盘的击键接收是允许的。
- @3 H$ h4 ?% j- B4 Y1 {' {! m/ J4 B
; R$ [) s3 v' `& @: i0 v0 u$ @    * FEh
' [0 d: }' y3 D( |7 D
8 K; m  v0 S7 P  EResend。如果Keyboard收到此命令,则必须将刚才发送到8042 Output Register中的数据重新发送一遍。当系统检测到一个来自于Keyboard的错误之后,可以使用自命令让Keyboard重新发送刚才发送的字节。/ A$ q2 V0 A& g

- W; e% R2 C8 Z' `    * FFh
, r& s. w  k9 X8 \# z! G
; w9 i$ T& R& @$ J7 I  y; X5 YReset Keyboard。如果Keyboard收到此命令,则首先回复一个ACK,然后启动自身的Reset程序,并进行自身基本正确性检测(BAT-Basic Assurance Test)。等这一切结束之后,将返回给系统一个单字节的结束码(AAh=Success, FCh=Failed),并将键盘的Scan code set设置为2。
回复

使用道具 举报

发表于 2009-1-14 13:49:16 | 显示全部楼层
好贴!: s5 X$ u% }3 {% D+ o- Q
非常详细!' F) e8 ~, k2 v# j- y
谢谢!
回复

使用道具 举报

发表于 2009-2-25 17:37:28 | 显示全部楼层
2楼好帖!在helppc的Hardware Data and Specifications栏里有相应的寄存器和命令详细描述。3 z6 R* j* b. a4 j
& O9 A0 ?' x4 `* j3 F
我的问题是,上述描述应该是针对老的8042的,在目前的使用EC的系统中,这些status register和command描述在哪个文档中可以找到?比如intel的santarosa,使用renessas的H8s 2104 EC,按道理上述状态寄存器,命令等的描述应该在2104 datasheet里,但是我怎么找都没有。还是有某个spec定义了这些命令格式?新手问题,高手莫笑
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

Archiver|手机版|小黑屋|计匠网

GMT+8, 2026-4-4 18:51 , Processed in 0.155771 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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