Exynos4412 中断处理流程详解

发布者:upsilon30最新更新时间:2022-12-19 来源: zhihu关键字:Exynos4412  中断  处理流程 手机看文章 扫描二维码
随时随地手机看文章

  Linux 中,当外设触发中断后,大体处理流程如下:


  a -- 具体CPU architecture相关的模块会进行现场保护,然后调用machine driver对应的中断处理handler;


  b -- machine driver对应的中断处理handler中会根据硬件的信息获取HW interrupt ID,并且通过irq domain模块翻译成IRQ number;


  c -- 调用该IRQ number 对应的high level irq event handler,在这个high level的handler中,会通过和interupt controller交互,进行中断处理的flow control(处理中断的嵌套、抢占等),当然最终会遍历该中断描述符的IRQ action list,调用外设的specific handler来处理该中断;


  d -- 具体CPU architecture相关的模块会进行现场恢复;


  总结下来,整个过程可以分为三部分:1、硬件处理部分;2、汇编处理部分;3、C 处理部分;


  下面我们来追踪一下代码,了解当中断发生时,Linux 是如何处理的,前面的一些中断初始化部分就不再这里详述了,下面开始具体分析:


  一、硬件处理部分


  当一切准备好之后,一旦打开处理器的全局中断就可以处理来自外设的各种中断事件了。


  当外设(SOC内部或者外部都可以)检测到了中断事件,就会通过interrupt requestion line上的电平或者边沿(上升沿或者下降沿或者both)通知到该外设连接到的那个中断控制器,而中断控制器就会在多个处理器中选择一个,并把该中断通过IRQ(或者FIQ,本文不讨论FIQ的情况)分发给该processor。


  ARM处理器感知到了中断事件后,会进行下面一系列的动作(硬件处理部分):


  1、切换处理器模式


  修改 CPSR 寄存器中的 M[4:0],切换处理器模式位 IRQ Mode(这里M[4:0] 所添值为 10010);


  2、保护现场


  保存发生中断时,CPSR值与PC值(为恢复现场做准备);这里要注意,此时中断可能发生在 usr mode (用户空间),也可能发生在 SVC mode(内核空间);


  3、mask IRQ exception


  关闭IRQ中断,也就是设定CPSR.I = 1;


  4、设定PC值为IRQ exception vector


  实现向异常向量表的跳转,ARM处理器会跳转到IRQ的exception vector地址,到这硬件所做的工作就结束了,下面就是软件行为了。


  软件处理部分流程如下:


  可以看到 Vetor_irq 是汇编部分入口点,而Asm_do_irq 是C 部分入口点,下面分析Vetor_irq 向 Asm_do_irq 跳转过程


  二、汇编部分


  前面硬件部分结束后,跳转到相应的异常中断处理程序处执行,对于ARMv7向量表普遍是0xFFFF0018 ,而对于低向量PC=0x00000018


  假设在用户空间时,产生了外部硬件中断,这个时候的会跳转到异常向量表,向量表(vector table)的代码如下


  【arch/arm/kernel/entry-armv.S】

  __vectors_start:---------------〉在中断向量表被拷贝后,该地址就是0xffff0000.

  ARM( swi SYS_ERROR0 )

  THUMB( svc #0 )

  THUMB( nop )

  W(b) vector_und + stubs_offset

  W(ldr) pc, .LCvswi + stubs_offset

  W(b) vector_pabt + stubs_offset

  W(b) vector_dabt + stubs_offset

  W(b) vector_addrexcptn + stubs_offset

  W(b)vector_irq + stubs_offset----------〉当外部中断产生时,pc直接指向这个地址。

  W(b) vector_fiq + stubs_offset

  .globl __vectors_end

  1、IRQ mode中的处理 (vector table --- > vector_irq )

  IRQ mode的处理都在vector_irq中,vector_stub是一个宏,定义如下:

  /*

  * Vector stubs.

  *

  * This code is copied to 0xffff0200 so we can use branches in the

  * vectors, rather than ldr's. Note that this code must not

  * exceed 0x300 bytes.

  *

  * Common stub entry macro:

  * Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC

  *

  * SP points to a minimal amount of processor-private memory, the  address

  * of which is copied into r0 for the mode specific abort handler.

  */

  .macro vector_stub, name, mode, correction=0

  .align 5

  vector_name:

  .if correction

  sub lr, lr, #correction //因为硬件处理器是将当前指令的下两条指令的地址存储在lr寄存器中,所以这里需要减4,让他指向被中断指令的下一条,这样当中断被恢复时,可以继续被中断的指令继续执行。


  .endif //需要注意的是,这个时候的lr寄存器,已经是irq模式下的私有寄存器了,在中断产生时,硬件处理器已经自动为他赋了值。


  @

  @ Save r0, lr_ (parent PC) and spsr_

  @ (parent CPSR)

  @

  stmia sp, {r0, lr} @ save r0, lr//保存r0和lr寄存器,即被中断的下一条指令

  mrs lr, spsr

  str lr, [sp, #8] @ save spsr

  @

  @ Prepare for SVC32 mode. IRQs remain  disabled.//准备从中断模式切换到管理模式,不同的模式,对应各自不同的堆栈。

  @

  mrs r0, cpsr

  eor r0, r0, #(mode ^ SVC_MODE | PSR_ISETSTATE)

  msr spsr_cxsf, r0

  @

  @ the branch table must immediately follow this code

  @

  and lr, lr, #0x0f //获取被中断前,处理器所处的模式

  THUMB( adr r0, 1f )

  THUMB( ldr lr, [r0, lr, lsl #2] )

  mov r0, sp //让r0寄存器指向中断模式下堆栈的基地址

  ARM( ldr lr, [pc, lr, lsl #2] )

  movs pc, lr @ branch to handler in SVC mode,同时将中断模式下的spsr_irq(irq私有的)赋值给cpsr(该寄存器所有模式共享)


  ENDPROC(vector_name)

  从这可以看出 vector_stub 的使用方法:


  vector_stub, name, mode, correction=0

  上面这段究竟做了些什么呢?


  (1)我们期望在栈上保存发生中断时候的硬件现场(HW context),这里就包括ARM的core register。上一章我们已经了解到,当发生IRQ中断的时候,lr中保存了发生中断的PC+4,如果减去4的话,得到的就是发生中断那一点的PC值。


  (2)当前是IRQ mode,SP_irq在初始化的时候已经设定(12个字节)。在irq mode的stack上,依次保存了发生中断那一点的r0值、PC值以及CPSR值(具体操作是通过spsr进行的,其实硬件已经帮我们保存了CPSR到SPSR中了)。为何要保存r0值?因为随后的代码要使用r0寄存器,因此我们要把r0放到栈上,只有这样才能完完全全恢复硬件现场。


  (3)可怜的IRQ mode稍纵即逝,这段代码就是准备将ARM推送到SVC mode。如何准备?其实就是修改SPSR的值,SPSR不是CPSR,不会引起processor mode的切换(毕竟这一步只是准备而已)。


  (4)很多异常处理的代码返回的时候都是使用了stack相关的操作,这里没有。“movs pc, lr ”指令除了字面上意思(把lr的值付给pc),还有一个


  隐含的操作(movs中‘s’的含义):把SPSR copy到CPSR,从而实现了模式的切换。


  这里有个问题:中断为什么必须进入svc模式?


  一个最重要原因是:如果一个中断模式(例如从usr进入irq模式,在irq模式中)中重新允许了中断,并且在这个中断例程中使用了BL指令调用子程序,BL指令会自动将子程序返回地址保存到当前模式的sp(即r14_irq)中,这个地址随后会被在当前模式下产生的中断所破坏,因为产生中断时CPU会将当前模式的PC保存到r14_irq,这样就把刚刚保存的子程序返回地址冲掉。为了避免这种情况,中断例程应该切换到SVC或者系统模式,这样的话,BL指令可以使用r14_svc来保存子程序的返回地址。


  2、vector table --- > vector_irq ---> vector _stub


  对于IRQ Mode 则 vector_stub, irq, IRQ_MODE, 4


  __stubs_start:

  /*

  * Interrupt dispatcher

  */

  vector_stub irq, IRQ_MODE, 4 //减去4,确保返回发生中断之后的那条指令

  .long __irq_usr@ 0 (USR_26 / USR_32) //从用户态进入中断的处理函数 base address + 0

  .long __irq_invalid@ 1 (FIQ_26 / FIQ_32)

  .long __irq_invalid@ 2 (IRQ_26 / IRQ_32)

  .long __irq_svc@ 3 (SVC_26 / SVC_32) //从SVC进入中断的处理函数 base address + 12

  .long __irq_invalid@ 4

  .long __irq_invalid@ 5

  .long __irq_invalid@ 6

  .long __irq_invalid@ 7

  .long __irq_invalid@ 8

  .long __irq_invalid@ 9

  .long __irq_invalid@ a

  .long __irq_invalid@ b

  .long __irq_invalid@ c

  .long __irq_invalid@ d

  .long __irq_invalid@ e

  .long __irq_invalid@ f

  这里根据被中断时,处理器模式的不同,分别跳转到__irq_usr和__irq_svc两个分支。


  3、vector table --- > vector_irq ---> vector _stub ---> __irq_usr


  在这里我们以__irq_usr为例来说明:


  __irq_usr:

  usr_entry //进行中断前的硬件上下文的保存

  kuser_cmpxchg_check

  irq_handler

  get_thread_info tsk//获取被中断的用户进程或内核线程所对应的内核栈所对应的thread info结构。

  mov why, #0

  b ret_to_user_from_irq//恢复被中断时的上下文,然后继续被中断的进程或线程的执行

  UNWIND(.fnend )

  ENDPROC(__irq_usr)

  4、vector table --- > vector_irq ---> vector _stub ---> __irq_usr ---> usr_entry


  usr_entry展开如下:

  .macro usr_entry

  UNWIND(.fnstart )

  UNWIND(.cantunwind ) @ don't unwind the user space

  sub sp, sp, #S_FRAME_SIZE // #S_FRAME_SIZE的值为72

  ARM( stmib sp, {r1 - r12} )  //尽管当前是处于管理模式,但由于svc和usr的r0-r12是公共的,所以相当于保存用户模式的r1-r12寄存器

  THUMB( stmia sp, {r0 - r12} )

  ldmia r0, {r3 - r5} //将之前保存在中断模式堆栈中的r0_usr,lr,spsr分别存储到r3-r5中

  add r0, sp, #S_PC @ here for interlock avoidance #S_PC=60

  mov r6, #-1 @ "" "" "" ""

  str r3, [sp] @ save the "real" r0 copied

  @ from the exception stack

  @

  @ We are now ready to fill in the remaining blanks on the stack:

  @

  @ r4 - lr_, already fixed up for correct return/restart

  @ r5 - spsr_

  @ r6 - orig_r0 (see pt_regs definition in ptrace.h)

  @

  @ Also, separately save sp_usr and lr_usr

  @

  stmia r0, {r4 - r6}

  ARM( stmdb r0, {sp, lr}^ )//保存用户模式下的sp_usr,lr_usr

  THUMB( store_user_sp_lr r0, r1, S_SP - S_PC )

  @

  @ Enable the alignment trap while in kernel mode

  @

  alignment_trap r0

  @

  @ Clear FP to mark the first stack frame

  @

  zero_fp

  ifdef CONFIG_IRQSOFF_TRACER

  bl trace_hardirqs_off

  endif

  .endm

  代码执行到这里的时候,ARM处理已经切换到了【SVC mode】。一旦进入SVC mode,ARM处理器看到的寄存器已经发生变化,这里的sp已经变成了sp_svc了。因此,后续的压栈操作都是压入了发生中断那一刻的进程的(或者内核线程)内核栈(svc mode栈)。


  此时的管理模式的内核栈分布如下:


  需要说明的是:上图中的lr_irq即为用户模式下被中断指令的下一条指令,spsr_irq即为用户模式下被中断时的cpsr寄存器。


  5、vector table --- > vector_irq ---> vector _stub ---> __irq_usr ---> (usr_entry ---> irq_handler )


  usr_entry 结束后,会执行 irq_handler


  irq_handler的实现过程 archarmkernelentry-armv.S


  .macro irq_handler


  get_irqnr_preamble r5, lr


  @在include/asm/arch-s3c2410/entry-macro.s中定义了宏get_irqnr_preamble为空操作,什么都不做


  1: get_irqnr_and_base r0, r6, r5, lr @判断中断号,通过R0返回,3.5节有实现过程


  movne r1, sp


  @


  @ routine called with r0 = irq number, r1 = struct pt_regs *


  @


  adrne lr, 1b


  bne asm_do_IRQ @进入中断处理。


  ……


  .endm


  可以看到 bne asm_do_IRQ @进入中断处理 泪奔~~o(>_<)o ~~ 终于到了asm_do_IRQ


  可以看到整个跳转过程:


  vector table --- > vector_irq --->vector_stub, irq, IRQ_MODE, 4 ---> __irq_usr ---> usr_entry ---> irq_handler --->asm_do_IRQ


  三、C语言处理部分


  1、 asm_do_IRQ


  asm_do_IRQ实现过程,arch/arm/kernel/irq.c

  * asm_do_IRQ is the interface to be used from assembly code.

  */

  asmlinkage void __exception_irq_entry

  asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

  {

  handle_IRQ(irq, regs);

  }

其中关键一步handle_IRQ(irq, regs)


  2、handle_IRQ(irq, regs)


  /*

  * handle_IRQ handles all hardware IRQ's. Decoded IRQs should

  * not come via this function. Instead, they should provide their

  * own 'handler'. Used by platform code implementing C-based 1st

  * level decoding.

  */

  void handle_IRQ(unsigned int irq, struct pt_regs *regs)

  {

  struct pt_regs *old_regs = set_irq_regs(regs);

  irq_enter();

  /*

  * Some hardware gives randomly wrong interrupts. Rather

  * than crashing, do something sensible.

  */

  if (unlikely(irq >= nr_irqs)) {

  if (printk_ratelimit())

  printk(KERN_WARNING "Bad IRQ%un", irq);

  ack_bad_irq(irq);

  } else {

  generic_handle_irq(irq);

  }

  irq_exit();

  set_irq_regs(old_regs);

  }

  主要调用generic_handle_irq(irq)


  3、generic_handle_irq(irq)


  include/linux/irq.h

  static inline void generic_handle_irq_desc(unsigned int irq, struct  irq_desc *desc)

  {

  #ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ

  desc->handle_irq(irq, desc);

  #else

  if (likely(desc->handle_irq))

  desc->handle_irq(irq, desc);

  else

  __do_IRQ(irq);

  #endif

  }

  static inline void generic_handle_irq(unsigned int irq)

  {

  generic_handle_irq_desc(irq, irq_to_desc(irq));

  }

  generic_handle_irq调用前面定义的generic_handle_irq_desc

  4、generic_handle_irq_des

  static inline void generic_handle_irq_desc(unsigned int irq, struct  irq_desc *desc)

  {

  desc->handle_irq(irq, desc);

  }

  而 generic_handle_irq_desc也没做什么,调用desc——>handle_irq,这个函数就是irq_desc中的成员


  5、irq_desc结构体


  Linux内核将所有中断统一编号,使用irq_desc结构来描述中断:每个数组项对应一个中断(也可能是一组中断,它们使用共同的中断号),里面记录了中断的名称,中断状态,中断标记,并提供硬件访问函数(清除,屏蔽,使能中断),提供了这个中断的处理函数的入口,通过它可以调用用户注册的中断处理函数


  include/linux/irq.h

  {

  .........

  irq_flow_handler_t handle_irq; //当前的中断处理函数入口

  struct irq_chip *chip; //底层的硬件访问

  ..........

  struct irqaction *action; //用户提供的中断处理函数链表

  unsigned int status; //IRQ状态

  ...........

  const char *name; //中断名称

  } ____cacheline_internodealigned_in_smp;

  Handle_irq是这个或者这组中断的处理函数入口

  这里调用desc->handle_irq分为俩种情况,一是单独的中断号的,一是共享中断号的,俩者的区别在于后者需要先判断是共享中断的中的哪一个然后再真正的去调用handle_irq,所以我这里分析一下单独中断号的处理流程,共享中断也是一样可以分析。


  我们分析一个具体的,以外部中断为例


  for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {

  irqdbf("registering irq %d (ext int)n", irqno);

  set_irq_chip(irqno, &s3c_irq_eint0t4);

  set_irq_handler(irqno, handle_edge_irq);

  set_irq_flags(irqno, IRQF_VALID);

  }

  上面代码我们看到,set_irq_handler的值是handler_edge_irq ,这里是处理边沿触发的中断函数,当然还有电平触发方式的中断(handler_level_irq),继续看代码

[1] [2]
关键字:Exynos4412  中断  处理流程 引用地址:Exynos4412 中断处理流程详解

上一篇:ARM指令adr adrl ldr mov简单科普
下一篇:嵌入式ARM系统异常和中断处理知识总结

推荐阅读最新更新时间:2024-11-12 08:42

Tiny210裸机之按键中断
start.S源码: .global _start _start: ldr sp, =0xD0030000 // 初始化栈,因为后面要调用C函数 bl clock_init // 初始化时钟 bl ddr_init // 初始化内存 bl nand_init // 初始化NAND ldr r0, =0x36000000 // 要拷贝到DDR中的位置 ldr r1, =0x0 // 从NAND的0地址开始拷贝 ldr r2, =bss_start // BSS段的开始地址 sub r2,r2,r0 /
[单片机]
PIC18FXX2之INT0中断
RB0/INT0、RB1/INT1及RB2/INT2引脚的外部中断是边沿触发的; 如果INTCON2 寄存器中相应的INTEDGx位被置1,则为上升沿触发;如果该 INTEDGx 位清零,则为下降沿触发。 当RBx/INTx引脚上出现一个有效边沿时,相应标志位 INTxF 被置1 。在重新使能该中断前,必须在中断服务程序中先用软件将标志位INTxF 清零。 通过对相应的使能位INTxE 清零,可以禁止该中断。 如果INTxE 位在进入休眠状态前被置1 ,则所有的外部中断(INT0、INT1 及INT2)能把处理器从休眠状态中唤醒。如果全局中断使能位 GIE 被置1 ,则处理器将在唤醒之后转移到中断向量。 INT1 和INT2 的中
[单片机]
关于STM32利用硬件仿真串口中断处理函数应注意的问题
我们在利用jlink或其他仿真器对串口中断处理函数的数据接收进行仿真时,如果在中断函数中设置了断点,我们向串口发送数据端会采用逐个字节发送而不能采用一次性发送多个字节,当然从广义上来讲,你一次发那么多字节我在断点处程序已经停止了再运行肯定你的数据我会丢失啊,所以需要逐个字节发送这是可以理解的。而我这里讲的是如果你一次发送多个字节将会导致什么后果的问题. 假设1:假如你的断点设置在res = USART_ReceiveData(USART1)之后,以下面发送的这串字符为例,那么实际上当你停到断点处看到接收到fe时由于DR寄存器之前已经清空,实际上01已经存到DR中了,这时你运行会再次运行到这个断点,即受到了fe 和01两
[单片机]
关于STM32利用硬件仿真串口<font color='red'>中断</font><font color='red'>处理</font>函数应注意的问题
C51/C52单片机的定时器计数器与中断(内附代码)
一、定时器/计数器简介 设置等待时间,到达等待时间之后执行指定的硬件操作。 定时器最基本的功能就是定时,比如说定时发送串口数据,定时采集AD数据,如果将定时器和IO结合起来就可以实现非常丰富的功能,可以测量输入信号的脉冲宽度,可以产生PWM方波,定时器产生PWM控制电机状态是工业控制的普通。 二、定时器/计数器相关寄存器介绍 1、计数寄存器TH和HL T/C是16位的,计数寄存器由TH高8位和TL低8位构成 对应T/C0为TH0和TL0, 对应T/C1为TH1和TL1,定时器/计数器的初始值通过TH0/TL0和TH1/TL1设置 2、定时器/计数器控制寄存器TCON
[单片机]
C51/C52单片机的定时器计数器与<font color='red'>中断</font>(内附代码)
stm32f103——基本定时器与定时器中断
我们前面已经学过了滴答定时器,那么定时器的原理与它一样,只不过滴答定时器是在内核中的定时器,而定时器是片上外设。 定时器分为:基本定时器和通用定时器。 而基本定时器所拥有的功能,通用定时器都有。所以,通用定时器内集成了基本定时器。 定时器作用:产生一个精准的定时 stm32f03中基本定时器为TIM6和TIM7: 16位预分频器:将输入进来的72Mhz进行预分频,但是它是16位的,所以它的分配系数范围为1~65535。 16位自动重装载累加器:用来装载我们设置的计数值。当16位计数单元计数完后,它就将设置的计数值传给计数单元,让计数单元重新开始计数。 16位计数单元:它的内部就是
[单片机]
stm32f103——基本定时器与定时器<font color='red'>中断</font>
IAR For AVR 定时器溢出中断 (使用小结)
关于溢出中断不管是哪个单片机都是不断累加,使其寄存器溢出触发中断,然后跳转到中断函数处执行中断服务程序。对于定时器初值的设定可以加深对定时器的工作原理的理解。 ATMega16 里面有8位和16位两种定时器,他们何时会溢出这个是固定的,也就是到达他们的计数范围的最大值就会产生中断,8位的定时器的最大计数范围是0~256(2的8次方),就是累加到256后他就会产生中断,16位的定时器最大计数范围是0~65536(2的16次方),累加到65536时他就会产生中断。 而我们所谓的计数初值是就是要设定定时器在什么地方开始计数,以8位定时器为例比如:初值为100,所以定时器从100开始累加,累加了156次,加到256后产
[单片机]
IAR For AVR 定时器溢出<font color='red'>中断</font> (使用小结)
MSP430的中断优先级、打开关闭、中断嵌套
优先级顺序从高到低为: PORT2_VECTOR (1 * 2u) PORT1_VECTOR (4 * 2u) TIMERA1_VECTOR (5 * 2u) TIMERA0_VECTOR (6 * 2u) ADC_VECTOR (7 * 2u) USART0TX_VECTOR (8 * 2u) USART0RX_VECTOR (9 * 2u) WDT_VECTOR (10 * 2u) COMPARATORA_VECTOR (11 * 2u) TIMERB1_VECTOR (12 * 2u) TIMERB0_VECTOR (13 * 2u) NMI_VECTOR (14 * 2u) RESET_VECTOR
[单片机]
STM32中断优先级void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct) { u32 tmppriority = 0x00, tmpreg = 0x00, tmpmask = 0x00; u32 tmppre = 0, tmpsub = 0x0F; /* Check the parameters */ assert(IS_FUNCTIONAL_STATE(NVIC_InitStruct- NVIC_IRQChannelCmd)); assert(IS_NVIC_IRQ_CHANNEL(NVIC_InitStruct- NVIC_IRQChannel)); assert(IS_NVIC_PREE
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
  • 学习ARM开发(16)
    ARM有很多东西要学习,那么中断,就肯定是需要学习的东西。自从CPU引入中断以来,才真正地进入多任务系统工作,并且大大提高了工作效率。采 ...
  • 学习ARM开发(17)
    因为嵌入式系统里全部要使用中断的,那么我的S3C44B0怎么样中断流程呢?那我就需要了解整个流程了。要深入了解,最好的方法,就是去写程序 ...
  • 学习ARM开发(18)
    上一次已经了解ARM的中断处理过程,并且可以设置中断函数,那么它这样就可以工作了吗?答案是否定的。因为S3C44B0还有好几个寄存器是控制中 ...
  • 嵌入式系统调试仿真工具
    嵌入式硬件系统设计出来后就要进行调试,不管是硬件调试还是软件调试或者程序固化,都需要用到调试仿真工具。 随着处理器新品种、新 ...
  • 最近困扰在心中的一个小疑问终于解惑了~~
    最近在驱动方面一直在概念上不能很好的理解 有时候结合别人写的一点usb的例子能有点感觉,但是因为arm体系里面没有像单片机那样直接讲解引脚 ...
  • 学习ARM开发(1)
  • 学习ARM开发(2)
  • 学习ARM开发(4)
  • 学习ARM开发(6)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved