zl程序教程

您现在的位置是:首页 >  硬件

当前栏目

sdio接口wifi模块_连接路由器的用哪个接口

WiFi接口模块连接 哪个 路由器 SDIO
2023-06-13 09:14:51 时间

SDIO-WiFi即基于SDIO接口符合WiFi标准的嵌入式模块,内置802.11协议栈以及TCP/IP协议栈,可实现主平台铜鼓SDIO到无线网络之间转换 SDIO:传输数据块,兼容SD,MMC接口等 先以SDIO设备注册,然后检测到再注册WiFi功能,即用SDIO协议发送命令和数据

sdio 基本概念 接口 1.SD的IO接口,透过SD的IO接口连接外设,透过SD卡的IO数据接位与外设传递数据,具备完整的SDIO stack驱动,热门好用 常见SDIO外设:WiFi card/CMOS sensor card /GPS card/GSM,GPRS moderm card/Bluetooth card / SDIO将会取代GPIO式的SPI接口 总线 与USB总线类似,有两端,host端,device端,host端发命令,device端解析命令,然后可以互通 CLK:host给device的时钟信号,一个时钟周期一个命令 CMD:双向信号,用于传输命令和反应 DAT0-DAT3:四条数据线 VDD:电源信号 VSS1-VSS2:电源地信号 热插拔原理 方法:设置定时器检查/插拔中断检测 硬件:例如GPG10(EINT18)用于检测SD卡,GPG10为高电平,即未插入SD卡,GPG10为低电平,即插入了SD卡 命令 总线上host发请求,device回应,SDIO命令由6个字节组成 a–command: 用于开始传输命令, b–response:device返回给host的命令,作为command的回应也是cmd传送 c–data:双向,可以设置1线也可设置4线模式,通过data0–data4线 读命令:host->device device–>host(握手信号),host收到回应,通过数据线传送,包含CRC校验码,读完host发送命令通知device,device返回响应 写命令: host->device 发送命令,device回应握手,host收到握手发送数据,同时包含CRC,写入完毕,发送命令通知device操作完毕,device回应

驱动(分层&&分离思想) 设备驱动层(wifi设备)–>核心层(向上向下提供接口)—>主机驱动层(SDIO驱动)

Linux-5.4.rc8源码 文件路径 /include/linux/mmc/host.h struct mmc_host SD控制器结构定义 struct mmc_card SD卡定义 struct mmc_driver mmc驱动定义 struct sdio_func 功能设备定义 struct mmc_host_ops 操作接口函数定义,从主机控制器向core层注册操作函数,将core层与具体的控制器隔离,即core通过他操作控制器,不直接调用具体主控制器函数

1.host驱动 sdhci-s3c定义的host驱动程序 /driver/mmc/host/sdhci-s3c.c static struct platform_driver sdhci_s3c_driver = { .probe = sdhci_s3c_probe, .remove = sdhci_s3c_remove, .id_table = sdhci_s3c_driver_ids, .driver = { .name = “s3c-sdhci”, .of_match_table = of_match_ptr(sdhci_s3c_dt_match), .pm = &sdhci_s3c_pmops, }, };

static int sdhci_s3c_probe(struct platform_device *pdev) { struct s3c_sdhci_platdata *pdata; struct sdhci_s3c_drv_data *drv_data; struct device *dev = &pdev->dev; struct sdhci_host *host; struct sdhci_s3c *sc; struct resource *res; int ret, irq, ptr, clks;

if (!pdev->dev.platform_data && !pdev->dev.of_node) { dev_err(dev, “no device data specified\n”); return -ENOENT; }

irq = platform_get_irq(pdev, 0); if (irq < 0) return irq;

host = sdhci_alloc_host(dev, sizeof(struct sdhci_s3c)); if (IS_ERR(host)) { dev_err(dev, “sdhci_alloc_host() failed\n”); return PTR_ERR(host); } sc = sdhci_priv(host);

pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) { ret = -ENOMEM; goto err_pdata_io_clk; }

if (pdev->dev.of_node) { ret = sdhci_s3c_parse_dt(&pdev->dev, host, pdata); if (ret) goto err_pdata_io_clk; } else { memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata)); sc->ext_cd_gpio = -1; /* invalid gpio number */ }

drv_data = sdhci_s3c_get_driver_data(pdev);

sc->host = host; sc->pdev = pdev; sc->pdata = pdata; sc->cur_clk = -1;

platform_set_drvdata(pdev, host);

sc->clk_io = devm_clk_get(dev, “hsmmc”); if (IS_ERR(sc->clk_io)) { dev_err(dev, “failed to get io clock\n”); ret = PTR_ERR(sc->clk_io); goto err_pdata_io_clk; }

/* enable the local io clock and keep it running for the moment. */ clk_prepare_enable(sc->clk_io);

for (clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) { char name[14];

snprintf(name, 14, “mmc_busclk.%d”, ptr); sc->clk_bus[ptr] = devm_clk_get(dev, name); if (IS_ERR(sc->clk_bus[ptr])) continue;

clks++; sc->clk_rates[ptr] = clk_get_rate(sc->clk_bus[ptr]);

dev_info(dev, “clock source %d: %s (%ld Hz)\n”, ptr, name, sc->clk_rates[ptr]); }

if (clks == 0) { dev_err(dev, “failed to find any bus clocks\n”); ret = -ENOENT; goto err_no_busclks; }

res = platform_get_resource(pdev, IORESOURCE_MEM, 0); host->ioaddr = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(host->ioaddr)) { ret = PTR_ERR(host->ioaddr); goto err_req_regs; }

/* Ensure we have minimal gpio selected CMD/CLK/Detect */ if (pdata->cfg_gpio) pdata->cfg_gpio(pdev, pdata->max_width);

host->hw_name = “samsung-hsmmc”; host->ops = &sdhci_s3c_ops; host->quirks = 0; host->quirks2 = 0; host->irq = irq;

/* Setup quirks for the controller */ host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC; host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT; if (drv_data) { host->quirks |= drv_data->sdhci_quirks; sc->no_divider = drv_data->no_divider; }

#ifndef CONFIG_MMC_SDHCI_S3C_DMA

/* we currently see overruns on errors, so disable the SDMA * support as well. */ host->quirks |= SDHCI_QUIRK_BROKEN_DMA;

#endif /* CONFIG_MMC_SDHCI_S3C_DMA */

/* It seems we do not get an DATA transfer complete on non-busy * transfers, not sure if this is a problem with this specific * SDHCI block, or a missing configuration that needs to be set. */ host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ;

/* This host supports the Auto CMD12 */ host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;

/* Samsung SoCs need BROKEN_ADMA_ZEROLEN_DESC */ host->quirks |= SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC;

if (pdata->cd_type == S3C_SDHCI_CD_NONE || pdata->cd_type == S3C_SDHCI_CD_PERMANENT) host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;

if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT) host->mmc->caps = MMC_CAP_NONREMOVABLE;

switch (pdata->max_width) { case 8: host->mmc->caps |= MMC_CAP_8_BIT_DATA; /* Fall through */ case 4: host->mmc->caps |= MMC_CAP_4_BIT_DATA; break; }

if (pdata->pm_caps) host->mmc->pm_caps |= pdata->pm_caps;

host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR | SDHCI_QUIRK_32BIT_DMA_SIZE);

/* HSMMC on Samsung SoCs uses SDCLK as timeout clock */ host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK;

/* * If controller does not have internal clock divider, * we can use overriding functions instead of default. */ if (sc->no_divider) { sdhci_s3c_ops.set_clock = sdhci_cmu_set_clock; sdhci_s3c_ops.get_min_clock = sdhci_cmu_get_min_clock; sdhci_s3c_ops.get_max_clock = sdhci_cmu_get_max_clock; }

/* It supports additional host capabilities if needed */ if (pdata->host_caps) host->mmc->caps |= pdata->host_caps;

if (pdata->host_caps2) host->mmc->caps2 |= pdata->host_caps2;

pm_runtime_enable(&pdev->dev); pm_runtime_set_autosuspend_delay(&pdev->dev, 50); pm_runtime_use_autosuspend(&pdev->dev); pm_suspend_ignore_children(&pdev->dev, 1);

ret = mmc_of_parse(host->mmc); if (ret) goto err_req_regs;

ret = sdhci_add_host(host); if (ret) goto err_req_regs;

#ifdef CONFIG_PM if (pdata->cd_type != S3C_SDHCI_CD_INTERNAL) clk_disable_unprepare(sc->clk_io); #endif return 0;

err_req_regs: pm_runtime_disable(&pdev->dev);

err_no_busclks: clk_disable_unprepare(sc->clk_io);

err_pdata_io_clk: sdhci_free_host(host);

return ret; }

host->detected得到 /drivers/mmc/core/host.c INIT_DELAYED_WORK(&host->detect, mmc_rescan); INIT_DELAYED_WORK(&host->sdio_irq_work, sdio_irq_work);

mmc_start_host(host); mmc_register_pm_notifier(host);

/drivers/mmc/core/core.c

static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) { host->f_init = freq;

pr_debug(“%s: %s: trying to init card at %u Hz\n”, mmc_hostname(host), __func__, host->f_init);

mmc_power_up(host, host->ocr_avail);

/* * Some eMMCs (with VCCQ always on) may not be reset after power up, so * do a hardware reset if possible. */ mmc_hw_reset_for_init(host);

/* * sdio_reset sends CMD52 to reset card. Since we do not know * if the card is being re-initialized, just send it. CMD52 * should be ignored by SD/eMMC cards. * Skip it if we already know that we do not support SDIO commands */ if (!(host->caps2 & MMC_CAP2_NO_SDIO)) sdio_reset(host);

mmc_go_idle(host);

if (!(host->caps2 & MMC_CAP2_NO_SD)) mmc_send_if_cond(host, host->ocr_avail);

/* Order’s important: probe SDIO, then SD, then MMC */ if (!(host->caps2 & MMC_CAP2_NO_SDIO)) if (!mmc_attach_sdio(host)) return 0;

if (!(host->caps2 & MMC_CAP2_NO_SD)) if (!mmc_attach_sd(host)) return 0;

if (!(host->caps2 & MMC_CAP2_NO_MMC)) if (!mmc_attach_mmc(host)) return 0;

mmc_power_off(host); return -EIO; }

static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) { host->f_init = freq;

pr_debug(“%s: %s: trying to init card at %u Hz\n”, mmc_hostname(host), __func__, host->f_init);

mmc_power_up(host, host->ocr_avail);

/* * Some eMMCs (with VCCQ always on) may not be reset after power up, so * do a hardware reset if possible. */ mmc_hw_reset_for_init(host);

/* * sdio_reset sends CMD52 to reset card. Since we do not know * if the card is being re-initialized, just send it. CMD52 * should be ignored by SD/eMMC cards. * Skip it if we already know that we do not support SDIO commands */ if (!(host->caps2 & MMC_CAP2_NO_SDIO)) sdio_reset(host);

mmc_go_idle(host);

if (!(host->caps2 & MMC_CAP2_NO_SD)) mmc_send_if_cond(host, host->ocr_avail);

/* Order’s important: probe SDIO, then SD, then MMC */ if (!(host->caps2 & MMC_CAP2_NO_SDIO)) if (!mmc_attach_sdio(host)) return 0;

if (!(host->caps2 & MMC_CAP2_NO_SD)) if (!mmc_attach_sd(host)) return 0;

if (!(host->caps2 & MMC_CAP2_NO_MMC)) if (!mmc_attach_mmc(host)) return 0;

mmc_power_off(host); return -EIO; }

/driver/mmc/core/stdio.c

int mmc_attach_sdio(struct mmc_host *host) { int err, i, funcs; u32 ocr, rocr; struct mmc_card *card;

WARN_ON(!host->claimed);

err = mmc_send_io_op_cond(host, 0, &ocr); if (err) return err;

mmc_attach_bus(host, &mmc_sdio_ops); if (host->ocr_avail_sdio) host->ocr_avail = host->ocr_avail_sdio;

rocr = mmc_select_voltage(host, ocr);

/drivers/mmc/core/mmc.c static int mmc_init_card(struct mmc_host *host, u32 ocr, struct mmc_card *oldcard) { struct mmc_card *card; int err; u32 cid[4]; u32 rocr;

WARN_ON(!host->claimed);

/* Set correct bus mode for MMC before attempting init */ if (!mmc_host_is_spi(host)) mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN);

/* * Since we’re changing the OCR value, we seem to * need to tell some cards to go back to the idle * state. We wait 1ms to give cards time to * respond. * mmc_go_idle is needed for eMMC that are asleep */ mmc_go_idle(host);

/* The extra bit indicates that we support high capacity */ err =

/drivers/mmc/core/stdio.c for (i = 0; i < funcs; i++, card->sdio_funcs++) { err = sdio_init_func(host->card, i + 1); if (err) goto remove;

/* * Enable Runtime PM for this func (if supported) */ if (host->caps & MMC_CAP_POWER_OFF_CARD) pm_runtime_enable(&card->sdio_func[i]->dev); }

2.设备热插拔 SDIO插拔会触发中断通知cpu,执行卡检测中断处理函数在这个中断服务函数中 mmc_detect_change->mmc_schedule_delayed_work(&host->detect,delay) INIT_DELAY_WORK(&host->detect,mmc_rescan)调度mmc_rescan,触发初始化流程,检测到有效SDIO设备,将他注册都系统中

static irqreturn_t msdc_irq(int irq, void *dev_id) { struct msdc_host *host = (struct msdc_host *) dev_id;

while (true) { unsigned long flags; struct mmc_request *mrq; struct mmc_command *cmd; struct mmc_data *data; u32 events, event_mask;

spin_lock_irqsave(&host->lock, flags); events = readl(host->base + MSDC_INT); event_mask = readl(host->base + MSDC_INTEN); if ((events & event_mask) & MSDC_INT_SDIOIRQ) __msdc_enable_sdio_irq(host, 0); /* clear interrupts */ writel(events & event_mask, host->base + MSDC_INT);

mrq = host->mrq; cmd = host->cmd; data = host->data; spin_unlock_irqrestore(&host->lock, flags);

if ((events & event_mask) & MSDC_INT_SDIOIRQ) sdio_signal_irq(host->mmc);

if ((events & event_mask) & MSDC_INT_CDSC) { if (host->internal_cd) mmc_detect_change(host->mmc, msecs_to_jiffies(20)); events &= ~MSDC_INT_CDSC; }

if (!(events & (event_mask & ~MSDC_INT_SDIOIRQ))) break;

if (!mrq) { dev_err(host->dev, “%s: MRQ=NULL; events=%08X; event_mask=%08X\n”, __func__, events, event_mask); WARN_ON(1); break; }

dev_dbg(host->dev, “%s: events=%08X\n”, __func__, events);

if (cmd) msdc_cmd_done(host, events, mrq, cmd); else if (data) msdc_data_xfer_done(host, events, mrq, data); }

return IRQ_HANDLED;

驱动分析(主机端和SDIO设备) 天线接收802.11帧的数据,主机接收802.3帧,固件负责转换 天线收到数据,firmware处理放在buffer中,产生中断,主机收到中断去读取buffer数据

1)注册&&匹配 /include/linux/mmc/stdio_func.h /* * SDIO function device driver */ struct sdio_driver { char *name; const struct sdio_device_id *id_table;

int (*probe)(struct sdio_func *, const struct sdio_device_id *); void (*remove)(struct sdio_func *);

struct device_driver drv; };

具体函数填充(不同WiFi对应的wireless文件夹下的接口文件夹不同) static struct sdio_driver if_sdio_driver = { .name = “libertas_sdio”, .id_table = if_sdio_ids, .probe = if_sdio_probe, .remove = if_sdio_remove, .drv = { .pm = &if_sdio_pm_ops, }, }; 设备注册函数 /driver/mmc/core/sdio_bus.c /** * sdio_register_driver – register a function driver * @drv: SDIO function driver */ int sdio_register_driver(struct sdio_driver *drv) { drv->drv.name = drv->name; drv->drv.bus = &sdio_bus_type; return driver_register(&drv->drv); } EXPORT_SYMBOL_GPL(sdio_register_driver);

总线函数 /include/linux/device.h /** * struct bus_type – The bus type of the device * * @name: The name of the bus. * @dev_name: Used for subsystems to enumerate devices like (“foo%u”, dev->id). * @dev_root: Default device to use as the parent. * @bus_groups: Default attributes of the bus. * @dev_groups: Default attributes of the devices on the bus. * @drv_groups: Default attributes of the device drivers on the bus. * @match: Called, perhaps multiple times, whenever a new device or driver * is added for this bus. It should return a positive value if the * given device can be handled by the given driver and zero * otherwise. It may also return error code if determining that * the driver supports the device is not possible. In case of * -EPROBE_DEFER it will queue the device for deferred probing. * @uevent: Called when a device is added, removed, or a few other things * that generate uevents to add the environment variables. * @probe: Called when a new device or driver add to this bus, and callback * the specific driver’s probe to initial the matched device. * @remove: Called when a device removed from this bus. * @shutdown: Called at shut-down time to quiesce the device. * * @online: Called to put the device back online (after offlining it). * @offline: Called to put the device offline for hot-removal. May fail. * * @suspend: Called when a device on this bus wants to go to sleep mode. * @resume: Called to bring a device on this bus out of sleep mode. * @num_vf: Called to find out how many virtual functions a device on this * bus supports. * @dma_configure: Called to setup DMA configuration on a device on * this bus. * @pm: Power management operations of this bus, callback the specific * device driver’s pm-ops. * @iommu_ops: IOMMU specific operations for this bus, used to attach IOMMU * driver implementations to a bus and allow the driver to do * bus-specific setup * @p: The private data of the driver core, only the driver core can * touch this. * @lock_key: Lock class key for use by the lock validator * @need_parent_lock: When probing or removing a device on this bus, the * device core should lock the device’s parent. * * A bus is a channel between the processor and one or more devices. For the * purposes of the device model, all devices are connected via a bus, even if * it is an internal, virtual, “platform” bus. Buses can plug into each other. * A USB controller is usually a PCI device, for example. The device model * represents the actual connections between buses and the devices they control. * A bus is represented by the bus_type structure. It contains the name, the * default attributes, the bus’ methods, PM operations, and the driver core’s * private data. */ struct bus_type { const char *name; const char *dev_name; struct device *dev_root; const struct attribute_group **bus_groups; const struct attribute_group **dev_groups; const struct attribute_group **drv_groups;

int (*match)(struct device *dev, struct device_driver *drv); int (*uevent)(struct device *dev, struct kobj_uevent_env *env); int (*probe)(struct device *dev); int (*remove)(struct device *dev); void (*shutdown)(struct device *dev);

int (*online)(struct device *dev); int (*offline)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t state); int (*resume)(struct device *dev);

int (*num_vf)(struct device *dev);

int (*dma_configure)(struct device *dev);

const struct dev_pm_ops *pm;

const struct iommu_ops *iommu_ops;

struct subsys_private *p; struct lock_class_key lock_key;

bool need_parent_lock; };

//填充函数 /drivers/mmc/core/sdio_bus.c static struct bus_type sdio_bus_type = { .name = “sdio”, .dev_groups = sdio_dev_groups, .match = sdio_bus_match, .uevent = sdio_bus_uevent, .probe = sdio_bus_probe, .remove = sdio_bus_remove, .pm = &sdio_bus_pm_ops, };

static int sdio_bus_match(struct device *dev, struct device_driver *drv) { struct sdio_func *func = dev_to_sdio_func(dev); struct sdio_driver *sdrv = to_sdio_driver(drv);

if (sdio_match_device(func, sdrv)) return 1;

return 0; }

static const struct sdio_device_id *sdio_match_device(struct sdio_func *func, struct sdio_driver *sdrv) { const struct sdio_device_id *ids;

ids = sdrv->id_table;

if (ids) { while (ids->class || ids->vendor || ids->device) { if (sdio_match_one(func, ids)) return ids; ids++; } }

return NULL; } 2)if_sdio_probe函数 /driver/mmc/core/sdio_bus.c static int sdio_bus_probe(struct device *dev) { struct sdio_driver *drv = to_sdio_driver(dev->driver); struct sdio_func *func = dev_to_sdio_func(dev); const struct sdio_device_id *id; int ret;

id = sdio_match_device(func, drv); if (!id) return -ENODEV;

ret = dev_pm_domain_attach(dev, false); if (ret) return ret;

/* Unbound SDIO functions are always suspended. * During probe, the function is set active and the usage count * is incremented. If the driver supports runtime PM, * it should call pm_runtime_put_noidle() in its probe routine and }

/* Set the default block size so the driver is sure it’s something * sensible. */ sdio_claim_host(func); ret = sdio_set_block_size(func, 0); sdio_release_host(func); if (ret) goto disable_runtimepm;

ret = drv->probe(func, id); if (ret) goto disable_runtimepm;

return 0;

disable_runtimepm: if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD) pm_runtime_put_noidle(dev); dev_pm_domain_detach(dev, false); return ret; }

chunk = sdio_align_size(card->func, size);

ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk); if (ret) goto out;

switch (type) { case MVMS_CMD: ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk – 4); if (ret) goto out; break; case MVMS_DAT: ret = if_sdio_handle_data(card, card->buffer + 4, chunk – 4); if (ret) goto out; break; case MVMS_EVENT: ret = if_sdio_handle_event(card, card->buffer + 4, chunk – 4); if (ret) goto out; break; default: lbs_deb_sdio(“invalid type (%d) from firmware\n”, (int)type); ret = -EINVAL; goto out; } 3)通过中断方式接收数据

int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr, int count) { return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count); } EXPORT_SYMBOL_GPL(sdio_readsb); 4)数据发送 5)移除SDIO卡

static int sdio_bus_remove(struct device *dev) { struct sdio_driver *drv = to_sdio_driver(dev->driver); struct sdio_func *func = dev_to_sdio_func(dev);

/* Make sure card is powered before invoking ->remove() */ if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD) pm_runtime_get_sync(dev);

drv->remove(func);

if (func->irq_handler) { pr_warn(“WARNING: driver %s did not remove its interrupt handler!\n”, drv->name); sdio_claim_host(func); sdio_release_irq(func); sdio_release_host(func); }

/* First, undo the increment made directly above */ if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD) pm_runtime_put_noidle(dev);

/* Then undo the runtime PM settings in sdio_bus_probe() */ if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD) pm_runtime_put_sync(dev);

dev_pm_domain_detach(dev, false);

return 0; } * pm_runtime_get_noresume() in its remove routine. }

/* Set the default block size so the driver is sure it’s something * sensible. */ sdio_claim_host(func); ret = sdio_set_block_size(func, 0); sdio_release_host(func); if (ret) goto disable_runtimepm;

ret = drv->probe(func, id); if (ret) goto disable_runtimepm;

return 0;

disable_runtimepm: if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD) pm_runtime_put_noidle(dev); dev_pm_domain_detach(dev, false); return ret; }

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/186284.html原文链接:https://javaforall.cn