(102条消息) modetest编译、原理分析_空腹吃饭的博客-CSDN博客
(102条消息) tools:drm-kms调试手段[modetest]_drm_debug_kms_maze.ma的博客-CSDN博客
本内容通过modetest的打印,了解drm中各个object之间的关联,即如何正确的设置crtc,connector,encoder,plane的组合,才能正确的显示成功。内核驱动在加载后需要创建crtc,connector,encoder,plane等。且他们直接需要正确的匹配,比如connector和encoder。
在设置显示之前(内核模块加载完毕):
输出当前内核中的connectors:
modes是和当前connector关联的modes个数。encoders是encoder的列表。如果有多个会按逗号分隔。当前只有是37号。当前encoder是0说明还没有设置。
root@wzm-virtual-machine:/home/wzm# modetest -c
Connectors:
id encoder status name size (mm) modes encoders
36 0 connected Virtual-1 0x0 25 37
modes:
name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot)
800x600 60 800 850 900 950 600 650 700 750 42750 flags: nhsync, pvsync; type: preferred, driver
3840x2400 60 3840 3888 3920 4000 2400 2403 2409 2469 592250 flags: phsync, nvsync; type: driver
3840x2160 60 3840 3888 3920 4000 2160 2163 2168 2222 533000 flags: phsync, nvsync; type: driver
2880x1800 60 2880 2928 2960 3040 1800 1803 1809 1852 337500 flags: phsync, nvsync; type: driver
2560x1600 60 2560 2752 3032 3504 1600 1603 1609 1658 348500 flags: nhsync, pvsync; type: driver
2560x1440 60 2560 2608 2640 2720 1440 1443 1448 1481 241500 flags: phsync, nvsync; type: driver
1920x1440 60 1920 2048 2256 2600 1440 1441 1444 1500 234000 flags: nhsync, pvsync; type: driver
1856x1392 60 1856 1952 2176 2528 1392 1393 1396 1439 218250 flags: nhsync, pvsync; type: driver
1792x1344 60 1792 1920 2120 2448 1344 1345 1348 1394 204750 flags: nhsync, pvsync; type: driver
1920x1200 60 1920 2056 2256 2592 1200 1203 1209 1245 193250 flags: nhsync, pvsync; type: driver
1920x1080 60 1920 2048 2248 2576 1080 1083 1088 1120 173000 flags: nhsync, pvsync; type: driver
1600x1200 60 1600 1664 1856 2160 1200 1201 1204 1250 162000 flags: phsync, pvsync; type: driver
1680x1050 60 1680 1784 1960 2240 1050 1053 1059 1089 146250 flags: nhsync, pvsync; type: driver
1400x1050 60 1400 1488 1632 1864 1050 1053 1057 1089 121750 flags: nhsync, pvsync; type: driver
1280x1024 60 1280 1328 1440 1688 1024 1025 1028 1066 108000 flags: phsync, pvsync; type: driver
1440x900 60 1440 1520 1672 1904 900 903 909 934 106500 flags: nhsync, pvsync; type: driver
1280x960 60 1280 1376 1488 1800 960 961 964 1000 108000 flags: phsync, pvsync; type: driver
1360x768 60 1360 1424 1536 1792 768 771 777 795 85500 flags: phsync, pvsync; type: driver
1280x800 60 1280 1352 1480 1680 800 803 809 831 83500 flags: phsync, nvsync; type: driver
1152x864 75 1152 1216 1344 1600 864 865 868 900 108000 flags: phsync, pvsync; type: driver
1280x768 60 1280 1344 1472 1664 768 771 778 798 79500 flags: nhsync, pvsync; type: driver
1280x720 60 1280 1344 1472 1664 720 723 728 748 74500 flags: nhsync, pvsync; type: driver
1024x768 60 1024 1048 1184 1344 768 771 777 806 65000 flags: nhsync, nvsync; type: driver
800x600 60 800 840 968 1056 600 601 605 628 40000 flags: phsync, pvsync; type: driver
640x480 60 640 656 752 800 480 489 492 525 25175 flags: nhsync, nvsync; type: driver
props:
2 DPMS:
flags: enum
enums: On=0 Standby=1 Suspend=2 Off=3
value: 3
5 link-status:
flags: enum
enums: Good=0 Bad=1
value: 0
6 non-desktop:
flags: immutable range
values: 0 1
value: 0
4 TILE:
flags: immutable blob
blobs:
value:
20 CRTC_ID:
flags: object
41 0 disconnected Virtual-2 0x0 25 42
modes:
index name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot
#0 800x600 60.00 800 850 900 950 600 650 700 750 42750 flags: nhsync, pvsync; type: preferred, driver
.....省略输出
drm_connector结构体:
struct drm_connector
{
u32 possible_encoders; //encoders位掩码
struct drm_encoder *encoder;
}
ast驱动中初始化connetor和encoder的地方:
static int ast_connector_init(struct drm_device *dev)
{
struct ast_connector *ast_connector;
struct drm_connector *connector;
struct drm_encoder *encoder;
ast_connector = kzalloc(sizeof(struct ast_connector), GFP_KERNEL);
if (!ast_connector)
return -ENOMEM;
connector = &ast_connector->base;
drm_connector_init(dev, connector, &ast_connector_funcs, DRM_MODE_CONNECTOR_VGA);
drm_connector_helper_add(connector, &ast_connector_helper_funcs);
connector->interlace_allowed = 0;
connector->doublescan_allowed = 0;
drm_connector_register(connector);
connector->polled = DRM_CONNECTOR_POLL_CONNECT;
encoder = list_first_entry(&dev->mode_config.encoder_list, struct drm_encoder, head);
drm_connector_attach_encoder(connector, encoder); //这里建立connector和encoder
ast_connector->i2c = ast_i2c_create(dev);
if (!ast_connector->i2c)
DRM_ERROR("failed to add ddc bus for connector\n");
return 0;
}
drm_connector_attach_encoder函数:一般驱动在初始化connecor的时候调用。如果有多个encoder和connector则多次调用。
int drm_connector_attach_encoder(struct drm_connector *connector,
struct drm_encoder *encoder)
{
/*
* In the past, drivers have attempted to model the static association
* of connector to encoder in simple connector/encoder devices using a
* direct assignment of connector->encoder = encoder. This connection
* is a logical one and the responsibility of the core, so drivers are
* expected not to mess with this.
*
* Note that the error return should've been enough here, but a large
* majority of drivers ignores the return value, so add in a big WARN
* to get people's attention.
*/
if (WARN_ON(connector->encoder))
return -EINVAL;
connector->possible_encoders |= drm_encoder_mask(encoder);
return 0;
}
位图:drm_encoder.h //就是第几个左移几位。比如第0个就是0x1
static inline u32 drm_encoder_mask(const struct drm_encoder *encoder)
{
return 1 << drm_encoder_index(encoder);
}
static inline unsigned int drm_encoder_index(const struct drm_encoder *encoder)
{
return encoder->index;
}
connector的encoder是什么时候设置的?
1:目前在drm_crtc_helper_set_config函数中看到:
调用路径是drm_mode_setcrtc 后会通过__drm_mode_set_config_internal->drm_crtc_helper_set_config:
int drm_crtc_helper_set_config(struct drm_mode_set *set,
struct drm_modeset_acquire_ctx *ctx)
{
......
/* a) traverse passed in connector list and get encoders for them */
count = 0;
drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(connector, &conn_iter) {
const struct drm_connector_helper_funcs *connector_funcs =
connector->helper_private;
new_encoder = connector->encoder;
for (ro = 0; ro < set->num_connectors; ro++) {
if (set->connectors[ro] == connector) { //匹配到用户传入的connector
if (connector_funcs->best_encoder)
new_encoder = connector_funcs->best_encoder(connector); //调用best_encoder获取合适的encoder
else
new_encoder = drm_connector_get_single_encoder(connector);
/* if we can't get an encoder for a connector
we are setting now - then fail */
if (new_encoder == NULL)
/* don't break so fail path works correct */
fail = 1;
if (connector->dpms != DRM_MODE_DPMS_ON) {
DRM_DEBUG_KMS("connector dpms not on, full mode switch\n");
mode_changed = true;
}
break;
}
}
if (new_encoder != connector->encoder) { //找到新的encoder。
DRM_DEBUG_KMS("encoder changed, full mode switch\n");
mode_changed = true;
/* If the encoder is reused for another connector, then
* the appropriate crtc will be set later.
*/
if (connector->encoder)
connector->encoder->crtc = NULL;
connector->encoder = new_encoder; //设置encoder
}
}
drm_connector_list_iter_end(&conn_iter);
......
}
2:drm_atomic_helper.c. TODO
输出当前内核的encoders:
root@wzm-virtual-machine:/home/wzm/libdrm/build/tests/modetest# ./modetest -e
trying to open device 'i915'...failed
trying to open device 'amdgpu'...failed
trying to open device 'radeon'...failed
trying to open device 'nouveau'...failed
trying to open device 'vmwgfx'...done
Encoders:
id crtc type possible crtcs possible clones
37 0 Virtual 0x00000001 0x00000001
42 0 Virtual 0x00000002 0x00000002
47 0 Virtual 0x00000004 0x00000004
52 0 Virtual 0x00000008 0x00000008
57 0 Virtual 0x00000010 0x00000010
62 0 Virtual 0x00000020 0x00000020
67 0 Virtual 0x00000040 0x00000040
72 0 Virtual 0x00000080 0x00000080
possible_crtcs通常是驱动在是初始化connector同时直接设置的,比如ast驱动:
static int ast_encoder_init(struct drm_device *dev)
{
struct ast_encoder *ast_encoder;
ast_encoder = kzalloc(sizeof(struct ast_encoder), GFP_KERNEL);
if (!ast_encoder)
return -ENOMEM;
drm_encoder_init(dev, &ast_encoder->base, &ast_enc_funcs,
DRM_MODE_ENCODER_DAC, NULL);
drm_encoder_helper_add(&ast_encoder->base, &ast_enc_helper_funcs);
ast_encoder->base.possible_crtcs = 1; //直接设置。。这里就限定了两者的匹配关系。位图意味着就是第0个crtc。(1左移0)
return 0;
}
encoder中的crtc是什么时候设置的?
和connector的encoder设置一样,在是drm_mode_setcrtc 后会通过__drm_mode_set_config_internal->drm_crtc_helper_set_config:
int drm_crtc_helper_set_config(struct drm_mode_set *set,
struct drm_modeset_acquire_ctx *ctx)
{
.....
count = 0;
drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(connector, &conn_iter) {
if (!connector->encoder) //需确保有encoder,前面已经设置。
continue;
if (connector->encoder->crtc == set->crtc) //如果encoder的crtc是和新传入的一样,设置NULL
new_crtc = NULL;
else
new_crtc = connector->encoder->crtc;
for (ro = 0; ro < set->num_connectors; ro++) {
if (set->connectors[ro] == connector)
new_crtc = set->crtc;
}
/* Make sure the new CRTC will work with the encoder */
if (new_crtc &&
!drm_encoder_crtc_ok(connector->encoder, new_crtc)) { //确保crtc在encoder的位图中
ret = -EINVAL;
drm_connector_list_iter_end(&conn_iter);
goto fail;
}
if (new_crtc != connector->encoder->crtc) { //是全新的crtc。首次肯定是满足条件的。
DRM_DEBUG_KMS("crtc changed, full mode switch\n");
mode_changed = true;
connector->encoder->crtc = new_crtc; //更新encoder中的crtc
}
if (new_crtc) {
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] to [CRTC:%d:%s]\n",
connector->base.id, connector->name,
new_crtc->base.id, new_crtc->name);
} else {
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] to [NOCRTC]\n",
connector->base.id, connector->name);
}
}
drm_connector_list_iter_end(&conn_iter);
....
}
输出当前内核的crtc和plane:
root@wzm-virtual-machine:/home/wzm/libdrm/build/tests/modetest# ./modetest -p
CRTCs:
id fb pos size
38 0 (0,0) (0x0) //这里的fb是crtc->primary的fb。当前没有设置显示,所以为0
0 0 0 0 0 0 0 0 0 0 flags: ; type:
props:
22 ACTIVE:
flags: range
values: 0 1
value: 0
23 MODE_ID:
flags: blob
blobs:
value:
19 OUT_FENCE_PTR:
flags: range
values: 0 18446744073709551615
value: 0
24 VRR_ENABLED:
flags: range
values: 0 1
value: 0
43 0 (0,0) (0x0)
0 0 0 0 0 0 0 0 0 0 flags: ; type:
props:
22 ACTIVE:
flags: range
values: 0 1
value: 0
23 MODE_ID:
flags: blob
blobs:
value:
19 OUT_FENCE_PTR:
flags: range
values: 0 18446744073709551615
value: 0
24 VRR_ENABLED:
flags: range
values: 0 1
value: 0
.....
Planes:
id crtc fb CRTC x,y x,y gamma size possible crtcs
34 0 0 0,0 0,0 0 0x00000001 //当前plane可能的crtc,详见函数drm_crtc_init_with_planes赋值。因此通常一个crtc下会有两个plane。即当前34,和后面的35.只是fb会不同。primary的fb是drmModeAddFb2创建,而另一个Cursor是创建鼠标,内核创建的。
formats: XR15 RG16 XR24 AR24
props:
8 type:
flags: immutable enum
enums: Overlay=0 Primary=1 Cursor=2
value: 1
17 FB_ID:
flags: object
value: 0
18 IN_FENCE_FD:
flags: signed range
values: -1 2147483647
value: -1
20 CRTC_ID:
flags: object
value: 0
13 CRTC_X:
flags: signed range
values: -2147483648 2147483647
value: 0
14 CRTC_Y:
flags: signed range
values: -2147483648 2147483647
value: 0
15 CRTC_W:
flags: range
values: 0 2147483647
value: 1920
16 CRTC_H:
flags: range
values: 0 2147483647
value: 1080
9 SRC_X:
flags: range
values: 0 4294967295
value: 0
10 SRC_Y:
flags: range
values: 0 4294967295
value: 0
11 SRC_W:
flags: range
values: 0 4294967295
value: 125829120
12 SRC_H:
flags: range
values: 0 4294967295
value: 70778880
21 FB_DAMAGE_CLIPS:
flags: blob
blobs:
value:
35 0 0 0,0 0,0 0 0x00000001
formats: AR24
props:
8 type:
flags: immutable enum
enums: Overlay=0 Primary=1 Cursor=2
value: 2
17 FB_ID:
flags: object
value: 0
18 IN_FENCE_FD:
flags: signed range
values: -1 2147483647
value: -1
20 CRTC_ID:
flags: object
value: 0
13 CRTC_X:
flags: signed range
values: -2147483648 2147483647
value: 0
14 CRTC_Y:
flags: signed range
values: -2147483648 2147483647
value: 0
15 CRTC_W:
flags: range
values: 0 2147483647
value: 0
16 CRTC_H:
flags: range
values: 0 2147483647
value: 0
9 SRC_X:
上述输出的crtc中fb在什么时候设置?
答:上述crtc的fb是primary的。其在drm_mode_setcrtc->__drm_mode_set_config_internal函数设置 :
static int __drm_mode_set_config_internal(struct drm_mode_set *set,
struct drm_modeset_acquire_ctx *ctx)
{
.....
if (ret == 0) {
struct drm_plane *plane = crtc->primary;
plane->crtc = fb ? crtc : NULL; //设置crtc
plane->fb = fb; //设置fb
}
.....
}
另外一个cursor的在drm_mode_cursor2_ioctl->drm_mode_cursor_common->drm_mode_cursor_universal-> __setplane_internal:
static int __setplane_internal(struct drm_plane *plane,
struct drm_crtc *crtc,
struct drm_framebuffer *fb,
int32_t crtc_x, int32_t crtc_y,
uint32_t crtc_w, uint32_t crtc_h,
/* src_{x,y,w,h} values are 16.16 fixed point */
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h,
struct drm_modeset_acquire_ctx *ctx)
{
....
plane->old_fb = plane->fb;
ret = plane->funcs->update_plane(plane, crtc, fb,
crtc_x, crtc_y, crtc_w, crtc_h,
src_x, src_y, src_w, src_h, ctx);
if (!ret) {
plane->crtc = crtc; //设置crtc
plane->fb = fb; //设置fb
drm_framebuffer_get(plane->fb);
} else {
plane->old_fb = NULL;
}
...
}
上述输出的plane中的crtc和fb。以及possible_crtcs在哪里设置?
1:crtc和fb就是上面讲的。分别在primary和cursor两个plane中设置。
2:possible_crts在drm_crtc_init_with_planes:
int drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *crtc,
struct drm_plane *primary,
struct drm_plane *cursor,
const struct drm_crtc_funcs *funcs,
const char *name, ...)
{
struct drm_mode_config *config = &dev->mode_config;
int ret;
.....
crtc->primary = primary;
crtc->cursor = cursor;
if (primary && !primary->possible_crtcs)
primary->possible_crtcs = drm_crtc_mask(crtc);
if (cursor && !cursor->possible_crtcs)
cursor->possible_crtcs = drm_crtc_mask(crtc);
.....
}
总结:内核驱动中需要建立connecotr和encoder的关系。encoder和crtc的关系。也会建立了crtc和plane的关系。用户通过drmModeSetCrtc将 这三者串起来。
drmModeSetCrtc函数:
drm_public int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
uint32_t x, uint32_t y, uint32_t *connectors, int count,
drmModeModeInfoPtr mode)
{
struct drm_mode_crtc crtc;
memclear(crtc);
crtc.x = x;
crtc.y = y;
crtc.crtc_id = crtcId;
crtc.fb_id = bufferId;
crtc.set_connectors_ptr = VOID2U64(connectors);
crtc.count_connectors = count;
if (mode) {
memcpy(&crtc.mode, mode, sizeof(struct drm_mode_modeinfo));
crtc.mode_valid = 1;
}
return DRM_IOCTL(fd, DRM_IOCTL_MODE_SETCRTC, &crtc);
}
内核对应的函数drm_mode_setcrtc做的事情:
1:通过best_encoder设置connector的encoder。
2:设置encoder的crtc。
3:设置crtc下primary plane的fb和crtc。
由于一个connector可多个备选的encoder,一个encoder有备选的多个crtc,而一个crtc的两个plane是唯一的。因此需要最终用户通过libdrm来决定。
设置显示之后:下面摘取通过命令
modetest -M vmwgfx -a -s 36@38:1280x768 -P 34@38:1280x768 -Ftiles让显示器显示后的输出。
root@wzm-virtual-machine:/home/wzm# modetest -M vmwgfx -c
Connectors:
id encoder status name size (mm) modes encoders
36 37 connected Virtual-1 0x0 25 37
modes:
index name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot
#0 800x600 60.00 800 850 900 950 600 650 700 750 42750 flags: nhsync, pvsync; type: preferred, driver
#1 3840x2400 59.97 3840 3888 3920 4000 2400 2403 2409 2469 592250 flags: phsync, nvsync; type: driver
#2 3840x2160 59.97 3840 3888 3920 4000 2160 2163 2168 2222 533000 flags: phsync, nvsync; type: driver
#3 2880x1800 59.95 2880 2928 2960 3040 1800 1803 1809 1852 337500 flags: phsync, nvsync; type: driver
#4 2560x1600 59.99 2560 2752 3032 3504 1600 1603 1609 1658 348500 flags: nhsync, pvsync; type: driver
#5 2560x1440 59.95 2560 2608 2640 2720 1440 1443 1448 1481 241500 flags: phsync, nvsync; type: driver
#6 1920x1440 60.00 1920 2048 2256 2600 1440 1441 1444 1500 234000 flags: nhsync, pvsync; type: driver
#7 1856x1392 60.00 1856 1952 2176 2528 1392 1393 1396 1439 218250 flags: nhsync, pvsync; type: driver
#8 1792x1344 60.00 1792 1920 2120 2448 1344 1345 1348 1394 204750 flags: nhsync, pvsync; type: driver
#9 1920x1200 59.88 1920 2056 2256 2592 1200 1203 1209 1245 193250 flags: nhsync, pvsync; type: driver
#10 1920x1080 59.96 1920 2048 2248 2576 1080 1083 1088 1120 173000 flags: nhsync, pvsync; type: driver
#11 1600x1200 60.00 1600 1664 1856 2160 1200 1201 1204 1250 162000 flags: phsync, pvsync; type: driver
#12 1680x1050 59.95 1680 1784 1960 2240 1050 1053 1059 1089 146250 flags: nhsync, pvsync; type: driver
#13 1400x1050 59.98 1400 1488 1632 1864 1050 1053 1057 1089 121750 flags: nhsync, pvsync; type: driver
#14 1280x1024 60.02 1280 1328 1440 1688 1024 1025 1028 1066 108000 flags: phsync, pvsync; type: driver
#15 1440x900 59.89 1440 1520 1672 1904 900 903 909 934 106500 flags: nhsync, pvsync; type: driver
#16 1280x960 60.00 1280 1376 1488 1800 960 961 964 1000 108000 flags: phsync, pvsync; type: driver
#17 1360x768 60.02 1360 1424 1536 1792 768 771 777 795 85500 flags: phsync, pvsync; type: driver
#18 1280x800 59.81 1280 1352 1480 1680 800 803 809 831 83500 flags: phsync, nvsync; type: driver
#19 1152x864 75.00 1152 1216 1344 1600 864 865 868 900 108000 flags: phsync, pvsync; type: driver
#20 1280x768 59.87 1280 1344 1472 1664 768 771 778 798 79500 flags: nhsync, pvsync; type: driver
#21 1280x720 59.86 1280 1344 1472 1664 720 723 728 748 74500 flags: nhsync, pvsync; type: driver
#22 1024x768 60.00 1024 1048 1184 1344 768 771 777 806 65000 flags: nhsync, nvsync; type: driver
#23 800x600 60.32 800 840 968 1056 600 601 605 628 40000 flags: phsync, pvsync; type: driver
#24 640x480 59.94 640 656 752 800 480 489 492 525 25175 flags: nhsync, nvsync; type: driver
props:
2 DPMS:
xx
xx
41 0 disconnected Virtual-2 0x0 25 42
modes:
index name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot
#0 800x600 60.00 800 850 900 950 600 650 700 750 42750 flags: nhsync, pvsync; type: preferred, driver
.....
root@wzm-virtual-machine:/home/wzm# modetest -M vmwgfx -e
Encoders:
id crtc type possible crtcs possible clones
37 38 Virtual 0x00000001 0x00000001
42 0 Virtual 0x00000002 0x00000002
47 0 Virtual 0x00000004 0x00000004
52 0 Virtual 0x00000008 0x00000008
57 0 Virtual 0x00000010 0x00000010
62 0 Virtual 0x00000020 0x00000020
67 0 Virtual 0x00000040 0x00000040
72 0 Virtual 0x00000080 0x00000080
root@wzm-virtual-machine:/home/wzm# modetest -M vmwgfx -p
CRTCs:
id fb pos size
38 75 (0,0) (1280x768)
#0 1280x768 59.87 1280 1344 1472 1664 768 771 778 798 79500 flags: nhsync, pvsync; type: driver
props:
24 VRR_ENABLED:
flags: range
values: 0 1
value: 0
43 0 (0,0) (0x0)
#0 -nan 0 0 0 0 0 0 0 0 0 flags: ; type:
props:
24 VRR_ENABLED:
flags: range
values: 0 1
value: 0
.....
Planes:
id crtc fb CRTC x,y x,y gamma size possible crtcs
34 38 75 0,0 0,0 0 0x00000001 //primary
formats: XR15 RG16 XR24 AR24
props:
8 type:
flags: immutable enum
enums: Overlay=0 Primary=1 Cursor=2
value: 1
35 38 82 0,0 0,0 0 0x00000001 //鼠标,两者的区别是fb不同。
formats: AR24
props:
8 type:
flags: immutable enum
enums: Overlay=0 Primary=1 Cursor=2
value: 2
整个流程
文章来源:https://www.toymoban.com/news/detail-739505.html
文章来源地址https://www.toymoban.com/news/detail-739505.html
到了这里,关于modetest工具测试(linux-5.10)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!