Proceso de registro de ASOC

1. ¿Qué es ASOC?

El controlador de la tarjeta de sonido en el sistema integrado es ASOC (ALSA System on Chip), que es una capa encapsulada en el controlador ALSA. Se divide en 3 partes, Máquina, Plataforma y Códec. La relación entre las tres partes se muestra en la siguiente figura: La máquina se refiere a nuestra placa de desarrollo, la plataforma se refiere a Soc y el códec se refiere a códec (como uda1341)

En segundo lugar, el proceso de registro de la tarjeta de sonido del sistema integrado.

Aquí tomamos la placa de desarrollo mini2440 como ejemplo 

安装新驱动

insmod alsa/driver/myalsa/platform/s3c2440_iis.ko 
insmod alsa/driver/myalsa/platform/s3c2440_dma.ko 
insmod alsa/driver/myalsa/codec/uda1341.ko 
insmod alsa/driver/myalsa/machine/s3c2440_uda1341.ko 

Primero, instalamos el controlador de la tarjeta de sonido que escribimos en el kernel (la parte de la máquina del archivo ko debe colocarse al final, la plataforma y el códec no tienen requisitos especiales, lo siguiente explicará por qué)

1.plataforma parte

1.1 cpu_dai

 Al instalar s3c2440_iis.ko, se registrará el controlador de plataforma s3c24xx_iis_driver. Debido a que hay un dispositivo de plataforma con el mismo nombre que "s3c24xx-iis" en el núcleo, se llama a la función de sonda correspondiente s3c24xx_iis_dev_probe, que eventualmente colocará el hardware s3c24xx_i2s_dai (cpu_dai) Ingrese la lista enlazada dai_list y asígnele el nombre "s3c24xx-iis" (el controlador de la máquina luego crea una instancia de la tarjeta de sonido basada en este nombre)

1. platform:
1.1 s3c24xx-i2s.c : 把s3c24xx_i2s_dai放入链表dai_list, .name = "s3c24xx-iis",
s3c24xx_iis_dev_probe
  snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai);
    list_add(&dai->list, &dai_list);
平台驱动
static struct platform_driver s3c24xx_iis_driver = {
	.probe  = s3c24xx_iis_dev_probe,
	.remove = s3c24xx_iis_dev_remove,
	.driver = {
		.name = "s3c24xx-iis",
		.owner = THIS_MODULE,
	},
};

平台设备
struct platform_device s3c_device_iis = {
	.name		  = "s3c24xx-iis",
	.id		  = -1,
	.num_resources	  = ARRAY_SIZE(s3c_iis_resource),
	.resource	  = s3c_iis_resource,
	.dev              = {
		.dma_mask = &s3c_device_iis_dmamask,
		.coherent_dma_mask = 0xffffffffUL
	}
};
static struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
	.trigger	= s3c24xx_i2s_trigger,
	.hw_params	= s3c24xx_i2s_hw_params,
	.set_fmt	= s3c24xx_i2s_set_fmt,
	.set_clkdiv	= s3c24xx_i2s_set_clkdiv,
	.set_sysclk	= s3c24xx_i2s_set_sysclk,
};

//cpu_dai相关的硬件操作
static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
	.probe = s3c24xx_i2s_probe,
	.suspend = s3c24xx_i2s_suspend,
	.resume = s3c24xx_i2s_resume,
	.playback = {
		.channels_min = 2,
		.channels_max = 2,
		.rates = S3C24XX_I2S_RATES,
		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
	.capture = {
		.channels_min = 2,
		.channels_max = 2,
		.rates = S3C24XX_I2S_RATES,
		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
	.ops = &s3c24xx_i2s_dai_ops,
};

1.2 platform_dma

Cuando instale s3c2440_dma.ko, coloque Samsung_asoc_platform en la lista vinculada platform_list y asígnele el nombre "samsung-audio" (el controlador de Machineko luego crea una instancia de la tarjeta de sonido basada en este nombre)

1.2 sound/soc/samsung/dma.c : 把samsung_asoc_platform放入了链表platform_list, .name = "samsung-audio",
samsung_asoc_platform_probe
  snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
    list_add(&platform->list, &platform_list);
static struct snd_pcm_ops dma_ops = {
	.open		= dma_open,
	.close		= dma_close,
	.ioctl		= snd_pcm_lib_ioctl,
	.hw_params	= dma_hw_params,
	.hw_free	= dma_hw_free,
	.prepare	= dma_prepare,
	.trigger	= dma_trigger,
	.pointer	= dma_pointer,
	.mmap		= dma_mmap,
};
//cpu_dam相关的硬件操作
static struct snd_soc_platform_driver samsung_asoc_platform = {
	.ops		= &dma_ops,
	.pcm_new	= dma_new,
	.pcm_free	= dma_free_dma_buffers,
};

2. Parte de registro de códec

Al instalar uda1341.ko, coloque soc_codec_dev_uda134x y uda134x_dai en las listas enlazadas dai_list y codec_list (utilizadas para luego instanciar la tarjeta de sonido con el controlador Machineko)

2. codec: uda134x.c
uda134x_codec_probe
  snd_soc_register_codec(&pdev->dev,&soc_codec_dev_uda134x, &uda134x_dai, 1);
      struct snd_soc_codec *codec;
      codec->driver = codec_drv; = &soc_codec_dev_uda134x
      
      snd_soc_register_dais(dev, dai_drv, num_dai); // uda134x_dai
        list_add(&dai->list, &dai_list); : 把uda134x_dai放入了链表dai_list
      list_add(&codec->list, &codec_list); 把soc_codec_dev_uda134x放入了链表codec_list
//设置codec芯片的硬件操作
static struct snd_soc_codec_driver soc_codec_dev_uda134x = {
	.probe =        uda134x_soc_probe,
	.remove =       uda134x_soc_remove,
	.suspend =      uda134x_soc_suspend,
	.resume =       uda134x_soc_resume,
	.reg_cache_size = sizeof(uda134x_reg),
	.reg_word_size = sizeof(u8),
	.reg_cache_default = uda134x_reg,
	.reg_cache_step = 1,
	.read = uda134x_read_reg_cache,
	.write = uda134x_write,
	.set_bias_level = uda134x_set_bias_level,
};

 

static struct snd_soc_dai_ops uda134x_dai_ops = {
	.startup	= uda134x_startup,
	.shutdown	= uda134x_shutdown,
	.hw_params	= uda134x_hw_params,
	.digital_mute	= uda134x_mute,
	.set_sysclk	= uda134x_set_dai_sysclk,
	.set_fmt	= uda134x_set_dai_fmt,
};

//codec_dai相关的硬件操作
static struct snd_soc_dai_driver uda134x_dai = {
	.name = "uda134x-hifi",
	/* playback capabilities */
	.playback = {
		.stream_name = "Playback",
		.channels_min = 1,
		.channels_max = 2,
		.rates = UDA134X_RATES,
		.formats = UDA134X_FORMATS,
	},
	/* capture capabilities */
	.capture = {
		.stream_name = "Capture",
		.channels_min = 1,
		.channels_max = 2,
		.rates = UDA134X_RATES,
		.formats = UDA134X_FORMATS,
	},
	/* pcm operations */
	.ops = &uda134x_dai_ops,
};

3. parte de la máquina

La parte de la máquina tiene mucho contenido, y el proceso general de llamadas es el siguiente:

s3c24xx_uda134x_probe
  s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
  platform_set_drvdata(s3c24xx_uda134x_snd_device, &snd_soc_s3c24xx_uda134x);     
  platform_device_add(s3c24xx_uda134x_snd_device);
  
  .....
  soc_probe
    snd_soc_register_card(card);  // card = &snd_soc_s3c24xx_uda134x

      card->rtd = devm_kzalloc(card->dev,...
      card->rtd[i].dai_link = &card->dai_link[i];  // &s3c24xx_uda134x_dai_link
      
      list_add(&card->list, &card_list);
      
      snd_soc_instantiate_cards();  // 实例化声卡
          snd_soc_instantiate_card(card);
            3.1   /* bind DAIs */
                  for (i = 0; i < card->num_links; i++)
                    soc_bind_dai_link(card, i);
                        3.1.1 /* find CPU DAI */
                              rtd->cpu_dai = cpu_dai; = //&s3c24xx_i2s_dai
                        3.1.2 /* find_codec */
                              rtd->codec = codec;  = // codec, codec->driver=&soc_codec_dev_uda134x
                        3.1.3 /* find CODEC DAI */      
                              rtd->codec_dai = codec_dai; // = &uda134x_dai
                        3.1.4 /* find_platform */
                              rtd->platform = platform; // = &samsung_asoc_platform
            3.2 /* initialize the register cache for each available codec */
                ret = snd_soc_init_codec_cache(codec, compress_type);
                
            3.3 snd_card_create
			err = snd_ctl_create(card);
				static struct snd_device_ops ops = {
					.dev_free = snd_ctl_dev_free,
					.dev_register =	snd_ctl_dev_register,
					.dev_disconnect = snd_ctl_dev_disconnect,
				};
				snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
					dev->ops = ops;
            
            3.4 /* early DAI link probe */
                soc_probe_dai_link    
                		/* probe the cpu_dai */
                		/* probe the CODEC */
                		/* probe the platform */
                		/* probe the CODEC DAI */
                		/* create the pcm */
                		ret = soc_new_pcm(rtd, num);
                			struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;
							soc_pcm_ops->open	= soc_pcm_open;
							soc_pcm_ops->close	= soc_pcm_close;
							soc_pcm_ops->hw_params	= soc_pcm_hw_params;
							soc_pcm_ops->hw_free	= soc_pcm_hw_free;
							soc_pcm_ops->prepare	= soc_pcm_prepare;
							soc_pcm_ops->trigger	= soc_pcm_trigger;
							soc_pcm_ops->pointer	= soc_pcm_pointer;
                					
                			snd_pcm_new
								static struct snd_device_ops ops = {
									.dev_free = snd_pcm_dev_free,
									.dev_register =	snd_pcm_dev_register,
									.dev_disconnect = snd_pcm_dev_disconnect,
								};
								err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)
									dev->ops = ops;

								pcm->private_data = rtd;
            3.5 snd_card_register
			snd_device_register_all
				err = dev->ops->dev_register(dev)调用前面snd_pcm_new的snd_pcm_dev_register
                              		snd_pcm_dev_register
                           			err = snd_register_device_for_dev(devtype, pcm->card,
						  	pcm->device,
						  	&snd_pcm_f_ops[cidx],
						  	pcm, str, dev);

Al instalar s3c2440_uda1341.ko, registre el controlador de plataforma s3c24xx_uda134x_driver. Debido a que hay un dispositivo de plataforma mini2440_audio con el mismo nombre en el núcleo, la función s3c24xx_uda134x_probe se llama

//平台驱动
static struct platform_driver s3c24xx_uda134x_driver = {
	.probe  = s3c24xx_uda134x_probe,
	.remove = s3c24xx_uda134x_remove,
	.driver = {
		.name = "s3c24xx_uda134x",
		.owner = THIS_MODULE,
	},
};

//平台设备
static struct platform_device mini2440_audio = {
	.name		= "s3c24xx_uda134x",
	.id		= 0,
	.dev		= {
		.platform_data	= &mini2440_audio_pins,
	},
};

① La función s3c24xx_uda134x_probe llama a platform_set_drvdata para guardar la estructura snd_soc_card snd_soc_s3c24xx_uda134x (incluido dai_link: se usa para conectar Platform
y Codec, que se usará durante la creación de instancias) y guardarlo en pdev (platform_device) -> dev->
p_d_d_d_d_d_d_d_d_d_d_d_d_d_d_d_d_d_d ), Debido a que hay un controlador de plataforma soc_driver con el mismo nombre en el kernel, llame a la función de sonda del controlador (función soc_probe)

    s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
    platform_set_drvdata(s3c24xx_uda134x_snd_device, &snd_soc_s3c24xx_uda134x); 
        dev_set_drvdata(&pdev->dev, data);
            dev->p->driver_data = data;    
    platform_device_add(s3c24xx_uda134x_snd_device);
//指定了platform和codec
static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
	.name = "UDA134X",
	.stream_name = "UDA134X",
	.codec_name = "uda134x-codec",
	.codec_dai_name = "uda134x-hifi",
	.cpu_dai_name = "s3c24xx-iis",
	.ops = &s3c24xx_uda134x_ops,
	.platform_name	= "samsung-audio",
};

static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
	.name = "S3C24XX_UDA134X",
	.dai_link = &s3c24xx_uda134x_dai_link,
	.num_links = 1,        //只有1个dai_link
};
/* ASoC platform driver */
static struct platform_driver soc_driver = {
	.driver		= {
		.name		= "soc-audio",
		.owner		= THIS_MODULE,
		.pm		= &snd_soc_pm_ops,
	},
	.probe		= soc_probe,
	.remove		= soc_remove,
};

Function La función soc_probe llama a la función platform_get_drvdata para extraer la estructura snd_soc_card snd_soc_s3c24xx_uda134x (que contiene dai_link: se usa para conectar Platform y Codec, que se usará durante la creación de instancias) del paso ① , y luego llama a la función snd_soc_register_card para registrarla.

soc_probe
    struct snd_soc_card *card = platform_get_drvdata(pdev);
        return dev->p->driver_data;
    snd_soc_register_card(card);  // card = &snd_soc_s3c24xx_uda134x

③snd_soc_register_card función Quitar estructura snd_soc_card dai_link (estructura snd_soc_s3c24xx_uda134x es sólo una dai_link, donde durante la ejecución sólo una vez), a continuación, llamar a una instancia snd_soc_instantiate_cards sonido , de hecho, especificado por la estructura interior snd_soc_s3c24xx_uda134x nombre dai_link, en dai_list, platform_list, La lista vinculada codec_list encuentra las cuatro estructuras que nuestra plataforma y partes de registro de códec ponen en la cola . (La función soc_bind_dai_link escanea las tres listas enlazadas una por una, y coincide de acuerdo con los nombres en card-> dai_link []. Después de la coincidencia, las instancias de códec, dai y plataforma correspondientes se asignan a (snd_soc_card) card-> rtd [] (snd_soc_pcm_runtime). Después de este proceso, snd_soc_pcm_runtime: (tarjeta-> rtd) información de códec, DAI y plataforma guardada utilizada en esta máquina)

snd_soc_register_card
    card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) *
			    (card->num_links + card->num_aux_devs),
			    GFP_KERNEL);
    for (i = 0; i < card->num_links; i++)
		card->rtd[i].dai_link = &card->dai_link[i];
    list_add(&card->list, &card_list);
    snd_soc_instantiate_cards();
        snd_soc_instantiate_card(card);
            soc_bind_dai_link(card, i);
                /* find CPU DAI */
                rtd->cpu_dai = cpu_dai; = //&s3c24xx_i2s_dai
                /* find_codec */
                rtd->codec = codec;  = // codec, codec->driver=&soc_codec_dev_uda134x
                /* find CODEC DAI */      
                rtd->codec_dai = codec_dai; // = &uda134x_dai
                /* find_platform */
                rtd->platform = platform; // = &samsung_asoc_platform

④Llame a la API del controlador tradicional de la tarjeta de sonido ALSA, snd_card_create, esta función llama a snd_ctl_create, snd_ctl_create llama a la función snd_device_new para llenar la estructura snd_device_ops ops al miembro ops de snd_device (dev-> ops = ops;), y luego cuelga snd___nd_ s_ ) Tarjeta-> lista de dispositivos. El miembro dev_register de la estructura snd_device_ops se usará en el paso ⑧ snd_card_register más adelante. En la actualidad, solo lo colgamos en una lista vinculada , como se muestra a continuación:

            3.3 snd_card_create
			err = snd_ctl_create(card);
				static struct snd_device_ops ops = {
					.dev_free = snd_ctl_dev_free,
					.dev_register =	snd_ctl_dev_register,
					.dev_disconnect = snd_ctl_dev_disconnect,
				};
				snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
					dev->ops = ops;

Function La función soc_probe_dai_link primero llama a la función de sonda como cpu_dai que se encuentra en el paso anterior (por ejemplo: s3c24xx_i2s_dai structure s3c24xx_iis_dev_probe), es decir, inicialización de hardware . Luego llame a soc_new_pcm.

soc_probe_dai_link    
   /* probe the cpu_dai */
    ret = cpu_dai->driver->probe(cpu_dai);
   /* probe the CODEC */
    ret = soc_probe_codec(card, codec);
   /* probe the platform */
    ret = platform->driver->probe(platform);
   /* probe the CODEC DAI */
    ret = codec_dai->driver->probe(codec_dai);
   /* create the pcm */
    ret = soc_new_pcm(rtd, num);

⑥soc_new_pcm primera función para la función tarjeta-> rtd-> ops estructura en la función de asignación de puntero ( procedimiento APP invoca programa volverá invoke estas funciones por la función relacionados con el hardware (por ejemplo, la s3c24xx_i2s_dai plataforma delantera) ), entonces la La dirección de card-> rtd-> ops se asigna a substream-> ops . Finalmente llame a snd_pcm_new.

ret = soc_new_pcm(rtd, num);
    struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;

    soc_pcm_ops->open	= soc_pcm_open;
    soc_pcm_ops->close	= soc_pcm_close;
    soc_pcm_ops->hw_params	= soc_pcm_hw_params;
    soc_pcm_ops->hw_free	= soc_pcm_hw_free;
    soc_pcm_ops->prepare	= soc_pcm_prepare;
    soc_pcm_ops->trigger	= soc_pcm_trigger;
    soc_pcm_ops->pointer	= soc_pcm_pointer;
                					
    snd_pcm_new

    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops);
        substream->ops = ops;
或
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops);
        substream->ops = ops;

Ndsnd_pcm_new es la API de controlador de tarjeta de sonido ALSA tradicional, este paso es similar al paso ④, la diferencia es que el paso ④ corresponde a snd_control, aquí está snd_pcm . Después de ejecutar snd_pcm_new, vuelve a la función soc_new_pcm. Aquí hay un (snd_pcm) pcm-> private_data = rtd; cuando nuestra aplicación llama al programa, sacaremos rtd de pcm-> private_data (rtd tiene cpu_dai, codec_dai correspondiente a nuestra placa de desarrollo) ...)

snd_pcm_new
	static struct snd_device_ops ops = {
		.dev_free = snd_pcm_dev_free,
		.dev_register =	snd_pcm_dev_register,
		.dev_disconnect = snd_pcm_dev_disconnect,
	};
	err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)
			dev->ops = ops;

	pcm->private_data = rtd;

Ndsnd_card_register, esta función llamará a snd_ctl_dev_register y snd_pcm_dev_register en los pasos ④ y ⑦ (registre todos los dispositivos lógicos que cuelgan debajo de la tarjeta de sonido a través de snd_device_register_all (), snd_device_register_all () es en realidad a través de la lista de dispositivos de snd_card 's, y atraviesa todos los dispositivos, y recorre todos los dispositivos Llame a ops-> dev_register () de snd_device para registrar cada dispositivo), estas dos funciones también son la API de la tarjeta de sonido ALSA tradicional, eventualmente llamarán a la función snd_register_device_for_dev, snd_ctl_f_ops, snd_pcm_f_ops a medida que se pasan los parámetros snd_register_device_for_dev, Y se registra en el campo f_ops en snd_minors [minor], y crea un nodo de dispositivo (device_create (class_create está en sound_core.c)). Cuando el programa de usuario necesita abrir los dispositivos de control y pcm, el controlador puede obtener las diversas funciones de devolución de llamada en las estructuras snd_ctl_f_ops y snd_pcm_f_ops a través de la matriz global snd_minors [] y este número de dispositivo (introducido en otro artículo)

3.5 snd_card_register
		snd_device_register_all
			err = dev->ops->dev_register(dev)调用前面snd_ctl_create的snd_ctl_dev_register和snd_pcm_new的snd_pcm_dev_register
				snd_ctl_dev_register
					snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,&snd_ctl_f_ops, card, name)
                                            snd_register_device_for_dev
	                                            preg->type = type;
	                                            preg->card = card ? card->number : -1;
	                                            preg->device = dev;
	                                            preg->f_ops = f_ops;
	                                            preg->private_data = private_data;

	                                            snd_minors[minor] = preg;
	                                            preg->dev = device_create(sound_class, device, MKDEV(major, minor),private_data, "%s", name);

				和snd_pcm_dev_register
				 	err = snd_register_device_for_dev(devtype, pcm->card,
						  	pcm->device,
						  	&snd_pcm_f_ops[cidx],
						  	pcm, str, dev);
static const struct file_operations snd_ctl_f_ops =
{
	.owner =	THIS_MODULE,
	.read =		snd_ctl_read,
	.open =		snd_ctl_open,
	.release =	snd_ctl_release,
	.llseek =	no_llseek,
	.poll =		snd_ctl_poll,
	.unlocked_ioctl =	snd_ctl_ioctl,
	.compat_ioctl =	snd_ctl_ioctl_compat,
	.fasync =	snd_ctl_fasync,
};


const struct file_operations snd_pcm_f_ops[2] = {
	{
		.owner =		THIS_MODULE,
		.write =		snd_pcm_write,
		.aio_write =		snd_pcm_aio_write,
		.open =			snd_pcm_playback_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_playback_poll,
		.unlocked_ioctl =	snd_pcm_playback_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	},
	{
		.owner =		THIS_MODULE,
		.read =			snd_pcm_read,
		.aio_read =		snd_pcm_aio_read,
		.open =			snd_pcm_capture_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_capture_poll,
		.unlocked_ioctl =	snd_pcm_capture_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	}
};

 En este punto, la tarea de la parte de la máquina se ha completado.

 

4. Registro de dispositivo de personaje

Hay una función alsa_sound_init () en sound / core / sound.c. El parámetro major en register_chrdev es el mismo que major cuando se crea el dispositivo pcm es device_create. Este resultado es cuando la aplicación abre el archivo del dispositivo / dev / snd / Cuando pcmCxDxp, ingresará a la función de devolución de llamada abierta de snd_fops. La función abierta se indexa por el número de dispositivo menor, y la estructura snd_minor que se rellenó cuando el dispositivo conrol y pcm se registró originalmente se toma de la matriz global snd_minors. f_ops, y reemplace file-> f_op con f_ops del dispositivo pcm, luego llame directamente f_ops-> open () del dispositivo pcm ...

#define CONFIG_SND_MAJOR	116	/* standard configuration */
static int major = CONFIG_SND_MAJOR;

static const struct file_operations snd_fops =
{
	.owner =	THIS_MODULE,
	.open =		snd_open,
	.llseek =	noop_llseek,
};

alsa_sound_init
    register_chrdev(major, "alsa", &snd_fops)

Introduciremos el proceso de llamada en detalle en el próximo artículo.

 

 

42 artículos originales publicados · Me gusta 10 · Visitantes más de 10,000

Supongo que te gusta

Origin blog.csdn.net/qq_37659294/article/details/104748747
Recomendado
Clasificación