2012年11月20日 星期二

MMC0 card remove bug , kernel lag.

最近在測試 整個 kernel 功能的時候發現 , SD Card 移除的過程中 kernel 會"卡機" 約800mS , 這 800 mS 中 , 連 kernel driver 中的 add_timer() function 都停滯 !!
初步 判別 , 應該卡在 IRQ 中.

尋找了半天 ,發現 , Card remove 過程 , 會呼叫
int _mmc_detect_card_removed(struct mmc_host *host)
在這個 function 中 會呼叫 ret = host->bus_ops->alive(host);
alive() ; 在core/sd.c 中有註冊 , 內容是

static int mmc_sd_alive(struct mmc_host *host)
{
    return mmc_send_status(host->card, NULL);
}


好玩的事情來了 , Card 已經拔除 , 還送 status command 給 Host !!

mmc_send_status() 定義在 mmc_ops.c  中

int mmc_send_status(struct mmc_card *card, u32 *status)
{
    int err;
    struct mmc_command cmd = {0};

    BUG_ON(!card);
    BUG_ON(!card->host);

    cmd.opcode = MMC_SEND_STATUS;
    if (!mmc_host_is_spi(card->host))
        cmd.arg = card->rca << 16;
    cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;

    err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
    if (err)
        return err;

    /* NOTE: callers are required to understand the difference
     * between "native" and SPI format status words!
     */
    if (status)
        *status = cmd.resp[0];

    return 0;
}


送出 command 後要等  cmd done. cmd done 需要 mmc controller 的irq 中斷 .
AM335x BSP 的 mmc 中斷流程如下:

omap_hsmmc_irq()
   omap_hsmmc_do_irq()
      ## if have error.
          omap_hsmmc_reset_controller_fsm()

主要卡在 omap_hsmmc_reset_controller_fsm() 中.

首先 , 我不喜歡 IRQ 執行太多事情 , 所以先用 tasklet 分開 ,這時候要注意 , MMC 的 IRQ 會一直送 ,直到IRQ 事件處理完畢 , tasklet 只能排程一次 , 所以要先關閉   IRQ , 等 tasklit 完成後在打開.

修改後如下:

static void omap_hsmmc_irq_tasklet(unsigned long arg)
{
struct omap_hsmmc_host *host = (struct omap_hsmmc_host *) arg;

    omap_hsmmc_do_irq(host, host->irq_status,host->irq);

//---- reenable mmc irq signal.

    OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK);
    spin_unlock(&host->irq_lock);

}


static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id)
{
struct omap_hsmmc_host *host = dev_id;


#if defined(USING_TASKLET)

//---- disable mmc irq signal.
    spin_lock(&host->irq_lock);
    OMAP_HSMMC_WRITE(host->base, ISE, 0);

    host->irq_status = OMAP_HSMMC_READ(host->base, STAT);

    tasklet_schedule(&host->omap_irq_tasklet);

#else

int status;

    status = OMAP_HSMMC_READ(host->base, STAT);
    do {
        omap_hsmmc_do_irq(host, status, irq);
        /* Flush posted write */
        status = OMAP_HSMMC_READ(host->base, STAT);
    } while (status & INT_EN_MASK);


#endif

    return IRQ_HANDLED;
}


這樣後還是會 "卡機" , 看一下主要卡住的地方 omap_hsmmc_reset_controller_fsm()

unsigned long limit = (loops_per_jiffy  *
            msecs_to_jiffies(MMC_TIMEOUT_MS));


 ..............
...............

        while ((!(OMAP_HSMMC_READ(host->base, SYSCTL) & bit)) &&
                    (i++ < limit))
            cpu_relax();



首先 , cpu_relax() 其實應該是 一堆 NOP 指令(我之前沒有用過@@) , 主要是 loops_per_jiffy 這個參數 ,這個參數應該是每秒執行多少個 nop 指令 , 所以上面這樣的 code 感覺怪怪的 , 這個 cpu_relax() + OMAP_HSMMC_READ + 其他判別 的 code 會執行 數百萬次 ....當然會卡住 !!
都改成 tacklet 了 , 就想說換成一般 mdelay 的方式 , 這個 limit 只是 timeout 的變數, 沒有太大影響. 修改後如下:

static inline void omap_hsmmc_reset_controller_fsm(struct omap_hsmmc_host *host,
                           unsigned long bit)
{
unsigned long i = 0;

#if !defined(USING_TASKLET)
unsigned long limit = (loops_per_jiffy  *
            msecs_to_jiffies(MMC_TIMEOUT_MS));
#endif

    OMAP_HSMMC_WRITE(host->base, SYSCTL,
             OMAP_HSMMC_READ(host->base, SYSCTL) | bit);

    /*
     * OMAP4 ES2 and greater has an updated reset logic.
     * Monitor a 0->1 transition first
     */
    if (mmc_slot(host).features & HSMMC_HAS_UPDATED_RESET)
    {

#if defined(USING_TASKLET)
        while ((!(OMAP_HSMMC_READ(host->base, SYSCTL) & bit)) &&
                    (i++ < MMC_TIMEOUT_MS))
            mdelay(1);

#else
        while ((!(OMAP_HSMMC_READ(host->base, SYSCTL) & bit)) &&
                    (i++ < limit))
            cpu_relax();
#endif

......................
}

這樣.... 終於不 lag 了....... 呼 ...... BSP 有這樣的 bug 還真難找....!!





沒有留言:

張貼留言