Android SDカードのホットスワップ実現、およびデバッグ

80859 ワード

dtsデバイスツリー.
aliases {
    sdhc1 = &sdhc_1; /* SDC1 eMMC slot */
    sdhc2 = &sdhc_2; /* SDC2 SD card slot */
};
sdhc_2: sdhci@f98a4900 {
    cell-index = <2>;
    compatible = "qcom,sdhci-msm";
    reg = <0xf98a4900 0x11c>, <0xf98a4000 0x800>;
    reg-names = "hc_mem", "core_mem";

    interrupts = <0 125 0>, <0 221 0>;
    interrupt-names = "hc_irq", "pwr_irq";

    qcom,bus-width = <4>;
    qcom,irq_gpios = <42>;
    qcom,cpu-dma-latency-us = <701>;
    status = "disabled";
};
&sdhc_2 {
    vdd-supply = pm8110_l18>;
    qcom,vdd-voltage-level = <2950000 2950000>;
    qcom,vdd-current-level = <15000 400000>;

    vdd-io-supply = pm8110_l21>;
    qcom,vdd-io-voltage-level = <1800000 2950000>;
    qcom,vdd-io-current-level = <200 50000>;

    qcom,pad-pull-on = <0x0 0x3 0x3>; /* no-pull, pull-up, pull-up */
    qcom,pad-pull-off = <0x0 0x3 0x3>; /* no-pull, pull-up, pull-up */
    qcom,pad-drv-on = <0x4 0x4 0x4>; /* 10mA, 10mA, 10mA */
    qcom,pad-drv-off = <0x0 0x0 0x0>; /* 2mA, 2mA, 2mA */

    qcom,clk-rates = <400000 25000000 50000000>;
    qcom,sup-voltages = <2950 2950>;
    #address-cells = <0>;
    interrupt-parent = sdhc_2>;
    interrupts = <0 1 2>;
    #interrupt-cells = <1>;
    interrupt-map-mask = <0xffffffff>;
    interrupt-map = <0 &intc 0 125 0
            1 &intc 0 221 0
            2 &msmgpio 42 0x3>;
    interrupt-names = "hc_irq", "pwr_irq", "status_irq";
    cd-gpios = msmgpio 42 0x0>;

    status = "ok";
};

Makefile
#
# Makefile for the kernel mmc device drivers.
#

subdir-ccflags-$(CONFIG_MMC_DEBUG) := -DDEBUG

obj-$(CONFIG_MMC)      += core/
obj-$(CONFIG_MMC)      += card/
obj-$(subst m,y,$(CONFIG_MMC))  += host/
#core/
obj-$(CONFIG_MMC_BLOCK)        += mmc_block.o
mmc_block-objs          := block.o queue.o
obj-$(CONFIG_MMC_TEST)     += mmc_test.o
obj-$(CONFIG_SDIO_UART)        += sdio_uart.o
obj-$(CONFIG_MMC_BLOCK_TEST)   += mmc_block_test.o
obj-$(CONFIG_MMC)      += mmc_core.o
#card/
mmc_core-y          := core.o bus.o host.o \
                   mmc.o mmc_ops.o sd.o sd_ops.o \
                   sdio.o sdio_ops.o sdio_bus.o \
                   sdio_cis.o sdio_io.o sdio_irq.o \
                   quirks.o cd-gpio.o
mmc_core-$(CONFIG_DEBUG_FS)    += debugfs.o
#host/
obj-$(CONFIG_MMC_MSM_SPS_SUPPORT) += msm_sdcc_dml.o
obj-$(CONFIG_MMC_MSM)  += msm_sdcc.o
obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o
obj-$(CONFIG_MMC_SDHCI)        += sdhci.o
obj-$(CONFIG_MMC_SDHCI_PLTFM)  += sdhci-pltfm.o

Kconfig
#
# MMC subsystem configuration
#

menuconfig MMC
    tristate "MMC/SD/SDIO card support"
    depends on HAS_IOMEM
    help
      This selects MultiMediaCard, Secure Digital and Secure
      Digital I/O support.

      If you want MMC/SD/SDIO support, you should say Y here and
      also to your specific host controller driver.

config MMC_DEBUG
    bool "MMC debugging"
    depends on MMC != n
    help
      This is an option for use by developers; most people should
      say N here.  This enables MMC core and driver debugging.

config MMC_PERF_PROFILING
    bool "MMC performance profiling"
    depends on MMC != n
    default n
    help
      If you say Y here, support will be added for collecting
      performance numbers at the MMC Queue and Host layers.

if MMC

source "drivers/mmc/core/Kconfig"

source "drivers/mmc/card/Kconfig"

source "drivers/mmc/host/Kconfig"

endif # MMC

まず、SDカードはブロックデバイスであり、カーネルのメカニズムに従って、ブロックデバイスはバスにマウントされるべきであることを認識します.だからMMCとSDバスもありました.バスがある以上、バスを登録しなければなりません.主な内容
subsys_initcall(mmc_init);
module_exit(mmc_exit);

厚かましい初期化はmoduleより先にinit()の.優先度の問題.初期化の手順を見てみましょう.
static int __init mmc_init(void)
{
    int ret;

    workqueue = alloc_ordered_workqueue("kmmcd", 0);
    if (!workqueue)
        return -ENOMEM;

    ret = mmc_register_bus();
    if (ret)
        goto destroy_workqueue;

    ret = mmc_register_host_class();
    if (ret)
        goto unregister_bus;

    ret = sdio_register_bus();
    if (ret)
        goto unregister_host_class;

    return 0;

unregister_host_class:
    mmc_unregister_host_class();
unregister_bus:
    mmc_unregister_bus();
destroy_workqueue:
    destroy_workqueue(workqueue);

    return ret;
}

workqueue = alloc_ordered_workqueue(“kmmcd”, 0);ワークキューの実装は、対応するワークを実行するために個別のスレッドを作成することである.しかし、最新のカーネル実装はそうではなく、元のワークキューのインタフェースが廃棄されそうになりました.New API:alloc_workqueue(name, flags, max_active) alloc_ordered_workqueue(const char *name, unsigned int flags) { return alloc_workqueue(name, WQ_UNBOUND | flags, 1); } 1つのワークキューにジョブをコミットすると、指定したスレッドで実行されるわけではありません.システムはWorker Poolを維持し、各Workerは別のスレッドに走り、各CPUにはWorker Poolがあります.ワークが処理する必要がある場合、ワークを起動することで、システムのリソースの占有量を減らすことができる(従来の実装では、ワークキューを作成するたびに、システムはスレッドを作成するが、各スレッドにはtask_struct、pidなどのリソースが必要であるため、システムにワークキューが多くなると、リソースの占有率が高くなる).カーネル空間はすべてのプロセスが共有するアドレス空間であるため、異なるプロセスがワークキューにコミットする作業では、ユーザーは私のこの作業がどのプロセスで処理されているのか気にする必要はありませんが、そうすると、2つの作業が同期する必要がある場合(例えば共有リソースにアクセスする場合)、よく考えなければなりません.2つのジョブが同じワークキューにコミットされると、同時に実行される可能性があり(それぞれ異なるCPU上を走る)、RACEが発生し、この問題を解決するためにWQ_が導入されるUNBOUNDフラグとmax_acitve=1で、この2つのパラメータは、このワークキューに1つのジョブをコミットした場合、このジョブが特定のCPUにバインドされないことを示している(WQ_UNBOUNDフラグが指定されていない場合、どのCPUでコミットされたジョブがそのCPUで必ず実行されるか)、max_Activeは、このワークキューのバックグラウンドにどのくらいのworkerスレッドがバインドされているかを示し、デフォルトパラメータは0で、バックグラウンドスレッドの数を指定します.
static struct bus_type mmc_bus_type = {
    .name       = "mmc",
    .dev_attrs  = mmc_dev_attrs,
    .match      = mmc_bus_match,
    .uevent     = mmc_bus_uevent,
    .probe      = mmc_bus_probe,
    .remove     = mmc_bus_remove,
    .shutdown        = mmc_bus_shutdown,
    .pm     = &mmc_bus_pm_ops,
};
int mmc_register_bus(void)
{
    return bus_register(&mmc_bus_type);
}

ここではバスの登録、kobjectについて説明します.kset,ktype.この3つの構造は結合して設備モデル全体の基石を構成した.それで...device.device_driver.kobjectに基づいている.kset.ktype上のアーキテクチャちょっと説明します:bus_register()は、busというアーキテクチャを含むデータ構造(bus_type_private)であるシェルを作成し、このデータ構造がbus_を先に担持する必要がある.typeのデータ構造、bus_type_private->bus = bus_type;bus_type->p=bus_type_private. retval = kobject_set_name(&priv->subsys.kobj, “%s”, bus->name); kobject_set_nameの能力は/sys/bus/下にmmcのシェル(よし!フォルダ)を作成し、それから完全なデータ構造展示/sys/bus/mmc/devices:デバイスのファイル化drivers:駆動のファイル化drivers_Autoprobe:自動化されているかどうか、読み書きがサポートされています.(cat,echo) drivers_probe:駆動マッチングuevent:sysfsの多くのkobjectの下にuevent属性があります.主にカーネルとudev(自動デバイス発見プログラム)の間の通信インタフェースに使用されています.次を見てみましょう.
static struct class mmc_host_class = {
    .name       = "mmc_host",
    .dev_release    = mmc_host_classdev_release,
    .pm     = &mmc_host_pm_ops,
};

int mmc_register_host_class(void)
{
    return class_register(&mmc_host_class);
}
ret = mmc_register_host_class();

ここで登録したのはhostというクラスです(クラスは、デバイスがどのように接続され、動作しているかにかかわらず、デバイスが提供する機能をユーザ空間で使用できるようにする最下位の実装の詳細を抽象化したデバイスの上位ビューです.クラスメンバーは、ドライバの明確なサポートを必要とせずに上位コードによって制御されます.ただし、ドライバがクラスを直接処理する必要がある場合もあります.クラスが存在する本当の目的は、クラスメンバーの各デバイスはコンテナを提供します)関連するパスの下に/sys/class/mmc_があります.host/
ret = sdio_register_bus();
static struct bus_type sdio_bus_type = {
    .name       = "sdio",
    .dev_attrs  = sdio_dev_attrs,
    .match      = sdio_bus_match,
    .uevent     = sdio_bus_uevent,
    .probe      = sdio_bus_probe,
    .remove     = sdio_bus_remove,
    .pm     = SDIO_PM_OPS_PTR,
};

int sdio_register_bus(void)
{
    return bus_register(&sdio_bus_type);
}

実はSDIOの感覚はSDカードのIOの使用、つまりプロトコルの問題で、1.CLK信号:HOSTがDEVICEに与えるクロック信号.2.CMD信号:命令と反応を伝達するための双方向の信号.3.DAT 0-DAT 3信号:4本の伝送用データ線.4.VDD信号:電源信号.5.VSS 1,VSS 2:電源の信号.データの交流を実現する.SDカードだけではありません.以上はバスとhostを構築するだけです.本物のデバイスは、compatible=「qcom,sdhci-msm」です.デバイスが駆動するprobeはcompatible=「qcom,sdhci-msm」を通過しなければならない.に表示されます.
static const struct of_device_id sdhci_msm_dt_match[] = {
    {.compatible = "qcom,sdhci-msm"},
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match);

static struct platform_driver sdhci_msm_driver = {
    .probe      = sdhci_msm_probe,
    .remove     = __devexit_p(sdhci_msm_remove),
    .driver     = {
        .name   = "sdhci_msm",
        .owner  = THIS_MODULE,
        .of_match_table = sdhci_msm_dt_match,
        .pm = SDHCI_MSM_PMOPS,
    },
};

module_platform_driver(sdhci_msm_driver);

説明するmodule_platform_driverマクロ
include/linux/platform_device.h
 #define module_platform_driver(__platform_driver) \  
    module_driver(__platform_driver, platform_driver_register, \  
            platform_driver_unregister)  
include/linux/device.h        
 #define module_driver(__driver, __register, __unregister, ...) \  
static int __init __driver##_init(void) \  
{ \  
    return __register(&(__driver) , ##__VA_ARGS__); \  
} \  
module_init(__driver##_init); \  
static void __exit __driver##_exit(void) \  
{ \  
    __unregister(&(__driver) , ##__VA_ARGS__); \  
} \  
module_exit(__driver##_exit);  static int __init xxx_init(void)
{
        return platform_driver_register(&xxx);
}
module_init(xxx_init);
static void __exit xxx_init(void)
{
        return platform_driver_unregister(&xxx);
}
module_exit(xxx_exit);

いわば「一労永逸」だ.
デバイスツリーに対応するデバイスが追加されるとprobe,sdhci_msm_probe:
static int __devinit sdhci_msm_probe(struct platform_device *pdev)
{
    struct sdhci_host *host;
    struct sdhci_pltfm_host *pltfm_host;
    struct sdhci_msm_host *msm_host;
    struct resource *core_memres = NULL;
    int ret = 0, dead = 0;
    u32 vdd_max_current;
    u16 host_version;
    u32 pwr, irq_status, irq_ctl;
    unsigned long flags;

    pr_debug("%s: Enter %s
"
, dev_name(&pdev->dev), __func__); msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host), GFP_KERNEL); if (!msm_host) { ret = -ENOMEM; goto out; } msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops; host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata); if (IS_ERR(host)) { ret = PTR_ERR(host); goto out; } pltfm_host = sdhci_priv(host); pltfm_host->priv = msm_host; msm_host->mmc = host->mmc; msm_host->pdev = pdev; /* Extract platform data */ if (pdev->dev.of_node) { ret = of_alias_get_id(pdev->dev.of_node, "sdhc"); if (ret < 0) { dev_err(&pdev->dev, "Failed to get slot index %d
"
, ret); goto pltfm_free; } if (disable_slots & (1 << (ret - 1))) { dev_info(&pdev->dev, "%s: Slot %d disabled
"
, __func__, ret); ret = -ENODEV; goto pltfm_free; } msm_host->pdata = sdhci_msm_populate_pdata(&pdev->dev); if (!msm_host->pdata) { dev_err(&pdev->dev, "DT parsing error
"
); goto pltfm_free; } } else { dev_err(&pdev->dev, "No device tree node
"
); goto pltfm_free; } /* Setup Clocks */ /* Setup SDCC bus voter clock. */ msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus_clk"); if (!IS_ERR_OR_NULL(msm_host->bus_clk)) { /* Vote for max. clk rate for max. performance */ ret = clk_set_rate(msm_host->bus_clk, INT_MAX); if (ret) goto pltfm_free; ret = clk_prepare_enable(msm_host->bus_clk); if (ret) goto pltfm_free; } /* Setup main peripheral bus clock */ msm_host->pclk = devm_clk_get(&pdev->dev, "iface_clk"); if (!IS_ERR(msm_host->pclk)) { ret = clk_prepare_enable(msm_host->pclk); if (ret) goto bus_clk_disable; } atomic_set(&msm_host->controller_clock, 1); /* Setup SDC MMC clock */ msm_host->clk = devm_clk_get(&pdev->dev, "core_clk"); if (IS_ERR(msm_host->clk)) { ret = PTR_ERR(msm_host->clk); goto pclk_disable; } /* Set to the minimum supported clock frequency */ ret = clk_set_rate(msm_host->clk, sdhci_msm_get_min_clock(host)); if (ret) { dev_err(&pdev->dev, "MClk rate set failed (%d)
"
, ret); goto pclk_disable; } ret = clk_prepare_enable(msm_host->clk); if (ret) goto pclk_disable; msm_host->clk_rate = sdhci_msm_get_min_clock(host); atomic_set(&msm_host->clks_on, 1); /* Setup CDC calibration fixed feedback clock */ msm_host->ff_clk = devm_clk_get(&pdev->dev, "cal_clk"); if (!IS_ERR(msm_host->ff_clk)) { ret = clk_prepare_enable(msm_host->ff_clk); if (ret) goto clk_disable; } /* Setup CDC calibration sleep clock */ msm_host->sleep_clk = devm_clk_get(&pdev->dev, "sleep_clk"); if (!IS_ERR(msm_host->sleep_clk)) { ret = clk_prepare_enable(msm_host->sleep_clk); if (ret) goto ff_clk_disable; } msm_host->saved_tuning_phase = INVALID_TUNING_PHASE; ret = sdhci_msm_bus_register(msm_host, pdev); if (ret) goto sleep_clk_disable; if (msm_host->msm_bus_vote.client_handle) INIT_DELAYED_WORK(&msm_host->msm_bus_vote.vote_work, sdhci_msm_bus_work); sdhci_msm_bus_voting(host, 1); /* Setup regulators */ ret = sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, true); if (ret) { dev_err(&pdev->dev, "Regulator setup failed (%d)
"
, ret); goto bus_unregister; } /* Reset the core and Enable SDHC mode */ core_memres = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core_mem"); msm_host->core_mem = devm_ioremap(&pdev->dev, core_memres->start, resource_size(core_memres)); if (!msm_host->core_mem) { dev_err(&pdev->dev, "Failed to remap registers
"
); ret = -ENOMEM; goto vreg_deinit; } /* Unset HC_MODE_EN bit in HC_MODE register */ writel_relaxed(0, (msm_host->core_mem + CORE_HC_MODE)); /* Set SW_RST bit in POWER register (Offset 0x0) */ writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_POWER) | CORE_SW_RST, msm_host->core_mem + CORE_POWER); /* * SW reset can take upto 10HCLK + 15MCLK cycles. * Calculating based on min clk rates (hclk = 27MHz, * mclk = 400KHz) it comes to ~40us. Let's poll for * max. 1ms for reset completion. */ ret = readl_poll_timeout(msm_host->core_mem + CORE_POWER, pwr, !(pwr & CORE_SW_RST), 10, 1000); if (ret) { dev_err(&pdev->dev, "reset failed (%d)
"
, ret); goto vreg_deinit; } /* Set HC_MODE_EN bit in HC_MODE register */ writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE)); /* Set FF_CLK_SW_RST_DIS bit in HC_MODE register */ writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_HC_MODE) | FF_CLK_SW_RST_DIS, msm_host->core_mem + CORE_HC_MODE); /* * CORE_SW_RST above may trigger power irq if previous status of PWRCTL * was either BUS_ON or IO_HIGH_V. So before we enable the power irq * interrupt in GIC (by registering the interrupt handler), we need to * ensure that any pending power irq interrupt status is acknowledged * otherwise power irq interrupt handler would be fired prematurely. */ irq_status = readl_relaxed(msm_host->core_mem + CORE_PWRCTL_STATUS); writel_relaxed(irq_status, (msm_host->core_mem + CORE_PWRCTL_CLEAR)); irq_ctl = readl_relaxed(msm_host->core_mem + CORE_PWRCTL_CTL); if (irq_status & (CORE_PWRCTL_BUS_ON | CORE_PWRCTL_BUS_OFF)) irq_ctl |= CORE_PWRCTL_BUS_SUCCESS; if (irq_status & (CORE_PWRCTL_IO_HIGH | CORE_PWRCTL_IO_LOW)) irq_ctl |= CORE_PWRCTL_IO_SUCCESS; writel_relaxed(irq_ctl, (msm_host->core_mem + CORE_PWRCTL_CTL)); /* * Ensure that above writes are propogated before interrupt enablement * in GIC. */ mb(); /* * Following are the deviations from SDHC spec v3.0 - * 1. Card detection is handled using separate GPIO. * 2. Bus power control is handled by interacting with PMIC. */ host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE; host->quirks |= SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN; host->quirks2 |= SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK; host->quirks2 |= SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE; host->quirks2 |= SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD; host->quirks2 |= SDHCI_QUIRK2_BROKEN_PRESET_VALUE; host->quirks2 |= SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT; if (host->quirks2 & SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK) host->quirks2 |= SDHCI_QUIRK2_DIVIDE_TOUT_BY_4; host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x
"
, host_version, ((host_version & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT)); if (((host_version & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT) == SDHCI_VER_100) { /* * Add 40us delay in interrupt handler when * operating at initialization frequency(400KHz). */ host->quirks2 |= SDHCI_QUIRK2_SLOW_INT_CLR; /* * Set Software Reset for DAT line in Software * Reset Register (Bit 2). */ host->quirks2 |= SDHCI_QUIRK2_RDWR_TX_ACTIVE_EOT; } host->quirks2 |= SDHCI_QUIRK2_IGN_DATA_END_BIT_ERROR; /* Setup PWRCTL irq */ msm_host->pwr_irq = platform_get_irq_byname(pdev, "pwr_irq"); if (msm_host->pwr_irq < 0) { dev_err(&pdev->dev, "Failed to get pwr_irq by name (%d)
"
, msm_host->pwr_irq); goto vreg_deinit; } ret = devm_request_threaded_irq(&pdev->dev, msm_host->pwr_irq, NULL, sdhci_msm_pwr_irq, IRQF_ONESHOT, dev_name(&pdev->dev), host); if (ret) { dev_err(&pdev->dev, "Request threaded irq(%d) failed (%d)
"
, msm_host->pwr_irq, ret); goto vreg_deinit; } /* Enable pwr irq interrupts */ writel_relaxed(INT_MASK, (msm_host->core_mem + CORE_PWRCTL_MASK)); /* Set clock gating delay to be used when CONFIG_MMC_CLKGATE is set */ msm_host->mmc->clkgate_delay = SDHCI_MSM_MMC_CLK_GATE_DELAY; /* Set host capabilities */ msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width; msm_host->mmc->caps |= msm_host->pdata->caps; vdd_max_current = sdhci_msm_get_vreg_vdd_max_current(msm_host); if (vdd_max_current >= 800) msm_host->mmc->caps |= MMC_CAP_MAX_CURRENT_800; else if (vdd_max_current >= 600) msm_host->mmc->caps |= MMC_CAP_MAX_CURRENT_600; else if (vdd_max_current >= 400) msm_host->mmc->caps |= MMC_CAP_MAX_CURRENT_400; else msm_host->mmc->caps |= MMC_CAP_MAX_CURRENT_200; if (vdd_max_current > 150) msm_host->mmc->caps |= MMC_CAP_SET_XPC_180 | MMC_CAP_SET_XPC_300| MMC_CAP_SET_XPC_330; msm_host->mmc->caps2 |= msm_host->pdata->caps2; msm_host->mmc->caps2 |= MMC_CAP2_CORE_RUNTIME_PM; msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR; msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR_CONTROL; msm_host->mmc->caps2 |= (MMC_CAP2_BOOTPART_NOACC | MMC_CAP2_DETECT_ON_ERR); msm_host->mmc->caps2 |= MMC_CAP2_SANITIZE; msm_host->mmc->caps2 |= MMC_CAP2_CACHE_CTRL; msm_host->mmc->caps2 |= MMC_CAP2_POWEROFF_NOTIFY; msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; msm_host->mmc->caps2 |= MMC_CAP2_STOP_REQUEST; msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE; msm_host->mmc->caps2 |= MMC_CAP2_CORE_PM; msm_host->mmc->pm_caps |= MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ; if (msm_host->pdata->nonremovable) msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE; host->cpu_dma_latency_us = msm_host->pdata->cpu_dma_latency_us; init_completion(&msm_host->pwr_irq_completion); if (gpio_is_valid(msm_host->pdata->status_gpio)) { ret = mmc_cd_gpio_request(msm_host->mmc, msm_host->pdata->status_gpio); if (ret) { dev_err(&pdev->dev, "%s: Failed to request card detection IRQ %d
"
, __func__, ret); goto vreg_deinit; } } if (dma_supported(mmc_dev(host->mmc), DMA_BIT_MASK(32))) { host->dma_mask = DMA_BIT_MASK(32); mmc_dev(host->mmc)->dma_mask = &host->dma_mask; } else { dev_err(&pdev->dev, "%s: Failed to set dma mask
"
, __func__); } msm_host->pdata->sdiowakeup_irq = platform_get_irq_byname(pdev, "sdiowakeup_irq"); if (msm_host->pdata->sdiowakeup_irq >= 0) { msm_host->is_sdiowakeup_enabled = true; ret = request_irq(msm_host->pdata->sdiowakeup_irq, sdhci_msm_sdiowakeup_irq, IRQF_SHARED | IRQF_TRIGGER_LOW, "sdhci-msm sdiowakeup", host); if (ret) { dev_err(&pdev->dev, "%s: request sdiowakeup IRQ %d: failed: %d
"
, __func__, msm_host->pdata->sdiowakeup_irq, ret); msm_host->pdata->sdiowakeup_irq = -1; msm_host->is_sdiowakeup_enabled = false; goto free_cd_gpio; } else { spin_lock_irqsave(&host->lock, flags); sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false); spin_unlock_irqrestore(&host->lock, flags); } } ret = sdhci_add_host(host); if (ret) { dev_err(&pdev->dev, "Add host failed (%d)
"
, ret); goto free_cd_gpio; } msm_host->msm_bus_vote.max_bus_bw.show = show_sdhci_max_bus_bw; msm_host->msm_bus_vote.max_bus_bw.store = store_sdhci_max_bus_bw; sysfs_attr_init(&msm_host->msm_bus_vote.max_bus_bw.attr); msm_host->msm_bus_vote.max_bus_bw.attr.name = "max_bus_bw"; msm_host->msm_bus_vote.max_bus_bw.attr.mode = S_IRUGO | S_IWUSR; ret = device_create_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw); if (ret) goto remove_host; if (!gpio_is_valid(msm_host->pdata->status_gpio)) { msm_host->polling.show = show_polling; msm_host->polling.store = store_polling; sysfs_attr_init(&msm_host->polling.attr); msm_host->polling.attr.name = "polling"; msm_host->polling.attr.mode = S_IRUGO | S_IWUSR; ret = device_create_file(&pdev->dev, &msm_host->polling); if (ret) goto remove_max_bus_bw_file; } ret = pm_runtime_set_active(&pdev->dev); if (ret) pr_err("%s: %s: pm_runtime_set_active failed: err: %d
"
, mmc_hostname(host->mmc), __func__, ret); else if (mmc_use_core_runtime_pm(host->mmc)) pm_runtime_enable(&pdev->dev); if (msm_host->pdata->mpm_sdiowakeup_int != -1) { ret = sdhci_msm_cfg_mpm_pin_wakeup(host, SDC_DAT1_ENABLE); if (ret) { pr_err("%s: enabling wakeup: failed: ret: %d
"
, mmc_hostname(host->mmc), ret); ret = 0; msm_host->pdata->mpm_sdiowakeup_int = -1; } } device_enable_async_suspend(&pdev->dev); /* Successful initialization */ goto out; remove_max_bus_bw_file: device_remove_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw); remove_host: dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); sdhci_remove_host(host, dead); free_cd_gpio: if (gpio_is_valid(msm_host->pdata->status_gpio)) mmc_cd_gpio_free(msm_host->mmc); if (sdhci_is_valid_gpio_wakeup_int(msm_host)) free_irq(msm_host->pdata->sdiowakeup_irq, host); vreg_deinit: sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false); bus_unregister: if (msm_host->msm_bus_vote.client_handle) sdhci_msm_bus_cancel_work_and_set_vote(host, 0); sdhci_msm_bus_unregister(msm_host); sleep_clk_disable: if (!IS_ERR(msm_host->sleep_clk)) clk_disable_unprepare(msm_host->sleep_clk); ff_clk_disable: if (!IS_ERR(msm_host->ff_clk)) clk_disable_unprepare(msm_host->ff_clk); clk_disable: if (!IS_ERR(msm_host->clk)) clk_disable_unprepare(msm_host->clk); pclk_disable: if (!IS_ERR(msm_host->pclk)) clk_disable_unprepare(msm_host->pclk); bus_clk_disable: if (!IS_ERR_OR_NULL(msm_host->bus_clk)) clk_disable_unprepare(msm_host->bus_clk); pltfm_free: sdhci_pltfm_free(pdev); out: pr_debug("%s: Exit %s
"
, dev_name(&pdev->dev), __func__); return ret; }

ステップ1:msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host) sdhci_msm_hostデータ構造のインスタンス化.ステップ2:msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops; 主に関連するsdioメインコントローラのパラメータ:mmc駆動によって実現
static struct sdhci_ops sdhci_msm_ops = {
    .set_uhs_signaling = sdhci_msm_set_uhs_signaling,//       
    .check_power_status = sdhci_msm_check_power_status,//      
    .execute_tuning = sdhci_msm_execute_tuning,//  
    .toggle_cdr = sdhci_msm_toggle_cdr,
    .get_max_segments = sdhci_msm_max_segs,
    .set_clock = sdhci_msm_set_clock,//    
    .get_min_clock = sdhci_msm_get_min_clock,//      
    .get_max_clock = sdhci_msm_get_max_clock,//      
    .disable_data_xfer = sdhci_msm_disable_data_xfer,
    .enable_controller_clock = sdhci_msm_enable_controller_clock,
};

ステップ3:sdhciを作成するhost構造体変数を初期化し、付与host=sdhci_を初期化します.pltfm_init(pdev, &msm_host->sdhci_msm_pdata); デバイスツリーのデータを取得するmsm_host->pdata = sdhci_msm_populate_pdata(&pdev->dev); 例えばof_property_read_u32(np, “qcom,irq_gpios”, &irq_gpio); of_node :ret = of_alias_get_id(pdev->dev.of_node, “sdhc”); np : struct device_node *np = dev->of_node; ステップ4:Setup Clocks:Setup SDCC bus voter clock Setup main peripheral bus clock Setup SDC MMC clock Set to the minimum supported clock frequency Setup CDC calibration fixed feedback clock Setup CDC calibration sleep clock
ステップ5:ret=sdhci_msm_bus_register(msm_host, pdev); プラットフォームデバイスをhost/*Reset the core and Enable SDHC mode*/core_に追加memres = platform_get_resource_byname(pdev, IORESOURCE_MEM, “core_mem”); msm_host->core_mem = devm_ioremap(&pdev->dev, core_memres->start, resource_size(core_memres));
ret = devm_request_threaded_irq(&pdev->dev, msm_host->pwr_irq, NULL,
                sdhci_msm_pwr_irq, IRQF_ONESHOT,
                dev_name(&pdev->dev), host);
7int devm_request_threaded_irq(struct device *dev, unsigned int irq,
48                irq_handler_t handler, irq_handler_t thread_fn,
49                unsigned long irqflags, const char *devname,
50                void *dev_id)
51{
52  struct irq_devres *dr;
53  int rc;
54
55  dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
56            GFP_KERNEL);
57  if (!dr)
58      return -ENOMEM;
59
60  rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
61                dev_id);
62  if (rc) {
63      devres_free(dr);
64      return rc;
65  }
66
67  dr->irq = irq;
68  dr->dev_id = dev_id;
69  devres_add(dev, dr);
70
71  return 0;

くどくど言う:linuxでは、割り込み処理はトップハーフ(top half)、ボトムハーフ(bottom half)に分けられ、トップハーフで優先度の高いことを処理し、割り込み時間をできるだけ短くすることを要求し、処理が完了した後、ボトムハーフをアクティブにし、ボトムハーフで残りのタスクを処理する.後半の処理方法は主にソフトがあります.IRq,tasklet,workqueueの3種類は,それぞれ使用方法と適用状況が異なる.soft_IRqは、底半実行時間の要求が緊急または非常に重要な場合に使用され、主にsubsystem用であり、一般的にdriverは基本的に使用されない.taskletとwork queueは通常のdriverで比較的多く用いられ、主な違いはtaskletが中断コンテキストで実行され、work queueがprocessコンテキストであるため、可能なsleepの操作を実行することができる.IRQF_ONESHOTとIRQF_SHAREDは同時に使用できません:IRQF_ONESHOTは割り込みスレッドの割り込みをオフにします.request_threaded_irq(gpio_irq.irq,gpio_irqhandler,gpio_threadhandler,gpio_irq.flags,gpio_irq.name,(void*)0); hardirqとthread_fnが同時に現れた場合、thread_を処理するfnでこの割り込みは開いているrequest_ですthreaded_irq(gpio_irq.irq,NULL,gpio_threadhandler,gpio_irq.flags,gpio_irq.name,(void*)0); しかしhardirqとthread_fnが1つしか存在しない場合、thread_を処理するfnの場合、割り込みは閉じられます.ドライバコードではdevmで始まる関数がよく見られます.このような関数はデバイスリソース管理(Managed Device Resource)に関連しています.ドライバでは、irq、regulator、gpioなど、申請されたリソースのリリースを容易にするために提供されています.ドライバが初期化に失敗すると、通常gotoはどこかにリソースを解放します.このようなラベルが多くなると、コードが簡潔に見えなくなります.devmはこのような状況を処理するためです.devres_add(dev, dr);つまりチェーンテーブルで管理されています.ステップ6:ホットスワップの中断を管理し、mmc_cd_gpio_request(msm_host->mmc, msm_host->pdata->status_gpio);
int mmc_cd_gpio_request(struct mmc_host *host, unsigned int gpio):
irq = gpio_to_irq(gpio);
struct mmc_cd_gpio {
    unsigned int gpio;
    bool status;:
    char label[0];
};
cd = kmalloc(sizeof(*cd) + len, GFP_KERNEL);
snprintf(cd->label, len, "%s cd", dev_name(host->parent));
gpio_request_one(gpio, GPIOF_DIR_IN, cd->label);
cd->gpio = gpio;
host->hotplug.irq = irq;
host->hotplug.handler_priv = cd;
mmc_cd_get_status(host);//      SD 
request_threaded_irq(irq, NULL, mmc_cd_gpio_irqt,
                   IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
                   cd->label, host);
//          
static irqreturn_t mmc_cd_gpio_irqt(int irq, void *dev_id)
{
    struct mmc_host *host = dev_id;
    struct mmc_cd_gpio *cd = host->hotplug.handler_priv;
    int status;
    printk("mmc_cd_gpio_irqt
"
); status = mmc_cd_get_status(host); if (unlikely(status < 0)) goto out; if (status ^ cd->status) { pr_info("%s: slot status change detected (%d -> %d), GPIO_ACTIVE_%s
"
, mmc_hostname(host), cd->status, status, (host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH) ? "HIGH" : "LOW"); cd->status = status; /* Schedule a card detection after a debounce timeout */ mmc_detect_change(host, msecs_to_jiffies(100)); } out: return IRQ_HANDLED; }

mmc_detect_change(host, msecs_to_jiffies(100));
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
#ifdef CONFIG_MMC_DEBUG
    unsigned long flags;
    spin_lock_irqsave(&host->lock, flags);
    WARN_ON(host->removed);
    spin_unlock_irqrestore(&host->lock, flags);
#endif
    host->detect_change = 1;

    mmc_schedule_delayed_work(&host->detect, delay);
}

それはcoreです.c実現した.すなわち、sd cardの挿抜が中断をトリガーし、遅延ワークキューに参加する
static int mmc_schedule_delayed_work(struct delayed_work *work,
                     unsigned long delay)
{
    return queue_delayed_work(workqueue, work, delay);
}

workqueueは最初から初期化されており、workqueue=alloc_ordered_workqueue(“kmmcd”, 0);
mmc_hostの構造体はkernel/include/linux/mmc/host.hではdetectがそのメンバーであると定義されている.struct delayed_work detect; 第七歩:sdにホットスワップの動作があれば、どうしますか?遅延作業の列は何をしましたか.実はprobeの時に初期化しました!1. mmc = mmc_alloc_host(sizeof(struct msmsdcc_host), &pdev->dev); 2. INIT_DELAYED_WORK(&host->detect, mmc_rescan);
void mmc_rescan(struct work_struct *work)
{
    struct mmc_host *host =
        container_of(work, struct mmc_host, detect.work);
    bool extend_wakelock = false;

    if (host->rescan_disable)
        return;

    mmc_bus_get(host);
    mmc_rpm_hold(host, &host->class_dev);

    /*
     * if there is a _removable_ card registered, check whether it is
     * still present
     */
    if (host->bus_ops && host->bus_ops->detect && !host->bus_dead
        && !(host->caps & MMC_CAP_NONREMOVABLE))
     if (host->bus_ops && host->bus_ops->detect && !host->bus_dead )
        host->bus_ops->detect(host);

/*       :     delay work ,         ,         ,                ,                      ,      。              ,                 ( sdio_reset_comm ),     host   delay work*/
    host->detect_change = 0;
    /* If the card was removed the bus will be marked
     * as dead - extend the wakelock so userspace
     * can respond */
    if (host->bus_dead)
        extend_wakelock = 1;


    /* If the card was removed the bus will be marked
     * as dead - extend the wakelock so userspace
     * can respond */
    if (host->bus_dead)
        extend_wakelock = 1;

    /*
     * Let mmc_bus_put() free the bus/bus_ops if we've found that
     * the card is no longer present.
     */
    mmc_bus_put(host);
    mmc_bus_get(host);

    /* if there still is a card present, stop here */
    /*//    bus        0,    0 ,  bus     ,           。    ,  bus_ops            ,                bus_ops   */
    if (host->bus_ops != NULL) {
        mmc_rpm_release(host, &host->class_dev);
        mmc_bus_put(host);
        goto out;
    }

    mmc_rpm_release(host, &host->class_dev);

    /*
     * Only we can add a new handler, so it's safe to
     * release the lock here.
     */
    mmc_bus_put(host);

    if (host->ops->get_cd && host->ops->get_cd(host) == 0) {
        mmc_claim_host(host);
        mmc_power_off(host);
        mmc_release_host(host);
        goto out;
    }

    mmc_rpm_hold(host, &host->class_dev);
    mmc_claim_host(host);
    if (!mmc_rescan_try_freq(host, host->f_min))
        extend_wakelock = true;
    mmc_release_host(host);
    mmc_rpm_release(host, &host->class_dev);
 out:
    /* only extend the wakelock, if suspend has not started yet */
    if (extend_wakelock && !host->rescan_disable)
        wake_lock_timeout(&host->detect_wake_lock, HZ / 2);

    if (host->caps & MMC_CAP_NEEDS_POLL)
        mmc_schedule_delayed_work(&host->detect, HZ);
}

host->bus_ops->detect(host); mmc_rescan_try_freq(); mmcを分析してみましょうrescan_try_freqは、その名の通り、ターゲットカードとの接続を異なるclockで初期化しようとします.
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
    host->f_init = freq; //  mmc_recan          ,       400kHZ.  mmc/sd/sdio          400kHZ.

#ifdef CONFIG_MMC_DEBUG
    pr_info("%s: %s: trying to init card at %u Hz
"
, mmc_hostname(host), __func__, host->f_init); #endif 1. mmc_power_up(host); // , , mmc_add_host , mmc_start_host, host 。 。 /* * 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); // , 。 /* Initialization should be done at 3.3 V I/O voltage. */ mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_330, 0);// , host host->ops->start_signal_voltage_switch /* * 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. */ 2. sdio_reset(host);//① SD ( MMC , ), , host , , 。② SDIO , SDIO , CMD52 。③ SD SDIO , CMD52 SDIO , SD , CMD52 CMD0 。 When the host re-initializes both I/O and Memory controllers, it is strongly recommended that the host either execute a power reset (power off then on) or issues a reset commands to both controllers prior to any other operation. If the host chooses to use the reset commands, it shall issue CMD52 (I/O Reset) first, because it cannot issue CMD52 after CMD0 (see 4.4). After the reset, the host shall re-initialize both the I/O and Memory controller as defined in Figure 3-2.( ) 3. mmc_go_idle(host); // CMD0 SD 。 4. mmc_send_if_cond(host, host->ocr_avail); // sd version 2.0 sd , ACMD41 , CMD8,CMD8 SD host 。 , , CMD8 SD , ,CMD8 R7 ,R7 SD CMD8 “check pattern( 0xAA)”, , , Linux , , , SD , , SD ! , ACMD41 CMD8, version 2.0 , CMD8 , 。 /* Order's important: probe SDIO, then SD, then MMC */ 5 .if (!mmc_attach_sdio(host)) return 0; 6.if (!mmc_attach_sd(host)) return 0; 7.if (!mmc_attach_mmc(host)) return 0; Linux : sdio sdio sd , SD , mmc 。 mmc_power_off(host); return -EIO; }

順序は重要です:probe SDIO、それからSD、それからMMCはmmc_rescan_try_freq();SDカードの場合:初期化:
/*
 * Starting point for SD card init.
 */
int mmc_attach_sd(struct mmc_host *host)

充填操作方法:
static const struct mmc_bus_ops mmc_sd_ops = {
    .remove = mmc_sd_remove,
    .detect = mmc_sd_detect,
    .suspend = NULL,
    .resume = NULL,
    .power_restore = mmc_sd_power_restore,
    .alive = mmc_sd_alive,
    .change_bus_speed = mmc_sd_change_bus_speed,
};

static const struct mmc_bus_ops mmc_sd_ops_unsafe = {
    .remove = mmc_sd_remove,
    .detect = mmc_sd_detect,
    .suspend = mmc_sd_suspend,
    .resume = mmc_sd_resume,
    .power_restore = mmc_sd_power_restore,
    .alive = mmc_sd_alive,
    .change_bus_speed = mmc_sd_change_bus_speed,
};

static void mmc_sd_attach_bus_ops(struct mmc_host *host)
{
    const struct mmc_bus_ops *bus_ops;

    if (!mmc_card_is_removable(host))
        bus_ops = &mmc_sd_ops_unsafe;
    else
        bus_ops = &mmc_sd_ops;
    mmc_attach_bus(host, bus_ops);
}

これで初期化とホットスワップの問題が完了しました.