A fairly detailed explanation of the MINI2440 key driver

Publisher:泉地水无痕Latest update time:2016-08-13 Source: eefocusKeywords:MINI2440 Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere
/*mini2440_buttons_my.c*/

/*_my*/ is added at the end

/*Button driver*/

/*Key resources used by mini2440*/
/******************************************************/
/* Key Corresponding IO Register Corresponding Interrupt Pin*/
/* K1 GPG0 EINT8 */
/* K2 GPG3 EINT11 */
/* K3 GPG5 EINT13 */
/* K4 GPG6 EINT14 */
/* K5 GPG7 EINT15 */
/* K6 GPG11 EINT19 */
/******************************************************/

/*We need to figure out who is the input*/
/*Here, the button controls the corresponding interrupt pin, thereby controlling the corresponding IO register*/
/*It is equivalent to information input from the outside*/
/*What we need to do is to take corresponding response actions based on the corresponding input information*/
/*This achieves the purpose of interrupt response*/
/*The core is to detect*/
/*So, how to detect?*/
/*What to detect?*/

/*How do you know what resources a device actually uses?*/
/*This is a very important question*/
/*I think you should look at the specific circuit schematic*/
/*Only by looking at the diagram can you understand the specific circuit connection*/
/*Thus, you can know the hardware resources required by the device*/
/*The manufacturer's schematics are usually more detailed*/

/*Referenced header file*/

#include /*Module related*/

#include /*kernel related*/

#include /*File system related*/

#include /*init*/

#include /*delay*/

#include /*poll*/

#include /*interrupt*/

#include /*linux interrupt*/

#include /*uaccess*/

#include /*Register settings*/

#include /*hardware*/

/*define macro*/

#define BUTTON_MAJOR 221 /*Main device number, originally 232, I changed it to 221*/

#define DEVICE_NAME "buttons_my" /*Device name, originally it was buttons, I added _my*/

/*Define the description structure of button interrupt*/
/*It integrates the information of button interrupt*/
/*What do the various members mean?*/

struct button_irq_desc
{
int irq; /*interrupt number*/
/*interrupt number uniquely represents an interrupt*/

int pin; /*Interrupt control register*/
/*The value of this register is set by the interrupt pin*/
/*We want to read the control information from this register*/

int pin_setting; /*Interrupt pin*/
/*The level of this pin is controlled by the button*/
/*Thus, we finally control the value of the register by the button*/

int number; /*number*/
char *name; /*name*/
};

/*Specify the information of 6 buttons*/

static struct button_irq_desc button_irqs [] =
{
{{IRQ_EINT8,S3C2410_GPG0,S3C2410_GPG0_EINT8,0,"KEY1"}, /*K1*/
{IRQ_EINT11,S3C2410_GPG3,S3C2410_GPG3_EINT11,1,"KEY2"}, /*K2*/
{IRQ_EINT13,S3C2410_GPG5 ,S3C2410_GPG5_EINT13,2,"KEY3"}, /*K3*/
{IRQ_EINT14,S3C2410_GPG6,S3C2410_GPG6_EINT14,3,"KEY4"}, /*K4*/
{IRQ_EINT15,S3C2410_GPG7,S3C2410_GPG7_EINT15,4,"KEY5"}, /*K5*/
{IRQ_EINT19,S3C2410_GPG11,S3C2410_GPG11_EINT19,5,"KEY6"}, /*K6*/
}

/*In this way, resources are organized*/
/*In fact, here we not only organize hardware resources*/
/*We also incorporate certain software resources*/
/*Like interrupt numbers*/


/*key_values ​​array*/
/*Stores the value of each key in the event of an interrupt*/
/*What does volatile mean?*/
/*This array is where we store the results of key operations, so it is very important*/

static volatile int key_values ​​[] = {0,0,0,0,0,0};


/*What does the macro DECLARE_WAIT_QUEUE_HEAD() do?*/
/*This macro should create a waiting queue*/
/*The waiting queue is an important method of process scheduling*/
/*The waiting queue is also very interesting. button_waitq represents the queue for key waiting*/
/*That is to say, once a key is pressed, the process in its waiting queue will be activated to perform corresponding processing*/
/*Therefore, the key waiting queue, or the waiting queue set by the interrupt,*/
/*is a very important resource in interrupt processing, which greatly expands the ability of interrupt processing*/


static DECLARE_WAIT_QUEUE_HEAD(button_waitq); /*What is button_waitq?*/
/*Should be the name of the waiting queue*/


/*A flag indicating whether there is data in the key_values ​​array, 0 means no data is readable, 1 means there is data to read*/

static volatile int ev_press = 0; /*Initial value is 0*/

/*Declaration of interrupt service routine buttons_interrupt()*/
/*When an interrupt is detected, the interrupt service routine will be executed*/
/*So how to detect an interrupt?*/
/*And when an interrupt occurs, how to know what kind of interrupt occurred?*/
/*There are many kinds of interrupts, which interrupt should the interrupt service routine serve?*/
/*Obviously, the interrupt number and the interrupt service routine should be linked to form a whole*/
/*This work can be done in the open function*/

/*Parameter irq---interrupt number*/
/*The interrupt service program should correspond to the interrupt number one by one*/
/*When an interrupt corresponding to a certain interrupt number occurs, the service program corresponding to the interrupt number will be called*/
/*Then, detecting the occurrence of the interrupt becomes a prerequisite*/
/*Parameter dev_id --- which button is it*/

static irqreturn_t buttons_interrupt(int irq,void *dev_id);

/*mini2440_buttons_open() function declaration*/
/*Specific function called by driver function open*/
/*The open function implements hardware initialization*/
/*and software initialization*/
/*Create a good environment for the operation of our keyboard device*/

static int mini2440_buttons_open(struct inode *inode,struct file *file);

/*Declaration of mini2440_buttons_close() function*/
/*Specific function called by release*/
/*Disassembly of device software environment*/
/*Specifically, the release of interrupts*/
/*Because interrupt resources are also valuable system resources, they must be released when not in use*/

static int mini2440_buttons_close(struct inode *inode,struct file *file);

/*Declaration of mini2440_buttons_read() function*/
/*Specific function called
by read*/ /*It reads the result of keyboard input*/
/*Essentially, it reads the value of key_values ​​array*/
/*It completes the core function of keyboard as input device*/
/*Whether the array is readable or not should be judged according to the flag bit ev_press*/
/*If the array is readable, read the data into user buffer*/
/*If the array is not readable, the process enters the waiting queue and waits until the array is readable*/
/*The waiting queue mechanism is commonly used in interrupt management*/
/*Because some processes often need to wait for a certain event to occur*/

static int mini2440_buttons_read(struct file *filp,char __user *buff,size_t count,loff_t *offp);
/*Note that __user refers to the user space*/
/*The input result of the keyboard should be read into the user space*/

/*mini2440_buttons_poll() function declaration*/
/*The specific function called by poll*/
/*poll is essentially the calling function of select*/
/*If there is key data, select will return immediately*/
/*If there is no key data, wait*/
/*Essentially, this is the mechanism for the keyboard to wait for input*/

static unsigned int mini2440_buttons_poll(struct file *file,struct poll_table_struct *wait);


/*file_operations structure*/
/*Driver function settings*/
/*Set the previous driver functions separately*/

static struct file_operations mini2440_buttons_fops =
{
.owner = THIS_MODULE,

.open = mini2440_buttons_open, /*open()*/

.release = mini2440_buttons_close, /*release()*/

.read = mini2440_buttons_read, /*read()*/

.poll = mini2440_buttons_poll /*poll()*/
};

/*mini2440_buttons_init() function declaration*/
/*Specific function called by module_init*/
/*Initialization function when module is created*/
/*Main work is to register and create devices*/
/*Specific hardware initialization work can be omitted*/
/*Leave it to the function in fops*/

static int __init mini2440_buttons_init(void);

/*mini2440_buttons_exit() function declaration*/
/*Cleanup work when module is uninstalled*/
/*Mainly device uninstallation*/

static void __exit mini2440_buttons_exit(void);

/*Entry point when creating the module*/

module_init(mini2440_buttons_init);


/*Entry point when module is unloaded*/

module_exit(mini2440_buttons_exit);

/*Some information about the driver*/

MODULE_AUTHOR("http://www.arm9.net"); /*driver author*/

MODULE_DESCRIPTION("S3C2410/S3C2440 BUTTON Driver"); /*Description information*/

MODULE_LICENSE("GPL"); /*Agreement to be followed*/

/************************************************************************/
/*************************The following is the implementation of the previously declared function***********************/
/************************************************************************/


/************************mini2440_buttons_init()************************/

static int __init mini2440_buttons_init(void)
{
int ret; /*Return value of device registration*/

/*Register device driver*/
/*Device number, device name, and driver function*/

ret = register_chrdev(BUTTON_MAJOR,DEVICE_NAME,&mini2440_buttons_fops);

/*Handling registration failure*/

if(ret < 0)
{
printk(DEVICE_NAME " can't register major number\n");
return ret;
}

/*Create device*/
/*devfs_mk_cdev() function is the kernel mode device creation function*/
/*mknod is the user mode device creation function*/

devfs_mk_cdev(MKDEV(BUTTON_MAJOR,0),S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP,DEVICE_NAME);

printk(DEVICE_NAME " initialized\n");

return 0;
}


/******************mini2440_buttons_exit()****************************** /

static void __exit mini2440_buttons_exit(void)
{
/*Remove device*/

devfs_remove(DEVICE_NAME);

/*Cancel the device driver*/

unregister_chrdev(BUTTON_MAJOR,DEVICE_NAME);
}


/**********************mini2440_buttons_open()****************************** */

static int mini2440_buttons_open(struct inode *inode,struct file *file)
{
int i; /*loop variable, because there are 6 buttons*/

int err; /*Return value of interrupt registration function*/

/*Process each button separately, using a for loop*/
/*Specifically, it is necessary to connect the register and the corresponding pin*/
/*Connect the interrupt number and the corresponding interrupt service routine*/
/*This step is similar to the driver registration mentioned above*/
/*We can successfully call it interrupt registration*/

for(i = 0;i < sizeof(button_irqs)/sizeof(button_irqs[0]);i++)
{
/*Connection between register and interrupt pin*/

   s3c2410_gpio_cfgpin(button_irqs[i].pin,button_irqs[i].pin_setting);

   /*Interrupt registration*/
/*request_irq() function*/
/*Pay attention to its input parameters*/
/*&button_irqs[i] is the resource enjoyed by the interrupt*/
/*Will be passed to buttons_interrupt for processing*/

   err = request_irq(button_irqs[i].irq,buttons_interrupt,NULL,button_irqs[i].name,(void *)&button_irqs[i]);

   /*Setting the interrupt type*/
/*set_irq_type() function*/
/*What kind of interrupt does the IRQT_BOTHEDGE interrupt type represent?*/

   /*There are several very important questions*/
/*After the interrupt is registered and its interrupt type is set, when an interrupt occurs,*/
/*when a button is pressed, can the system automatically detect that an interrupt has occurred?*/
/*After detecting that an interrupt has occurred, can it automatically identify which interrupt number it is?*/
/*If it knows which interrupt number it is, can it automatically call its interrupt service program?*/
/*The answers to these questions are enough to form the core of the interrupt handling mechanism of the Linux system*/

   set_irq_type(button_irqs[i].irq,IRQT_BOTHEDGE);

   /*Handling registration failure*/

   if(err)
break; /*jump out of the loop*/
}

/*If a button interrupt registration fails*/
/*then the previously successfully registered interrupt needs to be dismantled*/

if(err)
{
i--; /*Return to the previous button processing*/

   for(;i >=0; i--) /*Dismantle accordingly*/
{
/*Make the interrupt ineffective*/

    disable_irq(button_irqs[i].irq);

    /*Release interrupt resources*/

    free_irq(button_irqs[i].irq,(void *)&button_irqs[i]);
}

   return -EBUSY; /*The final return value if the interrupt registration is unsuccessful*/
}

return 0; /*Normal return*/
}


/**************************buttons_interrupt()*****************************/
/*This interrupt service routine sets the value of the key_values ​​array every time it is interrupted*/
/*and sets the value of the array readable flag ev_press*/
/*and wakes up the process in the waiting queue*/
/*This is what interrupt processing often does*/
/*Here, the process that often waits in the waiting queue button_waitq is the array reading process*/
/*That is to say, the reading process has been waiting for key input when no data is read*/
/*The reading process is waiting, which does not mean that all processes are waiting. Other processes should do what they should do*/

static irqreturn_t buttons_interrupt(int irq,void *dev_id)
{
/*button_irq_desc structure variable*/
/*Process the incoming resources*/

struct button_irq_desc *button_irqs = (struct button_irq_desc *)dev_id;

/*Get the value of the register*/
/*This step is crucial*/
/*s3c2410_gpio_getpin() function directly gets the value of the register*/

/*Please note that pressing a button will cause two interrupts*/

/*Pressing is an interrupt, releasing is another interrupt*/

int up = s3c2410_gpio_getpin(button_irqs->pin);

/*Through the circuit schematic, we can know that when the button is not pressed, the interrupt pin should be at a high level*/
/*So the value of the register should be 1*/
/*It is also meaningful to take the variable up, indicating that the default state is the pop-up state*/
/*When the button is pressed, the value of the register should be 0*/

/*The following is to process the value of up*/
/*That is, to store the data in the key_values ​​array after a certain transformation*/

if(up) /*If it is in the pop-up state*/
/*Then a large value must be stored in the corresponding position of the key_values ​​array*/
/*At the same time, it is necessary to be able to identify which button it is from the value*/

   key_values[button_irqs->number] = (button_irqs->number + 1) + 0x80;
/*For example, when the K1 key is turned on, key_values[0] is set to (0+1)+0x80, which is 129*/

else /*If the key is closed*/
/*Then store a very small number in the corresponding position of the key_values ​​array*/
/*At the same time, be able to identify which key it is from the value*/

   key_values[button_irqs->number] = (button_irqs->number + 1);
/*For example, if the K1 key is closed, key_values[0] is set to (0+1), which is 1*/

/*Set the array readable flag*/

ev_press = 1; /*Indicates that the array is readable*/

/*Wake up the dormant process?*/
/*The button_waitq queue stores the corresponding processing process*/
/*Such as the process of reading the value of the array*/
/*Pay attention to the usage of wake_up_interruptible() and other functions*/

wake_up_interruptible(&button_waitq);

/*return*/

return IRQ_RETVAL(IRQ_HANDLED); /*?*/
}

/************************mini2440_buttons_close()************************ *****/

static int mini2440_buttons_close(struct inode *inode,struct file *file)
{
int i; /*loop variable, need to operate several buttons*/

/*for loop, release interrupts for each key in turn*/

for(i = 0;i < sizeof(button_irqs)/sizeof(button_irqs[0]);i++)
{
/*Disable interrupt*/

   disable_irq(button_irqs[i].irq);

   /*Release resources*/

   free_irq(button_irqs[i].irq,(void *)&button_irqs[i]);
}

/*return*/

return 0;
}


/************************mini2440_buttons_read()************************ ***/

/*Note that the read function only reads the interrupt value once, not continuously*/
/*To read continuously, you need to make a loop and keep calling the read function, but that is not what the driver should do*/

static int mini2440_buttons_read(struct file *filp,char __user *buff,size_t count,loff_t *offp)
{
unsigned long err; /*return value of copy_to_user() function*/

/*If there is no value in the key_values ​​array, the process will sleep*/
/*Until an interrupt comes, the interrupt service routine will wake up the sleeping process to continue reading the value*/
/*Whether there is a value in the key_values ​​array is determined by the ev_press flag*/
/*If there is a value, it is 1, if there is no value, it is 0*/

/*The process waiting queue mechanism is a method of process scheduling*/

if(!ev_press) /*flag is 0, that is, no data*/
{
if(filp->f_flags & O_NONBLOCK) /*??*/
return -EAGAIN;
else /*Process sleeps and is put into button_waitq waiting queue*/
/*Here, the ev_press flag is set to the flag of the sleeping process?*/
/*This is to facilitate the use of poll_wait function*/
/*That is, it is beneficial to select function*/
wait_event_interruptible(button_waitq,ev_press);
/*In the interrupt handling function, this process will be awakened*/
/*Before awakening, ev_press has been set to 1*/
/*The execution point after awakening starts here*/
}

/*The following is the processing situation when the flag bit is 1, that is, there is data to read*/

/*Then start reading data from user space*/

err = copy_to_user(buff,(const void *)key_values,min(sizeof(key_values),count));
/*Use of copy_to_user() function*/

/* Clear the key_values ​​array */

memset((void *)key_values,0,sizeof(key_values));

/*Mark position 0*/
/*Indicates that it has been read*/

ev_press = 0;

/*Handling of err*/

if(err) /*read error*/
return -EFAULT;
else /*read correctly*/
/*return the number of bytes read*/
return min(sizeof(key_values),count);
}

/************************mini2440_buttons_poll()********************** */

static unsigned int mini2440_buttons_poll(struct file *file,struct poll_table_struct *wait)
{
unsigned int mask = 0; /* */

/*poll_wait() function*/
/*will monitor the processes in the process queue button_waitq*/
/*For example, if the flag ev_press of the process where mini2440_button_read is located is set to 1*/
/*then it will not wait any more*/
/*This is essentially the operating mechanism of the select function*/

poll_wait(file,&button_waitq,wait);

if(ev_press)
mask |= POLLIN | POLLRDNORM; /*??*/

return mask;
}

================================================== ================================
 
Here is the test code:

/*Button test program*/

#include /*Standard input and output header file*/

#include /*standard library*/

#include /*Some macro definitions are here*/

#include /*Device control*/

#include /*defines some types*/

#include /*status*/

#include /*File control*/

#include /*Select?*/

#include /*time functions*/

#include /*Macros related to errors*/

/*Main function entry*/

int main(void)
{
int i; /*loop variable used for keyboard output*/

int buttons_fd; /*buttons device number*/

int key_value[4]; /*Values ​​of four keys*/

/*Open the keyboard device file*/

buttons_fd = open("/dev/buttons",0); /*Open in mode 0*/

/* Enable error handling */

if(buttons_fd < 0) /*If an error occurs during opening, a negative value will be returned*/
{
perror("open device buttons"); /*perror function?*/

   exit(1); /*return 1*/
}

/*for infinite loop, waiting for user input*/
/*This is a typical program execution method*/

for(;;)
{
fd_set rds; /*fd_set is a type defined in types.h, essentially an int type*/
/*rds is used to store the device number*/

   int ret; /*local variable ret defined in for loop*/

   FD_ZERO(&rds); /*rds initialization*/
/*Where is FD_ZERO defined?*/

   FD_SET(buttons_fd,&rds); /*Assign the buttons device number to rds*/
/*Where is FD_SET defined?*/

   /*Use the select system call to check whether data can be read from the /dev/buttons device*/
/*What does the select function do?*/

   ret = select(buttons_fd + 1,&rds,NULL,NULL,NULL);
/*Return value ret*/
/*What is the specific meaning of the return value?*/

   /*Processing of ret*/

   if(ret < 0) /*When ret is less than 0*/
{
perror("select");
exit(1);
}    

   if(ret == 0) /*When ret is equal to 0*/
{
printf("Timeout.\n");
}
else /*Can read data*/
if(FD_ISSET(buttons_fd,&rds)) /*??*/
{
/*Read the data sent by the keyboard driver*/
/*key_value is consistent with the definition in the keyboard driver*/

     int ret = read(buttons_fd,key_value,sizeof(key_value)); /*Note the difference between ret here and the previous ret*/
/*Note the characteristics of keyboard device reading*/

     /*Processing of ret*/
if(ret != sizeof(key_value)) /*Not enough received*/
{
if(errno != EAGAIN) /*???*/
perror("read buttons\n");
continue;
}
else /*If received correctly, print to the standard terminal*/
{
for(i = 0;i < 4;i++) /*The loop variable i defined at the beginning*/
printf("K%d %s, key value = 0x%02x\n",i,(key_value[i] & 0x80) ? "released" : key_value[i] ? "pressed down" : "",key_value[i]);
/*Pay attention to the format of this series of outputs*/
}
}
}

/*Close the device*/

close(buttons_fd);

return 0; /*main function returns*/
}

END! !

Keywords:MINI2440 Reference address:A fairly detailed explanation of the MINI2440 key driver

Previous article:ARM optimization function parameter count
Next article:NAND FLASH ECC verification principle and implementation

Recommended ReadingLatest update time:2024-11-16 22:53

Mini2440 bare metal development: Keil development environment construction
Mini2440 bare metal development: Keil development environment construction I have worked on STM32 for a while, and also worked on uboot and Linux drivers, but I feel that these are not systematic and unsystematic. I feel that what I learned is very miscellaneous, and there is no record, so I decided to stick to blog
[Microcontroller]
Mini2440 bare metal development: Keil development environment construction
Porting madplay to mini2440 under Ubuntu 11.10
Today I spent half a day learning how to port madplay. Thanks to the rich online resources, I was able to find solutions to one problem after another. After returning from shopping, I immediately started porting madplay to mini2440 under Ubuntu 11.10. With the experience of porting madplay in the morning
[Microcontroller]
J-Link + ADS + mini2440 debug interruption issue
In RAM, I can debug programs like ticker and buzzer online. But I cannot debug an interrupt program today. It is a simple external interrupt. I get the external interrupt by pressing a button, but the interrupt never goes in (I can't jump in even if I set a breakpoint and press a button). I looked up some informatio
[Microcontroller]
Mini2440 system transplantation chapter rootfs production
 Make a root file system 1. Create a directory 2. Create necessary device files 3. Install the required shared libraries 4. Install init files, shells and various Linux commands, provided by busybox 5. Create and edit configuration 1.1. Create a directory mkdir rootfs cd rootfs mkdir bin sbin dev lib etc root usr
[Microcontroller]
Transplanting QT on mini2440
QT download: http://download.qt-project.org/ qt-everywhere-opensource-src-4.8.4.tar.gz: http://download.qt-project.org/archive/qt/ tslib download: https://github.com/libts/tslib/releases Compile and install QT-X11-4.5.3 QT-X11-4.5.3 is a software running on the Linux platform for simulating QT applications. In this
[Microcontroller]
Transplanting QT on mini2440
Socket communication of Apache php on mini2440
server.php ?php // Make sure there is no timeout when connecting to the client set_time_limit(0); //Set IP and port number $address='127.0.0.1'; $port=2009; //When debugging, you can change the port to test the program!
[Microcontroller]
Socket communication of Apache php on mini2440
mini2440 driver analysis ADC
1. ADC_DEV structure typedef struct { wait_queue_head_t wait; int channel; int prescale; }ADC_DEV;  wait waits for the queue. The process reads the device. If the data is not converted, it will sleep on this queue. channel conversion channel, s3c2440 has eight channels of ad, but only four channels AIN can be us
[Microcontroller]
Solve the full-duplex problem of mini2440 sound card to achieve simultaneous recording and playback
#include  unistd.h #include  fcntl.h #include  sys/types.h #include  sys/ioctl.h #include  stdlib.h #include  stdio.h #include  linux/soundcard.h #include  pthread.h #define LENGTH 3 #define RATE 8000
[Microcontroller]
Latest Microcontroller Articles
  • Download from the Internet--ARM Getting Started Notes
    A brief introduction: From today on, the ARM notebook of the rookie is open, and it can be regarded as a place to store these notes. Why publish it? Maybe you are interested in it. In fact, the reason for these notes is ...
  • Learn ARM development(22)
    Turning off and on interrupts Interrupts are an efficient dialogue mechanism, but sometimes you don't want to interrupt the program while it is running. For example, when you are printing something, the program suddenly interrupts and another ...
  • Learn ARM development(21)
    First, declare the task pointer, because it will be used later. Task pointer volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • Learn ARM development(20)
    With the previous Tick interrupt, the basic task switching conditions are ready. However, this "easterly" is also difficult to understand. Only through continuous practice can we understand it. ...
  • Learn ARM development(19)
    After many days of hard work, I finally got the interrupt working. But in order to allow RTOS to use timer interrupts, what kind of interrupts can be implemented in S3C44B0? There are two methods in S3C44B0. ...
  • Learn ARM development(14)
  • Learn ARM development(15)
  • Learn ARM development(16)
  • Learn ARM development(17)
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号