Linux芯片级移植与底层驱动(基于374内核)_第1页
Linux芯片级移植与底层驱动(基于374内核)_第2页
Linux芯片级移植与底层驱动(基于374内核)_第3页
Linux芯片级移植与底层驱动(基于374内核)_第4页
Linux芯片级移植与底层驱动(基于374内核)_第5页
已阅读5页,还剩21页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

本文格式为Word版,下载可任意编辑——Linux芯片级移植与底层驱动(基于374内核)

1.SoCLinux底层驱动的组成和现状

为了让Linux在一个全新的ARMSoC上运行,需要提供大量的底层支撑,如定时器节拍、中断控制器、SMP启动、CPUhotplug以及底层的GPIO、clock、pinctrl和DMA硬件的封装等。定时器节拍、中断控制器、SMP启动和CPUhotplug这几部分相对来说没有像早期GPIO、clock、pinctrl和DMA的实现那么杂乱,基本上有个固定的套路。定时器节拍为Linux基于时间片的调度机制以及内核和用户空间的定时器提供支撑,中断控制器的驱动则使得Linux内核的工程师可以直接调用local_irq_disable()、disable_irq()等通用的中断API,而SMP启动支持则用于让SoC内部的多个CPU核都投入运行,CPUhotplug则运行运行时挂载或拔除CPU。这些工作,在Linux3.7内核中,进行了良好的层次划分和架构设计。

在GPIO、clock、pinctrl和DMA驱动方面,Linux2.6时代,内核已或多或少有GPIO、clock等底层驱动的架构,但是核心层的代码太薄弱,各SoC对这些基础设施实现方面存在巨大差异,而且每个SoC依旧需要实现大量的代码。pinctrl和DMA则最为混乱,几乎各家公司都定义了自己的独特的实现和API。

社区必需改变这种局面,于是内核社区在2023~2023年进行了如下工作,这些工作在目前的3.7内核中基本准备就绪:§ST-Ericsson的工程师LinusWalleij提供了新的pinctrl驱动架构,内核新增加一个drivers/pinctrl目录,支撑SoC上的引脚复用,各个SoC的实现代码统一放入该目录;

§TI的工程师MikeTurquette提供了commonclk框架,让具体SoC实现clk_ops成员函数并通过clk_register、clk_register_clkdev注册时钟源以及源与设备对应关系,具体的clock驱动都统一迁移到drivers/clk目录;§建议各SoC统一采用dmaengine架构实现DMA驱动,该架构提供了通用的DMA通道API如

dmaengine_prep_slave_single()、dmaengine_submit()等,要求SoC实现dma_device的成员函数,实现代码统一放入drivers/dma目录;

§在GPIO方面,drivers/gpio下的gpiolib已能与新的pinctrl完美共存,实现引脚的GPIO和其他功能之间的复用,具体的SoC只需实现通用的gpio_chip结构体的成员函数。

经过以上工作,基本上就把芯片底层的基础架构方面的驱动的架构统一了,实现方法也统一了。另外,目前GPIO、clock、pinmux等功能都能良好的进行DeviceTree的映射处理,譬如我们可以方面的在.dts中定义一个设备要的时钟、pinmux引脚以及GPIO。

除了上述基础设施以外,在将Linux移植入新的SoC过程中,工程师往往猛烈依靠于早期的printk功能,内核则提供了相关的DEBUG_LL和EARLY_PRINTK支持,只需要SoC提供商实现少量的callback或宏。

本文主要对上述各个组成部分进行架构上的剖析以及关键的实现部分的实例分析,以求完整归纳将Linux移植入新SoC的主要工作。本文基于3.7.4内核。

2.用于操作系统节拍的timer驱动

Linux2.6的早期(2.6.21之前)基于tick设计,一般SoC公司在将Linux移植到自己的芯片上的时候,会从芯片内部找一个定时器,并将该定时器配置会HZ的频率,在每个时钟节拍到来时,调用ARMLinux内核核心层的timer_tick()函数,从而引发系统里的一系列行为。如2.6.17中arch/arm/mach-s3c2410/time.c的做法是:127/*

128*IRQhandlerforthetimer129*/

130staticirqreturn_t

131s3c2410_timer_interrupt(intirq,void*dev_id,structpt_regs*regs)132{

133write_seqlock(134timer_tick(regs);

135write_sequnlock(136returnIRQ_HANDLED;137}138

139staticstructirqactions3c2410_timer_irq={140.name=\141.flags=SA_INTERRUPT|SA_TIMER,142.handler=s3c2410_timer_interrupt,143};

252staticvoid__inits3c2410_timer_init(void)253{

254s3c2410_timer_setup();

255setup_irq(IRQ_TIMER4,256}257

当前Linux多采用tickless方案,并支持高精度定时器,内核的配置一般会使能NO_HZ(即tickless,或者说动态tick)和HIGH_RES_TIMERS。要强调的是tickless并不是说系统中没有时钟节拍了,而是说这个节拍不再像以前那样,周期性地产生。Tickless意味着,根据系统的运行状况,以事件驱动的方式动态决定下一个tick在何时发生。假使画一个时间轴,周期节拍的系统tick中断发生的时序看起来如下:

而NO_HZ的Linux看起来则是,2次定时器中断发生的时间间隔可长可短:

在当前的Linux系统中,SoC底层的timer被实现为一个clock_event_device和clocksource形式的驱动。在clock_event_device结构体中,实现其set_mode()和set_next_event()成员函数;在clocksource结构体中,主要实现read()成员函数。而定时器中断服务程序中,不再调用timer_tick(),而是调用clock_event_device的event_handler()成员函数。一个典型的SoC的底层tick定时器驱动形如:61staticirqreturn_txxx_timer_interrupt(intirq,void*dev_id)

62{

63structclock_event_device*ce=dev_id;65…

70ce-84

85returncycles;86}87

88staticintxxx_timer_set_next_event(unsignedlongdelta,89structclock_event_device*ce)90{

91unsignedlongnow,next;92

93writel_relaxed(XXX_TIMER_LATCH_BIT,xxx_timer_base+XXX_TIMER_LATCH);94now=readl_relaxed(xxx_timer_base+XXX_TIMER_LATCHED_LO);95next=now+delta;

96writel_relaxed(next,xxx_timer_base+SIRFSOC_TIMER_MATCH_0);97writel_relaxed(XXX_TIMER_LATCH_BIT,xxx_timer_base+XXX_TIMER_LATCH);98now=readl_relaxed(xxx_timer_base+XXX_TIMER_LATCHED_LO);99

100returnnext-now定时器的中断服务程序xxx_timer_interrupt()

在该中断服务程序中,直接调用clock_event_device的event_handler()成员函数,event_handler()成员函数的具体工作也是Linux内核根据Linux内核配置和运行状况自行设置的。clock_event_device的set_mode成员函数xxx_timer_set_mode()

用于设置定时器的模式以及resume和shutdown等功能,目前一般采用ONESHOT模式,即一次一次产生中断。当然新版的Linux也可以使用老的周期性模式,假使内核编译的时候未选择NO_HZ,该底层的timer驱动仍旧可以为内核的运行提供支持。

这些函数的结合,使得ARMLinux内核底层所需要的时钟得以运行。下面举一个典型的场景,假定定时器的晶振时钟频率为1MHz(即计数器每加1等于1us),应用程序透过nanosleep()API睡眠100us,内核会据此换算出下一次定时器中断的delta值为100,并间接调用到xxx_timer_set_next_event()去设置硬件让其在100us后产生中断。100us后,中断产生,xxx_timer_interrupt()被调用,event_handler()会间接唤醒睡眠的进程导致nanosleep()函数返回,从而用户进程继续。

这里特别要强调的是,对于多核处理器来说,一般的做法是给每个核分派一个独立的定时器,各个核根据自身的运行状况动态设置自己时钟中断发生的时刻。看看我们说运行的电脑的localtimer中断即知:barry@barry-VirtualBox:~$cat/proc/interruptsCPU0CPU1CPU2CPU3…

20:945000IO-APIC-fasteoivboxguest

21:44560021592IO-APIC-fasteoiahci,Intel82801AA-ICH22:26000IO-APIC-fasteoiohci_hcd:usb2NMI:0000Non-maskableinterrupts

LOC:177279177517177146177139LocaltimerinterruptsSPU:0000SpuriousinterruptsPMI:0000Performancemonitoring…

而比较低效率的方法则是只给CPU0提供定时器,由CPU0将定时器中断透过IPI(InterProcessorInterrupt,处理器间中断)广播到其他核。对于ARM来讲,1号IPIIPI_TIMER就是来负责这个广播的,从arch/arm/kernel/smp.c可以看出:

62enumipi_msg_type{63IPI_WAKEUP,64IPI_TIMER,65IPI_RESCHEDULE,66IPI_CALL_FUNC,

67IPI_CALL_FUNC_SINGLE,68IPI_CPU_STOP,69};

3.中断控制器驱动

在Linux内核中,各个设备驱动可以简单地调用request_irq()、enable_irq()、disable_irq()、local_irq_disable()、local_irq_enable()等通用API完成中断申请、使能、阻止等功能。在将Linux移植到新的SoC时,芯片供应商需要提供该部分API的底层支持。

local_irq_disable()、local_irq_enable()的实现与具体中断控制器无关,对于ARMv6以上的体系架构而言,是直接调用CPSID/CPSIE指令进行,而对于ARMv6以前的体系结构,则是透过MRS、MSR指令来读取和设置ARM的CPSR寄放器。由此可见,local_irq_disable()、local_irq_enable()针对的并不是外部的中断控制器,而是直接让CPU本身不响应中断请求。相关的实现位于arch/arm/include/asm/irqflags.h:

[cpp]viewplaincopy

1.11#if__LINUX_ARM_ARCH__5.305unsignedint(*irq_startup)(structirq_data*data);6.

7.306void(*irq_shutdown)(structirq_data*data);8.

9.307void(*irq_enable)(structirq_data*data);10.

11.308void(*irq_disable)(structirq_data*data);12.13.30914.

15.310void(*irq_ack)(structirq_data*data);16.

17.311void(*irq_mask)(structirq_data*data);18.

19.312void(*irq_mask_ack)(structirq_data*data);20.

21.313void(*irq_unmask)(structirq_data*data);22.

23.314void(*irq_eoi)(structirq_data*data);24.25.31526.

27.316int(*irq_set_affinity)(structirq_data*data,conststructcpumask*dest,boolforce);28.

29.317int(*irq_retrigger)(structirq_data*data);30.

31.318int(*irq_set_type)(structirq_data*data,unsignedintflow_type);32.

33.319int(*irq_set_wake)(structirq_data*data,unsignedinton);34.

35.334};

各个芯片公司会将芯片内部的中断控制器实现为irq_chip驱动的形式。受限于中断控制器硬件的能力,这些成员函数并不一定需要全部实现,有时候只需要实现其中的部分函数即可。譬如drivers/pinctrl/pinctrl-sirf.c驱动中的

[cpp]viewplaincopy

..

1438staticstructirq_chipsirfsoc_irq_chip={

1439.name=\

1440.irq_ack=sirfsoc_gpio_irq_ack,

1441.irq_mask=sirfsoc_gpio_irq_mask,

9.1442.irq_unmask=sirfsoc_gpio_irq_unmask,10.

11.1443.irq_set_type=sirfsoc_gpio_irq_type,12.

13.1444};

我们只实现了其中的ack、mask、unmask和set_type成员函数,ack函数用于清中断,mask、unmask用于中断屏蔽和取消中断屏蔽、set_type则用于配置中断的触发方式,如高电平、低电平、上升沿、下降沿等。至于enable_irq()的时候,虽然没有实现irq_enable成员函数,但是内核会间接调用到irq_unmask成员函数,这点从kernel/irq/chip.c可以看出:

[cpp]viewplaincopy

1.192voidirq_enable(structirq_desc*desc)2.3.193{4.

5.194irq_state_clr_disabled(desc);6.

7.195if(desc-;39interrupt-controller;

40reg=,;41};

而在arch/arm/mach-exynos/mach-exynos5-dt.c中即含有:

95DT_MACHINE_START(EXYNOS5_DT,\96/*Maintainer:KukjinKim*/97.init_irq=exynos5_init_irq,

98.smp=smp_ops(exynos_smp_ops),99.map_io=exynos5250_dt_map_io,100.handle_irq=gic_handle_irq,

101.init_machine=exynos5250_dt_machine_init,102.init_late=exynos_init_late,103.timer=&exynos4_timer,

104.dt_compat=exynos5250_dt_compat,105.restart=exynos5_restart,106MACHINE_END

4.SMP多核启动以及CPU热插拔驱动

在Linux系统中,对于多核的ARM芯片而言,Bootrom代码中,CPU0会率先起来,引导Bootloader和Linux内核执行,而其他的核则在上电时Bootrom一般将自身置于WFI或者WFE状态,并等待CPU0给其发CPU核间中断(IPI)或事件(一般透过SEV指令)唤醒之。一个典型的启动过程如下图:

被CPU0唤醒的CPUn可以在运行过程中进行热插拔。譬如运行如下命令即可卸载CPU1并且将CPU1上的任务全部迁移到其他CPU:

#echo0129set_cpu_possible(i,true);130

131set_smp_cross_call(gic_raise_softirq);132}

而smp_operations的成员函数smp_prepare_cpus()即vexpress_smp_prepare_cpus()则会透过v2m_flags_set(virt_to_phys(versatile_secondary_startup))设置其他CPU的启动地址为versatile_secondary_startup:

179staticvoid__initvexpress_smp_prepare_cpus(unsignedintmax_cpus)180{181…189190/*

191*Writetheaddressofsecondarystartupintothe192*system-wideflagsregister.Thebootmonitorwaits193*untilitreceivesasoftinterrupt,andthenthe194*secondaryCPUbranchestothisaddress.195*/

196v2m_flags_set(virt_to_phys(versatile_secondary_startup));197}

注意这部分的具体实现方法是SoC相关的,由芯片的设计以及芯片内部的Bootrom决定。对于VEXPRESS来讲,设置方法如下:

139void__initv2m_flags_set(u32data)

140{

141writel(~0,v2m_sysreg_base+V2M_SYS_FLAGSCLR);142writel(data,v2m_sysreg_base+V2M_SYS_FLAGSSET);143}

即填充v2m_sysreg_base+V2M_SYS_FLAGSCLR地址为0xFFFFFFFF,将其他CPU初始启动执行的指令地址填入v2m_sysreg_base+V2M_SYS_FLAGSSET。这2个地址属于芯片实现时候设定的。填入的CPUn的起始地址都透过virt_to_phys()转化为物理地址,由于此时CPUn的MMU尚未开启。

比较关键的是smp_operations的成员函数smp_boot_secondary(),它完成最终的CPUn的唤醒工作:27staticvoid__cpuinitwrite_pen_release(intval)28{

29pen_release=val;30smp_wmb();

31__cpuc_flush_dcache_area((void*)32outer_clean_range(__pa(33}

59int__cpuinitversatile_boot_secondary(unsignedintcpu,structtask_struct*idle)60{

61unsignedlongtimeout;6263/*

64*Setsynchronisationstatebetweenthisbootprocessor65*andthesecondaryone66*/

67spin_lock(6869/*

70*Thisisreallybeltandbraces;weholdunintendedsecondary71*CPUsintheholdingpenuntilwe'rereadyforthem.However,72*sincewehaven'tsentthemasoftinterrupt,theyshouldn't73*bethere.74*/

75write_pen_release(cpu_logical_map(cpu));7677/*

78*SendthesecondaryCPUasoftinterrupt,therebycausing79*thebootmonitortoreadthesystemwideflagsregister,80*andbranchtotheaddressfoundthere.81*/

82gic_raise_softirq(cpumask_of(cpu),0);83

84timeout=jiffies+(1*HZ);

85while(time_before(jiffies,timeout)){86smp_rmb();

87if(pen_release==-1)88break;89

90udelay(10);91}9293/*

94*nowthesecondarycoreisstartingupletitrunits95*calibrations,thenwaitforittofinish96*/

97spin_unlock(98

99returnpen_release!=-1?-ENOSYS:0;100}

上述代码中高亮的部分首先会将pen_release变量设置为要唤醒的CPU核的CPU号cpu_logical_map(cpu),而后透过gic_raise_softirq(cpumask_of(cpu),0)给CPUcpu发起0号IPI,这个时候,CPUcpu核会从前面smp_operations中的smp_prepare_cpus()成员函数即vexpress_smp_prepare_cpus()透过v2m_flags_set()设置的其他CPU核的起始地址versatile_secondary_startup开始执行,假使顺利的话,该CPU会将原先为正数的pen_release写为-1,以便CPU0从等待pen_release成为-1的循环中跳出。

versatile_secondary_startup实现于arch/arm/plat-versatile/headsmp.S,是一段汇编:21ENTRY(versatile_secondary_startup)22mrcp15,0,r0,c0,c0,523andr0,r0,#1524adrr4,1f25ldmiar4,{r5,r6}26subr4,r4,r527addr6,r6,r428pen:ldrr7,[r6]29cmpr7,r030bnepen3132/*

33*we'vebeenreleasedfromtheholdingpen:secondary_stack

34*shouldnowcontaintheSVCstackforthiscore35*/

36bsecondary_startup37

38.align391:.long.

40.longpen_release

41ENDPROC(versatile_secondary_startup)

第1段高亮的部分实际上是等待pen_release成为CPU0设置的cpu_logical_map(cpu),一般直接就成立了。第2段高亮的部分则调用到内核通用的secondary_startup()函数,经过一系列的初始化如MMU等,最终新的被唤醒的CPU将调用到smp_operations的smp_secondary_init()成员函数,对于本例为versatile_secondary_init():37void__cpuinitversatile_secondary_init(unsignedintcpu)38{39/*

40*ifanyinterruptsarealreadyenabledfortheprimary41*core(e.g.timerirq),thentheywillnothavebeenenabled42*forus:doso43*/

44gic_secondary_init(0);4546/*

47*lettheprimaryprocessorknowwe'reoutofthe48*pen,thenheadoffintotheCentrypoint49*/

50write_pen_release(-1);5152/*

53*Synchronisewiththebootthread.54*/

55spin_lock(56spin_unlock(57}

上述代码中高亮的那1行会将pen_release写为-1,于是CPU0还在执行的versatile_boot_secondary()函数中的如下循环就退出了:

85while(time_before(jiffies,timeout)){86smp_rmb();

87if(pen_release==-1)88break;

89

90udelay(10);91}

此后CPU0和新唤醒的其他CPU各自狂奔。整个系统在运行过程中会进行实时进程和正常进程的动态负载均衡。CPUhotplug的实现也是芯片相关的,对于VEXPRESS而言,实现了smp_operations的cpu_die()成员函数即vexpress_cpu_die()。它会在进行CPUn的拔除操作时将CPUn投入低功耗的WFI状态,相关代码位于arch/arm/mach-vexpress/hotplug.c:

90void__refvexpress_cpu_die(unsignedintcpu)91{

92intspurious=0;9394/*

95*we'rereadyforshutdownnow,sodoit96*/

97cpu_enter_lowpower();

98platform_do_lowpower(cpu,99100/*

101*bringthisCPUbackintotheworldofcache102*coherency,andthenrestoreinterrupts103*/

104cpu_leave_lowpower();105

106if(spurious)

107pr_warn(\108}

57staticinlinevoidplatform_do_lowpower(unsignedintcpu,int*spurious)58{59/*

60*thereisnopower-controlhardwareonthisplatform,soall61*wecandoisputthecoreintoWFI;thisissafeasthecalling62*codewillhavealreadydisabledinterrupts63*/64for(;;){65wfi();66

67if(pen_release==cpu_logical_map(cpu)){68/*

69*OK,properwakeup,we'redone70*/71break;72}73

74/*

75*Gettinghere,meansthatwehavecomeoutofWFIwithout76*havingbeenwokenup-thisshouldn'thappen77*

78*Justnoteithappening-whenwe'rewoken,wecanreport79*itsoccurrence.80*/

81(*spurious)++;82}83}

CPUn睡眠于wfi(),之后再次online的时候,又会由于CPU0给它发出的IPI而从wfi()函数返回继续执行,醒来时CPUn也判决了是否pen_release==cpu_logical_map(cpu)成立,以确定该次醒来确确实实是由CPU0唤醒的一次正常醒来。

5.DEBUG_LL和EARLY_PRINTK

在Linux启动的早期,console驱动还没有投入运行。当我们把Linux移植到一个新的SoC的时候,工程师一般十分需要早期就可以执行printk()功能以跟踪调试启动过程。内核的DEBUG_LL和EARLY_PRINTK选项为我们提供了这样的支持。而在Bootloader引导内核执行的bootargs中,则需要使能earlyprintk选项。

为了让DEBUG_LL和EARLY_PRINTK可以运行,Linux内核中需实现早期解压过程打印需要的putc()和后续的addruart、senduart和waituart等宏。以CSRSiRFprimaII为例,putc()的实现位于arch/arm/mach-prima2/include/mach/uncompress.h:22static__inline__voidputc(charc)23{24/*

25*duringkerneldecompression,allmappingsareflat:26*virt_addr==phys_addr27*/

28while(__raw_readl((void__iomem*)SIRFSOC_UART1_PA_BASE+SIRFSOC_UART_TXFIFO_STATUS)2931

32__raw_writel(c,(void__iomem*)SIRFSOC_UART1_PA_BASE+SIRFSOC_UART_TXFIFO_DATA);33}

由于解压过程中,MMU还没有初始化,所以这个时候的打印是直接往UART端口FIFO对应的物理地址丢打印字符。addruart、senduart和waituart等宏的实现位于每个SoC对应的MACHINE代码目录的

include/mach/debug-macro.S,SiRFprimaII的实现mach-prima2/include/mach/debug-macro.S如下:12.macroaddruart,rp,rv,tmp

13ldr\\rp,=SIRFSOC_UART1_PA_BASE@physical14ldr\\rv,=SIRFSOC_UART1_VA_BASE@virtual15.endm16

17.macrosenduart,rd,rx

18str\\rd,[\\rx,#SIRFSOC_UART_TXFIFO_DATA]19.endm20

21.macrobusyuart,rd,rx22.endm23

24.macrowaituart,rd,rx

251001:ldr\\rd,[\\rx,#SIRFSOC_UART_TXFIFO_STATUS]26tst\\rd,#SIRFSOC_UART1_TXFIFO_EMPTY27beq1001b28.endm

其中的senduart完成了往UART的FIFO丢打印字符的过程。waituart则相当于一个流量握手,等待FIFO为空。这些宏最终会被内核的arch/arm/kernel/debug.S引用。

6.GPIO驱动

在drivers/gpio下实现了通用的基于gpiolib的GPIO驱动,其中定义了一个通用的用于描述底层GPIO控制器的gpio_chip结构体,并要求具体的SoC实现gpio_chip结构体的成员函数,最终透过gpiochip_add()注册gpio_chip。gpio_chip结构体封装了底层的硬件的GPIOenable/disable等操作,它定义为:94structgpio_chip{

95constchar*label;96structdevice*dev;97structmodule*owner;98

99int(*request)(structgpio_chip*chip,100unsignedoffset);101void(*free)(structgpio_chip*chip,102unsignedoffset);103

104int(*direction_input)(structgpio_chip*chip,105unsignedoffset);106int(*get)(structgpio_chip*chip,107unsignedoffset);

108int(*direction_output)(structgpio_chip*chip,109unsignedoffset,intvalue);110int(*set_debounce)(structgpio_chip*chip,111unsignedoffset,unsigneddebounce);112

113void(*set)(structgpio_chip*chip,114unsignedoffset,intvalue);115

116int(*to_irq)(structgpio_chip*chip,117unsignedoffset);118

119void(*dbg_show)(structseq_file*s,

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论