Secure Digital(SD) Host Controller一般挂载sd和sdio设备,隶属于mmc子系统,其核心其实跟mmc子系统没什么差异;从我们先前的经验看,总线总是作为数据流通的的载体,那么这个载体我们总是分三段来看:设备--核心--主机;也可以简化成一句话:"设备在收发数据的时候,会调用核心提供的接口,这个接口会去匹配到设备挂载的具体控制器的具体收发的接口实现"。
那我们还是按照先前这个并不成熟的总结依然往下看
shdci主机驱动
这是一个简化的主机驱动的流程,下面我们会按这个依次往下看
asr_sdhci_probe
sdhci_pltfm_init
irq = platform_get_irq(pdev, 0);
sdhci_alloc_host
mmc_alloc_host
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
mmc->ops = sdhci_ops
.request = sdhci_request
sdhci_add_host
__sdhci_add_host
request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq,IRQF_SHARED,mmc_hostname(mmc), host);
sdhci_thread_irq
_mmc_detect_change
mmc_add_host
mmc_start_host
mmc_gpiod_request_cd_irq(host);
_mmc_detect_change(host, 0, false);
mmc_schedule_delayed_work(&host->detect, delay);
初始化主机
首先对平台数据初始化,比如拿中断,申请一个主机,将操作函数放进这个主机
struct sdhci_host *sdhci_alloc_host(struct device *dev,
size_t priv_size)
{
struct mmc_host *mmc;
struct sdhci_host *host;
WARN_ON(dev == NULL);
mmc = mmc_alloc_host(sizeof(struct sdhci_host) + priv_size, dev);
if (!mmc)
return ERR_PTR(-ENOMEM);
host = mmc_priv(mmc);
host->mmc = mmc;
host->mmc_host_ops = sdhci_ops;
mmc->ops = &host->mmc_host_ops;
host->flags = SDHCI_SIGNALING_330;
host->cqe_ier = SDHCI_CQE_INT_MASK;
host->cqe_err_ier = SDHCI_CQE_INT_ERR_MASK;
host->tuning_delay = -1;
host->tuning_loop_count = MAX_TUNING_LOOP;
host->sdma_boundary = SDHCI_DEFAULT_BOUNDARY_ARG;
/*
* The DMA table descriptor count is calculated as the maximum
* number of segments times 2, to allow for an alignment
* descriptor for each segment, plus 1 for a nop end descriptor.
*/
host->adma_table_cnt = SDHCI_MAX_SEGS * 2 + 1;
host->max_adma = 65536;
return host;
}
添加主机
将初始主机拿到的中断号,来注册中断:1.以处理各中断事件(sdhci_irq);2。处理request的完成情况和插拔设备后的探测操作(sdhci_thread_irq);最后就是添加主机,和使能卡探测
int __sdhci_add_host(struct sdhci_host *host)
{
unsigned int flags = WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI;
struct mmc_host *mmc = host->mmc;
int ret;
if ((mmc->caps2 & MMC_CAP2_CQE) &&
(host->quirks & SDHCI_QUIRK_BROKEN_CQE)) {
mmc->caps2 &= ~MMC_CAP2_CQE;
mmc->cqe_ops = NULL;
}
host->complete_wq = alloc_workqueue("sdhci", flags, 0);
if (!host->complete_wq)
return -ENOMEM;
INIT_WORK(&host->complete_work, sdhci_complete_work);
timer_setup(&host->timer, sdhci_timeout_timer, 0);
timer_setup(&host->data_timer, sdhci_timeout_data_timer, 0);
init_waitqueue_head(&host->buf_ready_int);
sdhci_init(host, 0);
ret = request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq,
IRQF_SHARED, mmc_hostname(mmc), host);
if (ret) {
pr_err("%s: Failed to request IRQ %d: %d\n",
mmc_hostname(mmc), host->irq, ret);
goto unwq;
}
ret = sdhci_led_register(host);
if (ret) {
pr_err("%s: Failed to register LED device: %d\n",
mmc_hostname(mmc), ret);
goto unirq;
}
ret = mmc_add_host(mmc);
if (ret)
goto unled;
pr_info("%s: SDHCI controller on %s [%s] using %s\n",
mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)),
host->use_external_dma ? "External DMA" :
(host->flags & SDHCI_USE_ADMA) ?
(host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" :
(host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO");
sdhci_enable_card_detection(host);
return 0;
unled:
sdhci_led_unregister(host);
unirq:
sdhci_reset_for_all(host);
sdhci_writel(host, 0, SDHCI_INT_ENABLE);
sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE);
free_irq(host->irq, host);
unwq:
destroy_workqueue(host->complete_wq);
return ret;
}
创建设备
然后将设备探测函数,作为一个工作,待适合的时机(中断或者poll)的来调度一次,以生成设备;可以看到如果探测到sdio设备,就会调用sdio_init_func读取设备的vendor id,然后添加这个card设备
mmc_rescan
mmc_rescan_try_freq
mmc_attach_sdio
sdio_init_func
mmc_add_card
device_add
static int sdio_init_func(struct mmc_card *card, unsigned int fn)
{
int ret;
struct sdio_func *func;
if (WARN_ON(fn > SDIO_MAX_FUNCS))
return -EINVAL;
func = sdio_alloc_func(card);
if (IS_ERR(func))
return PTR_ERR(func);
func->num = fn;
if (!(card->quirks & MMC_QUIRK_NONSTD_SDIO)) {
ret = sdio_read_fbr(func);
if (ret)
goto fail;
ret = sdio_read_func_cis(func);
if (ret)
goto fail;
} else {
func->vendor = func->card->cis.vendor;
func->device = func->card->cis.device;
func->max_blksize = func->card->cis.blksize;
}
card->sdio_func[fn - 1] = func;
return 0;
fail:
/*
* It is okay to remove the function here even though we hold
* the host lock as we haven't registered the device yet.
*/
sdio_remove_func(func);
return ret;
}
数据收发的具体实现
sdhci_request就是mmc->ops里request的具体实现,它包含了普通通道和dma通道的两种具体实现
sdhci_request
sdhci_data_irq
sdhci_transfer_pio //普通通道
sdhci_write_block_pio/sdhci_read_block_pio
sdhci_request_done //使能dma就会在这等待完成传输
普通写接口,就是通过写寄存器的方式,将主机的sg聚合的数据按固定字节大小,依次发送
static void sdhci_write_block_pio(struct sdhci_host *host)
{
unsigned long flags;
size_t blksize, len, chunk;
u32 scratch;
u8 *buf;
DBG("PIO writing\n");
blksize = host->data->blksz;
chunk = 0;
scratch = 0;
local_irq_save(flags);
while (blksize) {
BUG_ON(!sg_miter_next(&host->sg_miter));
len = min(host->sg_miter.length, blksize);
blksize -= len;
host->sg_miter.consumed = len;
buf = host->sg_miter.addr;
while (len) {
scratch |= (u32)*buf << (chunk * 8);
buf++;
chunk++;
len--;
if ((chunk == 4) || ((len == 0) && (blksize == 0))) {
sdhci_writel(host, scratch, SDHCI_BUFFER);
chunk = 0;
scratch = 0;
}
}
}
sg_miter_stop(&host->sg_miter);
local_irq_restore(flags);
}
使用dma,就使用sdhci_request_done来确保sg聚合的buffer以通过dma传输完成
static bool sdhci_request_done(struct sdhci_host *host)
{
unsigned long flags;
struct mmc_request *mrq;
int i;
spin_lock_irqsave(&host->lock, flags);
for (i = 0; i < SDHCI_MAX_MRQS; i++) {
mrq = host->mrqs_done[i];
if (mrq)
break;
}
if (!mrq) {
spin_unlock_irqrestore(&host->lock, flags);
return true;
}
/*
* The controller needs a reset of internal state machines
* upon error conditions.
*/
if (sdhci_needs_reset(host, mrq)) {
/*
* Do not finish until command and data lines are available for
* reset. Note there can only be one other mrq, so it cannot
* also be in mrqs_done, otherwise host->cmd and host->data_cmd
* would both be null.
*/
if (host->cmd || host->data_cmd) {
spin_unlock_irqrestore(&host->lock, flags);
return true;
}
/* Some controllers need this kick or reset won't work here */
if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET)
/* This is to force an update */
host->ops->set_clock(host, host->clock);
/*
* Spec says we should do both at the same time, but Ricoh
* controllers do not like that.
*/
sdhci_do_reset(host, SDHCI_RESET_CMD);
sdhci_do_reset(host, SDHCI_RESET_DATA);
host->pending_reset = false;
}
/*
* Always unmap the data buffers if they were mapped by
* sdhci_prepare_data() whenever we finish with a request.
* This avoids leaking DMA mappings on error.
*/
if (host->flags & SDHCI_REQ_USE_DMA) {
struct mmc_data *data = mrq->data;
if (data && data->host_cookie == COOKIE_MAPPED) {
if (host->bounce_buffer) {
/*
* On reads, copy the bounced data into the
* sglist
*/
if (mmc_get_dma_dir(data) == DMA_FROM_DEVICE) {
unsigned int length = data->bytes_xfered;
if (length > host->bounce_buffer_size) {
pr_err("%s: bounce buffer is %u bytes but DMA claims to have transferred %u bytes\n",
mmc_hostname(host->mmc),
host->bounce_buffer_size,
data->bytes_xfered);
/* Cap it down and continue */
length = host->bounce_buffer_size;
}
dma_sync_single_for_cpu(
host->mmc->parent,
host->bounce_addr,
host->bounce_buffer_size,
DMA_FROM_DEVICE);
sg_copy_from_buffer(data->sg,
data->sg_len,
host->bounce_buffer,
length);
} else {
/* No copying, just switch ownership */
dma_sync_single_for_cpu(
host->mmc->parent,
host->bounce_addr,
host->bounce_buffer_size,
mmc_get_dma_dir(data));
}
} else {
/* Unmap the raw data */
dma_unmap_sg(mmc_dev(host->mmc), data->sg,
data->sg_len,
mmc_get_dma_dir(data));
}
data->host_cookie = COOKIE_UNMAPPED;
}
}
host->mrqs_done[i] = NULL;
spin_unlock_irqrestore(&host->lock, flags);
mmc_request_done(host->mmc, mrq);
return false;
}
设备驱动
其实说的很复杂,设备驱动调用的这一个核心接口就可以看出,最上面的那句总结
sdio_memcpy_toio
sdio_io_rw_ext_helper
mmc_io_rw_extended//这里填充sg
mmc_wait_for_req
mmc_start_request
host->ops->request(host, mrq);
sdio_write_sg_extended//在这之前填充sg,并作为这个函数的参数,不调用sdio_memcpy_toio,将少一次拷贝,减少cpu利用率
mmc_wait_for_req
mmc_start_request
host->ops->request(host, mrq);
/drivers/mmc/core/sdio_io.c里通用的核心接口sdio_memcpy_toio,是在这个接口里面去填充sg,并最终调用控制器的ops的request来完成收发数据
nt mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz)
{
struct mmc_request mrq = {NULL};
struct mmc_command cmd = {0};
struct mmc_data data = {0};
struct scatterlist sg, *sg_ptr;
struct sg_table sgtable;
unsigned int nents, left_size, i;
unsigned int seg_size = card->host->max_seg_size;
BUG_ON(!card);
BUG_ON(fn > 7);
WARN_ON(blksz == 0);
/* sanity check */
if (addr & ~0x1FFFF)
return -EINVAL;
mrq.cmd = &cmd;
mrq.data = &data;
cmd.opcode = SD_IO_RW_EXTENDED;
cmd.arg = write ? 0x80000000 : 0x00000000;
cmd.arg |= fn << 28;
cmd.arg |= incr_addr ? 0x04000000 : 0x00000000;
cmd.arg |= addr << 9;
if (blocks == 0)
cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */
else
cmd.arg |= 0x08000000 | blocks; /* block mode */
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
cmd.retries = MMC_CMD_RETRIES;
data.blksz = blksz;
/* Code in host drivers/fwk assumes that "blocks" always is >=1 */
data.blocks = blocks ? blocks : 1;
data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
left_size = data.blksz * data.blocks;
nents = (left_size - 1) / seg_size + 1;
if (nents > 1) {
if (sg_alloc_table(&sgtable, nents, GFP_KERNEL))
return -ENOMEM;
data.sg = sgtable.sgl;
data.sg_len = nents;
for_each_sg(data.sg, sg_ptr, data.sg_len, i) {
sg_set_page(sg_ptr, virt_to_page(buf + (i * seg_size)),
min(seg_size, left_size),
offset_in_page(buf + (i * seg_size)));
left_size = left_size - seg_size;
}
} else {
data.sg = &sg;
data.sg_len = 1;
sg_init_one(&sg, buf, left_size);
}
mmc_set_data_timeout(&data, card);
mmc_wait_for_req(card->host, &mrq);
if (nents > 1)
sg_free_table(&sgtable);
if (cmd.error)
return cmd.error;
if (data.error)
return data.error;
if (mmc_host_is_spi(card->host)) {
/* host driver already reported errors */
} else {
if (cmd.resp[0] & R5_ERROR)
return -EIO;
if (cmd.resp[0] & R5_FUNCTION_NUMBER)
return -EINVAL;
if (cmd.resp[0] & R5_OUT_OF_RANGE)
return -ERANGE;
}
return 0;
}
当然还有一种优化方式,设备驱动不调用sdio_memcpy_toio,而是先在设备驱动里去填充sg;这样将少了sdio_memcpy_toio的一次拷贝,以减少cpu利用率
int sdio_write_sg_extended(struct mmc_card *card, unsigned fn, unsigned int addr,
struct tx_scatterlist *sg_list, int nents, int count, unsigned blksz)
{
struct mmc_request mrq = {NULL};
struct mmc_command cmd = {0};
struct mmc_data data = {0};
struct scatterlist *sg_ptr;
struct sg_table sgtable;
int i;
int blkcnt;
int size;
int err;
BUG_ON(!card);
BUG_ON(fn > 7);
if (sg_alloc_table(&sgtable, nents, GFP_KERNEL))
return -ENOMEM;
data.sg = sgtable.sgl;
data.sg_len = nents;
if (count < blksz) {
size = 0;
for_each_sg(data.sg, sg_ptr, data.sg_len, i) {
sg_set_buf(sg_ptr, sg_list[i].buf, sg_list[i].len);
size += sg_list[i].len;
/* add for log */
//printk(KERN_ERR "count=%d,blksz=%d size=%d : sg_list[%d].buf=%p,sg_list[%d].len=%d.\n",count,blksz,size,i,sg_list[i].buf,i,sg_list[i].len);
/* add end */
}
BUG_ON(size != count);
mrq.cmd = &cmd;
mrq.data = &data;
cmd.opcode = SD_IO_RW_EXTENDED;
cmd.arg = 1 << 31;
cmd.arg |= fn << 28;
cmd.arg |= 0x04000000; /* incr_addr */
cmd.arg |= addr << 9;
cmd.arg |= size; /* byte mode */
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
data.blksz = size;
data.blocks = 1;
data.flags = MMC_DATA_WRITE;
mmc_set_data_timeout(&data, card);
mmc_wait_for_req(card->host, &mrq);
if (cmd.error)
err = cmd.error;
else if (data.error)
err = data.error;
else if (cmd.resp[0] & R5_ERROR)
err = -EIO;
else if (cmd.resp[0] & R5_FUNCTION_NUMBER)
err = -EINVAL;
else if (cmd.resp[0] & R5_OUT_OF_RANGE)
err = -ERANGE;
else
err = 0;
sg_free_table(&sgtable);
if (err) {
printk(KERN_ERR "CMD53 write cmd_error = %d data_error=%d\n",
cmd.error, data.error);
return -1;
}
return 0;
}
size = 0;
blkcnt = count / blksz;
if (blkcnt) {
for_each_sg(data.sg, sg_ptr, data.sg_len, i) {
sg_set_buf(sg_ptr, sg_list[i].buf, sg_list[i].len);
size += sg_list[i].len;
/* add for log */
//printk(KERN_ERR "count=%d,blksz=%d size=%d : sg_list[%d].buf=%p,sg_list[%d].len=%d.\n",count,blksz,size,i,sg_list[i].buf,i,sg_list[i].len);
/* add end */
}
mrq.cmd = &cmd;
mrq.data = &data;
cmd.opcode = SD_IO_RW_EXTENDED;
cmd.arg = 1 << 31;
cmd.arg |= fn << 28;
cmd.arg |= 0x04000000; /* incr_addr */
cmd.arg |= addr << 9;
cmd.arg |= 0x08000000 | blkcnt; /* block mode */
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
data.blksz = blksz;
data.blocks = blkcnt;
data.flags = MMC_DATA_WRITE;
mmc_set_data_timeout(&data, card);
mmc_wait_for_req(card->host, &mrq);
if (cmd.error)
err = cmd.error;
else if (data.error)
err = data.error;
else if (cmd.resp[0] & R5_ERROR)
err = -EIO;
else if (cmd.resp[0] & R5_FUNCTION_NUMBER)
err = -EINVAL;
else if (cmd.resp[0] & R5_OUT_OF_RANGE)
err = -ERANGE;
else
err = 0;
sg_free_table(&sgtable);
if (err) {
printk(KERN_ERR "CMD53 write cmd_error = %d data_error=%d\n",
cmd.error, data.error);
return -1;
}
size -= blkcnt * blksz;
}
BUG_ON(size > 0);
return 0;
}