一、说明

1、mmc core概述

mmc core主模块是mmc core的实现核心。也是本章的重点内容。

对应代码位置drivers/mmc/core/core.c

其主要负责如下功能:

  • mmc core初始化,包括注册mmc bus、mm host class等等
  • mmc host的管理和维护,包括为其他模块提供mmc_host的操作接口,如下
    • host的启动和停止
    • host的占用和释放
    • host电源状态的保存和恢复
    • host总线操作集的绑定和解绑
    • host上卡状态检测
  • 为其他模块提供mmc_card的操作接口,如下
    • card的唤醒和休眠
    • card擦除
    • card属性的获取
  • 为其他模块提供总线io setting的接口
  • 为其他模块提供mmc请求接口
  • card检测接口
  • bkops操作接口
  • regulator操作接口
  • clock操作接口
  • mmc core电源管理操作接口

2、操作集说明

在mmc_host中有两个操作集成员,需要理解一下,以免在代码中产生误会:

  • mmc_host->struct mmc_host_ops *ops,这个是host的操作集,由host controller驱动决定。对于sdhci类host来说,就是sdhci_ops(sdhci.c中设置)。
  • mmc_host->struct mmc_bus_ops *bus_ops,这个是mmc总线的操作集(也可以理解为host的mmc bus handler,host的总线处理方法),由总线上的card type决定。对于mmc card type来说,就是mmc_ops_unsafe或者mmc_ops(mmc_attach_bus_ops中设置)。

二、API总览

1、mmc core初始化相关

  • mmc_init & mmc_exit (模块内使用)

2、mmc host的管理和维护相关

  • mmc_claim_host & mmc_try_claim_host & mmc_release_host (模块内使用)
  • mmc_power_up & mmc_power_off
  • mmc_start_host & mmc_stop_host
  • mmc_power_save_host & mmc_power_restore_host
  • mmc_resume_host & mmc_suspend_host
  • mmc_pm_notify

3、mmc card的操作相关(包括card状态的获取)

  • mmc_hw_reset & mmc_hw_reset_check &
  • mmc_card_awake & mmc_card_sleep
  • mmc_card_is_prog_state
  • mmc_can_erase
  • mmc_can_trim
  • mmc_can_discard
  • mmc_can_sanitize
  • mmc_can_secure_erase_trim
  • mmc_erase_group_aligned

4、总线io setting相关

  • mmc_set_ios
  • mmc_set_chip_select
  • mmc_set_clock
  • mmc_set_bus_mode
  • mmc_set_bus_width
  • mmc_select_voltage
  • mmc_set_signal_voltage(特殊)
  • mmc_set_timing
  • mmc_set_driver_type
  • mmc_get_max_frequency & mmc_get_min_frequency

5、host的mmc总线相关

  • mmc_resume_bus
  • mmc_attach_bus & mmc_detach_bus

6、mmc请求相关

  • mmc_request_done
  • mmc_wait_for_req
  • mmc_wait_for_cmd
  • mmc_set_data_timeout
  • mmc_align_data_size

7、card检测相关

mmc_detect_change

mmc_rescan

mmc_detect_card_removed

8、bkops操作相关

  • mmc_blk_init_bkops_statistics
  • mmc_start_delayed_bkops
  • mmc_start_bkops & mmc_stop_bkops
  • mmc_start_idle_time_bkops
  • mmc_read_bkops_status

9、regulator操作相关

  • mmc_regulator_get_ocrmask
  • mmc_regulator_set_ocr
  • mmc_regulator_get_supply

10、card擦除操作相关

  • mmc_init_erase
  • mmc_erase

11、clock操作接口

  • mmc_init_clk_scaling & mmc_exit_clk_scaling
  • mmc_can_scale_clk
  • mmc_disable_clk_scaling

12、mmc core电源管理操作

  • mmc_rpm_hold & mmc_rpm_release

二、接口代码说明——mmc core初始化相关

1、mmc_init实现

负责初始化整个mmc core。

  • 主要工作:

    • 分配一个workqueue,用于专门处理mmc core的执行的工作
    • 注册mmc bus
    • 注册mmc host class

代码如下:

  1. static int __init mmc_init(void)
  2. {
  3. int ret;
  4. /* 分配一个workqueue,用于专门处理mmc core的执行的工作 */
  5. workqueue = alloc_ordered_workqueue("kmmcd", 0);
  6. /* 注册mmc bus */
  7. ret = mmc_register_bus(); // 调用mmc_register_bus注册mmc bus,具体参考《mmc core——bus模块说明》
  8. // 会生成/sys/bus/mmc目录
  9. /* 注册mmc host class */
  10. ret = mmc_register_host_class(); // 调用mmc_register_host_class注册mmc host class,具体参考《mmc core——host模块说明》
  11. // 会生成/sys/class/mmc_host目录
  12. /* 注册sdio bus */
  13. ret = sdio_register_bus();
  14. return 0;
  15. }
  16. subsys_initcall(mmc_init);

三、接口代码说明——mmc host的管理和维护相关

1、mmc_claim_host & mmc_try_claim_host & mmc_release_host

host被使能之后就不能再次被使能,并且只能被某个进程独自占用。

可以简单地将host理解为一种资源,同时只能被一个进程获取,但是在占用进程里面可以重复占用。

在对host进行操作之前(包括发起mmc请求),必须使用mmc_claim_host和mmc_release_host来进行获取和释放。

  • 变量说明

    • mmc_host->claimed用来表示host是否被占用
    • mmc_host->claimer用来表示host的占用者(进程)
    • mmc_host->claim_cnt用来表示host的占用者的占用计数,为0时则会释放这个host
  • 代码如下

  1. static inline void mmc_claim_host(struct mmc_host *host)
  2. {
  3. __mmc_claim_host(host, NULL);·// 调用__mmc_claim_host来获取host
  4. }
  5. int __mmc_claim_host(struct mmc_host *host, atomic_t *abort)
  6. {
  7. /////只考虑abort为NULL的情况,在mmc core中的mmc_claim_host也是将其设置为NULL
  8. DECLARE_WAITQUEUE(wait, current);
  9. unsigned long flags;
  10. int stop;
  11. might_sleep(); // 说明这个函数可能导致进程休眠
  12. add_wait_queue(&host->wq, &wait); // 把当前进程加入到等待队列中
  13. spin_lock_irqsave(&host->lock, flags);
  14. while (1) { // 以下尝试获取host,如果host正在被占用,会进入休眠
  15. set_current_state(TASK_UNINTERRUPTIBLE); // 设置进程状态为TASK_UNINTERRUPTIBLE状态
  16. stop = abort ? atomic_read(abort) : 0;
  17. if (stop || !host->claimed || host->claimer == current) // 当host的占用标志claimed为0,或者占用者是当前进程的时候,说明可以占用了,退出
  18. break;
  19. spin_unlock_irqrestore(&host->lock, flags);
  20. schedule(); // 否则,进行调度进入休眠
  21. spin_lock_irqsave(&host->lock, flags);
  22. }
  23. set_current_state(TASK_RUNNING); // 设置进程为运行状态
  24. if (!stop) {
  25. host->claimed = 1; // 设置占用标志claimed
  26. host->claimer = current; // 设置占用者为当前进程
  27. host->claim_cnt += 1; // 占用计数加1
  28. } else
  29. wake_up(&host->wq);
  30. spin_unlock_irqrestore(&host->lock, flags);
  31. remove_wait_queue(&host->wq, &wait); // 将当前进程从等待队列中退出
  32. if (host->ops->enable && !stop && host->claim_cnt == 1)
  33. host->ops->enable(host); // 调用host操作集中的enable方法来占用该host,对应sdhci类host即为sdhci_enable
  34. return stop;
  35. }
  36. void mmc_release_host(struct mmc_host *host)
  37. {
  38. unsigned long flags;
  39. WARN_ON(!host->claimed);
  40. if (host->ops->disable && host->claim_cnt == 1) // 当前claim_cnt为1(马上要变为0),调用释放host了
  41. host->ops->disable(host); // 调用host操作集中的disable方法来释放该host,对应sdhci类host即为sdhci_disable
  42. spin_lock_irqsave(&host->lock, flags);
  43. if (--host->claim_cnt) {
  44. /* Release for nested claim */
  45. spin_unlock_irqrestore(&host->lock, flags); // 如果减一之后计数还不为0,说明当前进程需要继续占用该host,不做其他操作
  46. } else { // 以下需要释放该host
  47. host->claimed = 0; // 设置占用标志claimed为0
  48. host->claimer = NULL; // 清空占用者(进程)
  49. spin_unlock_irqrestore(&host->lock, flags);
  50. wake_up(&host->wq); // 唤醒host的等待队列,让那些调用mmc_claim_host睡眠等待host资源的进程被唤醒
  51. }
  52. }
  53. int mmc_try_claim_host(struct mmc_host *host)
  54. {
  55. // 和mmc_claim_host的主要区别在于进程不会休眠,获取失败直接返回
  56. int claimed_host = 0;
  57. unsigned long flags;
  58. spin_lock_irqsave(&host->lock, flags);
  59. if (!host->claimed || host->claimer == current) {
  60. host->claimed = 1;
  61. host->claimer = current;
  62. host->claim_cnt += 1;
  63. claimed_host = 1;
  64. }
  65. spin_unlock_irqrestore(&host->lock, flags);
  66. if (host->ops->enable && claimed_host && host->claim_cnt == 1)
  67. host->ops->enable(host);
  68. return claimed_host;
  69. }

会调用mmc_host->struct mmc_host_ops->enable和mmc_host->struct mmc_host_ops->disable来使能和禁用host。

对于sdhci类的host,相应就是sdhci_enable和sdhci_disable。

2、mmc_power_up & mmc_power_off

mmc host的上电操作和关电操作。

  • mmc的power状态

    • MMC_POWER_OFF:掉电状态
    • MMC_POWER_UP:正在上电的状态
    • MMC_POWER_ON:供电正常的状态
  • 主要工作

    主要工作就是初始化host的总线设置、总线时钟以及工作电压、信号电压。

代码如下

  1. void mmc_power_up(struct mmc_host *host)
  2. {
  3. int bit;
  4. /* 判断是否已经处于MMC_POWER_ON,是的话不进行后续操作 */
  5. if (host->ios.power_mode == MMC_POWER_ON)
  6. return;
  7. /* 第一阶段,先设置对应的io setting使host处于MMC_POWER_UP的状态(总线工作频率没有设置) */
  8. mmc_host_clk_hold(host); // 先获取host时钟
  9. /* If ocr is set, we use it */
  10. if (host->ocr)
  11. bit = ffs(host->ocr) - 1; // 选择一个ocr配置设置为host的工作电压
  12. else
  13. bit = fls(host->ocr_avail) - 1;
  14. host->ios.vdd = bit;
  15. if (mmc_host_is_spi(host))
  16. host->ios.chip_select = MMC_CS_HIGH;
  17. else {
  18. host->ios.chip_select = MMC_CS_DONTCARE;
  19. host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; // 设置总线模式
  20. }
  21. host->ios.power_mode = MMC_POWER_UP;
  22. host->ios.bus_width = MMC_BUS_WIDTH_1; // 设置总线宽度为1
  23. host->ios.timing = MMC_TIMING_LEGACY; // 串口时序
  24. mmc_set_ios(host); // 调用mmc_set_ios设置总线的io setting,后面会说明
  25. /*
  26. * This delay should be sufficient to allow the power supply
  27. * to reach the minimum voltage.
  28. */
  29. mmc_delay(10);
  30. /* 第二阶段,以host的初始化工作频率再次设置io setting,使host处于MMC_POWER_ON状态 */
  31. host->ios.clock = host->f_init; // 设置总线的时钟频率
  32. host->ios.power_mode = MMC_POWER_ON;
  33. mmc_set_ios(host); // 调用mmc_set_ios设置总线的io setting,后面会说明
  34. /*
  35. * This delay must be at least 74 clock sizes, or 1 ms, or the
  36. * time required to reach a stable voltage.
  37. */
  38. mmc_delay(10);
  39. /* 设置信号的电压 */
  40. /* Set signal voltage to 3.3V */
  41. __mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_330);
  42. mmc_host_clk_release(host); // 释放host时钟
  43. }

3、mmc_start_host & mmc_stop_host

mmc_start_host 用来启动一个host,mmc_stop_host用来停止一个host。

当底层host controller调用mmc_add_host来注册host时,在mmc_add_host中就会调用mmc_start_host来启动一个host了。具体参考《mmc core——host模块说明》。

相对应的,会在mmc_remove_host中调用mmc_stop_host停止host。

  1. void mmc_start_host(struct mmc_host *host)
  2. {
  3. mmc_claim_host(host); // 因为上电操作涉及到对host的使用和设置,需要先占用host
  4. host->f_init = max(freqs[0], host->f_min); // 通过最小频率要设置初始化频率
  5. host->rescan_disable = 0; // 设置rescan_disable标志为0,说明已经可以进行card检测了
  6. if (host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP) // 如果mmc属性设置了MMC_CAP2_NO_PRESCAN_POWERUP,也就是在rescan前不需要进行power up操作时,则进行关电
  7. mmc_power_off(host);
  8. else
  9. mmc_power_up(host); // 否则,调用mmc_power_up对host进行上电操作。这里也是mmc core中启动host的核心函数。
  10. mmc_release_host(host); // 完成上电操作,释放host
  11. /* 到这里host已经可以工作了,可以开始进行后续的card操作了 */
  12. mmc_detect_change(host, 0); // 调用mmc_detect_change检测card变化,后续会继续说明
  13. }

四、接口代码说明——card检测相关

1、mmc_detect_change

在上述中我们知道在启动host的函数mmc_start_host 中最后调用了mmc_detect_change来开始检测card(也就是检测mmc卡槽的状态变化情况)。

其实mmc_detect_change是在driver发现mmc卡槽状态发生变化时,调用mmc_detect_change来进行确认和处理。

  1. void mmc_detect_change(struct mmc_host *host, unsigned long delay)
  2. {
  3. #ifdef CONFIG_MMC_DEBUG
  4. unsigned long flags;
  5. spin_lock_irqsave(&host->lock, flags);
  6. WARN_ON(host->removed);
  7. spin_unlock_irqrestore(&host->lock, flags);
  8. #endif
  9. host->detect_change = 1; // 检测到card状态发生变化的标识
  10. mmc_schedule_delayed_work(&host->detect, delay); // 间隔delay jiffies之后调用host->detect的工作
  11. }

在《host模块说明》已经知道了在mmc_alloc_host中默认将host->detect工作设置为mmc_rescan(card重新扫描)函数, INIT_DELAYED_WORK(&host->detect, mmc_rescan)。

当然,host也可以自己另外设置,但是一般都是使用mmc core提供的mmc_rescan作为detect工作来搜索card。下面说明。

2、mmc_rescan

用于检测host的卡槽状态,并对状态变化做相应的操作。

有card插入时,重新扫描mmc card。

  1. void mmc_rescan(struct work_struct *work)
  2. {
  3. struct mmc_host *host =
  4. container_of(work, struct mmc_host, detect.work);
  5. bool extend_wakelock = false;
  6. /* 如果rescan_disable被设置,说明host此时还禁止rescan */
  7. if (host->rescan_disable)
  8. return;
  9. /* 对于设备不可移除的host来说,只能rescan一次 */
  10. /* If there is a non-removable card registered, only scan once */
  11. if ((host->caps & MMC_CAP_NONREMOVABLE) && host->rescan_entered)
  12. return;
  13. host->rescan_entered = 1;
  14. mmc_bus_get(host); // 获取host对应的bus
  15. mmc_rpm_hold(host, &host->class_dev); // 使host处于rpm resume的状态
  16. /* 以下判断原来的card是否已经被移除,移除了则需要做相应的操作 */
  17. /*
  18. * if there is a _removable_ card registered, check whether it is
  19. * still present
  20. */
  21. if (host->bus_ops && host->bus_ops->detect && !host->bus_dead
  22. && !(host->caps & MMC_CAP_NONREMOVABLE))
  23. host->bus_ops->detect(host);
  24. // host->bus_ops存在的话说明之前是有card插入的状态
  25. // 需要调用host->bus_ops->detect检测card是否被移除,是的话在host->bus_ops->detect中做相应的处理
  26. // 对于mmc type card来说,对应就是mmc_detect,具体参考《card相关模块》
  27. host->detect_change = 0;
  28. /* If the card was removed the bus will be marked
  29. * as dead - extend the wakelock so userspace
  30. * can respond */
  31. if (host->bus_dead)
  32. extend_wakelock = 1; // 需要设置一个wakelock锁,使用户空间可以及时做出相应
  33. /*
  34. * Let mmc_bus_put() free the bus/bus_ops if we've found that
  35. * the card is no longer present.
  36. */
  37. mmc_bus_put(host);
  38. // 因为在这个函数的前面已经获取了一次host,可能导致host->bus_ops->detect中检测到card拔出之后,没有真正释放到host的bus,所以这里先put一次
  39. // host bus的计数(bus_refs)为0的时候,会调用__mmc_release_bus清空host bus的信息
  40. mmc_bus_get(host);
  41. // 再获取host bus
  42. /* if there still is a card present, stop here */
  43. if (host->bus_ops != NULL) { // 说明此时还有card插入,退出后续的操作
  44. mmc_rpm_release(host, &host->class_dev);
  45. mmc_bus_put(host);
  46. goto out;
  47. }
  48. mmc_rpm_release(host, &host->class_dev);
  49. /*
  50. * Only we can add a new handler, so it's safe to
  51. * release the lock here.
  52. */
  53. mmc_bus_put(host);
  54. /* 检测当前卡槽状态,根据卡槽状态做相应的操作 */
  55. if (host->ops->get_cd && host->ops->get_cd(host) == 0) {
  56. // 调用host->ops->get_cd来判断host的卡槽的当前card插入状态
  57. // 对应sdhci类型的host来说,就是sdhci_get_cd
  58. // 为0的时候,表示没有card插入,对应host进行power off操作之后进行退出
  59. // 为1的时候,表示当前有card插入,跳到后续的操作
  60. mmc_claim_host(host);
  61. mmc_power_off(host);
  62. mmc_release_host(host);
  63. goto out;
  64. }
  65. mmc_rpm_hold(host, &host->class_dev);
  66. mmc_claim_host(host);
  67. if (!mmc_rescan_try_freq(host, host->f_min)) // 调用mmc_rescan_try_freq,以支持的最低频率作为工作频率尝试搜索card,后续继续说明
  68. extend_wakelock = true;
  69. mmc_release_host(host);
  70. mmc_rpm_release(host, &host->class_dev);
  71. out:
  72. /* only extend the wakelock, if suspend has not started yet */
  73. if (extend_wakelock && !host->rescan_disable)
  74. wake_lock_timeout(&host->detect_wake_lock, HZ / 2); // 占用wakelock,使系统在HZ/2的时间内不会休眠
  75. if (host->caps & MMC_CAP_NEEDS_POLL)
  76. mmc_schedule_delayed_work(&host->detect, HZ);
  77. // 当host设置了MMC_CAP_NEEDS_POLL属性时,需要每隔HZ的时间轮询检测host的卡槽状态,
  78. // 调度了host->detect工作,对应就是mmc_rescan
  79. }

会调用host->ops->get_cd来判断host的卡槽的当前card插入状态,对应sdhci类型的host就是sdhci_get_cd。

会调用host->bus_ops->detect检测card是否被移除,是的话在host->bus_ops->detect中做相应的处理。对于mmc type card,就是mmc_detect。

3、mmc_rescan_try_freq

以一定频率搜索host bus上的card。

  1. static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
  2. {
  3. host->f_init = freq;
  4. mmc_power_up(host); // 给host做上电操作
  5. mmc_hw_reset_for_init(host); // 硬件复位和初始化
  6. mmc_go_idle(host);
  7. mmc_send_if_cond(host, host->ocr_avail); // 获取card的可用频率,存储到host->ocr_avail中
  8. /* Order's important: probe SDIO, then SD, then MMC */
  9. /* 用于绑定card到host bus上(也就是card和host的绑定)。 */
  10. if (!mmc_attach_sdio(host)) // 先假设card是sdio type card,尝试绑定到host bus上,失败则说明不是sdio type card,继续后面的操作,否则返回
  11. return 0;
  12. if (!mmc_attach_sd(host)) // 先假设card是sd type card,尝试绑定到host bus上,失败则说明不是sd type card,继续后面的操作,否则返回
  13. return 0;
  14. if (!mmc_attach_mmc(host)) // 先假设card是mmc type card,尝试绑定到host bus上,失败则说明不是mmc type card,继续后面的操作,否则返回
  15. // mmc_attach_mmc通过mmc_host获取mmc type card信息,初始化mmc_card,并进行部分驱动,最后将其注册到mmc_bus上。
  16. // 具体参考《card相关模块说明》
  17. return 0;
  18. mmc_power_off(host);
  19. return -EIO;
  20. }

五、接口代码说明——总线io setting相关

0、mmc_ios说明

struct mmc_ios 由mmc core定义的规范的结构,用来维护mmc总线相关的一些io setting。如下:

  1. struct mmc_ios {
  2. unsigned int clock; /* clock rate */ // 当前工作频率
  3. unsigned int old_rate; /* saved clock rate */ // 上一次的工作频率
  4. unsigned long clk_ts; /* time stamp of last updated clock */ // 上一次更新工作频率的时间戳
  5. unsigned short vdd;/* vdd stores the bit number of the selected voltage range from below. */ // 支持的电压表
  6. unsigned char bus_mode; /* command output mode */ // 总线输出模式,包括开漏模式和上拉模式
  7. unsigned char chip_select; /* SPI chip select */ // spi片选
  8. unsigned char power_mode; /* power supply mode */ // 电源状态模式
  9. unsigned char bus_width; /* data bus width */ // 总线宽度
  10. unsigned char timing; /* timing specification used */ // 时序类型
  11. unsigned char signal_voltage; /* signalling voltage (1.8V or 3.3V) */ // 信号的工作电压
  12. unsigned char drv_type; /* driver type (A, B, C, D) */ // 驱动类型
  13. };

在设置总线io setting的过程中,就是要设置mmc_host->mmc_ios中的这些成员。

然后通过调用mmc_set_ios进行统一设置。后续会继续说明。

1、mmc_set_ios

统一设置mmc总线的io设置(io setting)。

  1. void mmc_set_ios(struct mmc_host *host)
  2. {
  3. struct mmc_ios *ios = &host->ios;
  4. if (ios->clock > 0)
  5. mmc_set_ungated(host); // 关闭clock的门控
  6. host->ops->set_ios(host, ios); // 调用host->ops->set_ios来对mmc总线的io setting进行设置,核心函数
  7. // 对于sdhci类型的host,对应就是sdhci_set_ios
  8. }

会调用host->ops->set_ios来对mmc总线的io setting进行设置,核心函数。对于sdhci类型的host,对应就是sdhci_set_ios

2、mmc_set_bus_mode & mmc_set_bus_width

  • mmc_set_bus_mode用于设置总线模式,有如下模式

    • MMC_BUSMODE_OPENDRAIN(开漏模式)
    • MMC_BUSMODE_PUSHPULL(上拉模式)
  • mmc_set_bus_width用于设置总线宽度,有如下模式
    • MMC_BUS_WIDTH_1
    • MMC_BUS_WIDTH_4
    • MMC_BUS_WIDTH_8

代码如下:

  1. void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode)
  2. {
  3. mmc_host_clk_hold(host);
  4. host->ios.bus_mode = mode;
  5. mmc_set_ios(host);
  6. mmc_host_clk_release(host);
  7. }
  8. void mmc_set_bus_width(struct mmc_host *host, unsigned int width)
  9. {
  10. mmc_host_clk_hold(host);
  11. host->ios.bus_width = width;
  12. mmc_set_ios(host);
  13. mmc_host_clk_release(host);
  14. }

其他设置io setting的函数类似,不多说明。

六、接口代码说明——host的mmc总线相关

1、mmc_attach_bus & mmc_detach_bus

  • 主要功能

    • mmc_attach_bus用于将分配一个mmc总线操作集给host。
    • mmc_detach_bus用于释放和host相关联的mmc总线操作集。
  • 一些变量
    • mmc_host->bus_ops,表示host的mmc总线操作集
    • mmc_host->bus_refs,表示host的mmc总线的使用者计数
    • mmc_host->bus_dead,表示host的mmc总线是否被激活,如果设置了bus_ops,那么就会被激活了
  • 代码如下
  1. void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)
  2. {
  3. unsigned long flags;
  4. spin_lock_irqsave(&host->lock, flags);
  5. BUG_ON(host->bus_ops); // 不允许重复设置host的mmc总线操作集
  6. BUG_ON(host->bus_refs); // 当mmc总线的使用者计数还存在时,不允许设置host的mmc总线操作集
  7. host->bus_ops = ops; // 设置host的mmc总线操作集
  8. host->bus_refs = 1; // host的mmc总线的使用者计数设置为1,相当于调用了mmc_bus_get
  9. host->bus_dead = 0; // 总线被激活了
  10. spin_unlock_irqrestore(&host->lock, flags);
  11. }
  12. void mmc_detach_bus(struct mmc_host *host)
  13. {
  14. unsigned long flags;
  15. spin_lock_irqsave(&host->lock, flags);
  16. host->bus_dead = 1; // host的mmc总线设置为dead状态
  17. spin_unlock_irqrestore(&host->lock, flags);
  18. mmc_bus_put(host); // 调用mmc_bus_put释放host的mmc总线,也就是对host的mmc总线的使用者计数-1
  19. }

在《card相关模块》中可以看到mmc_attach_mmc->mmc_attach_bus_ops调用mmc_attach_bus来绑定了host的mmc总线操作集为mmc_ops_unsafe或者mmc_ops

2、mmc_bus_get & mmc_bus_put

  1. static inline void mmc_bus_get(struct mmc_host *host)
  2. {
  3. unsigned long flags;
  4. spin_lock_irqsave(&host->lock, flags);
  5. host->bus_refs++; // 对host的mmc总线的使用者计数+1
  6. spin_unlock_irqrestore(&host->lock, flags);
  7. }
  8. static inline void mmc_bus_put(struct mmc_host *host)
  9. {
  10. unsigned long flags;
  11. spin_lock_irqsave(&host->lock, flags);
  12. host->bus_refs--; // 对host的mmc总线的使用者计数-1
  13. if ((host->bus_refs == 0) && host->bus_ops) // 说明host的mmc总线当前并没有使用,调用__mmc_release_bus进行实际的释放操作
  14. __mmc_release_bus(host);
  15. spin_unlock_irqrestore(&host->lock, flags);
  16. }
  17. static void __mmc_release_bus(struct mmc_host *host)
  18. {
  19. host->bus_ops = NULL; // 清空host的mmc总线操作集
  20. }

七、接口代码说明——mmc请求相关

分成同步的mmc请求和异步的mmc请求。差别如下:

  1. 1、流程上的差别:
  2. 1)会阻塞的处理流程:
  3. mmc_wait_for_req
  4. ——》__mmc_start_req // 发起请求
  5. ————》init_completion(&mrq->completion);
  6. ————》mrq->done = mmc_wait_done
  7. ————》mmc_start_request(host, mrq); // 实际发起请求的操作
  8. ——》mmc_wait_for_req_done // 阻塞等待请求处理完成
  9. ——》返回
  10. 2)不阻塞等待该命令的处理流程:
  11. (注意:并不是说调用这个接口并不会阻塞,而是不会为了等待当前请求处理完成而阻塞,但是可能会等待上一次请求处理完成而阻塞)
  12. mmc_start_req
  13. ——》mmc_wait_for_data_req_done // 阻塞等待上一次的请求处理
  14. ——》__mmc_start_data_req // 发起异步请求
  15. ————》mrq->done = mmc_wait_data_done
  16. ————》mmc_start_request // 实际发起请求的操作
  17. ——》返回

最后都是调用了mmc_start_request使host向MMC发起请求。

0、数据结构说明

一个mmc请求分成两部分内容,分别是命令部分和数据部分。

  • mmc_command
  1. struct mmc_command {
  2. u32 opcode; // 命令的操作码,如MMC_GO_IDLE_STATE、MMC_SEND_OP_COND等等
  3. u32 arg; // 命令的参数
  4. u32 resp[4]; // response值
  5. unsigned int flags; /* expected response type */ // 期待的response的类型
  6. #define mmc_resp_type(cmd) ((cmd)->flags & (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC|MMC_RSP_BUSY|MMC_RSP_OPCODE))
  7. /*
  8. * These are the command types.
  9. */
  10. #define mmc_cmd_type(cmd) ((cmd)->flags & MMC_CMD_MASK)
  11. unsigned int retries; /* max number of retries */ // 失败时的重复尝试次数
  12. unsigned int error; /* command error */ // 命令的错误码
  13. /*
  14. * Standard errno values are used for errors, but some have specific
  15. * meaning in the MMC layer:
  16. *
  17. * ETIMEDOUT Card took too long to respond
  18. * EILSEQ Basic format problem with the received or sent data
  19. * (e.g. CRC check failed, incorrect opcode in response
  20. * or bad end bit)
  21. * EINVAL Request cannot be performed because of restrictions
  22. * in hardware and/or the driver
  23. * ENOMEDIUM Host can determine that the slot is empty and is
  24. * actively failing requests
  25. */
  26. unsigned int cmd_timeout_ms; /* in milliseconds */ // 命令执行的等待超时事件
  27. struct mmc_data *data; /* data segment associated with cmd */ // 和该命令关联在一起的数据段
  28. struct mmc_request *mrq; /* associated request */ // 该命令关联到哪个request
  29. };
  • mmc_data
  1. struct mmc_data {
  2. unsigned int timeout_ns; /* data timeout (in ns, max 80ms) */ // 超时时间,以ns为单位
  3. unsigned int timeout_clks; /* data timeout (in clocks) */ // 超时时间,以clock为单位
  4. unsigned int blksz; /* data block size */ // 块大小
  5. unsigned int blocks; /* number of blocks */ // 块数量
  6. unsigned int error; /* data error */ // 传输的错误码
  7. unsigned int flags; // 传输标识
  8. #define MMC_DATA_WRITE (1 << 8)
  9. #define MMC_DATA_READ (1 << 9)
  10. #define MMC_DATA_STREAM (1 << 10)
  11. unsigned int bytes_xfered;
  12. struct mmc_command *stop; /* stop command */ // 结束传输的命令
  13. struct mmc_request *mrq; /* associated request */ // 该命令关联到哪个request
  14. unsigned int sg_len; /* size of scatter list */
  15. struct scatterlist *sg; /* I/O scatter list */
  16. s32 host_cookie; /* host private data */
  17. bool fault_injected; /* fault injected */
  18. };
  • mmc_request

struct mmc_request是mmc core向host controller发起命令请求的处理单位。

  1. struct mmc_request {
  2. struct mmc_command *sbc; /* SET_BLOCK_COUNT for multiblock */ // 设置块数量的命令,怎么用的后续再补充
  3. struct mmc_command *cmd; // 要传输的命令
  4. struct mmc_data *data; // 要传输的数据
  5. struct mmc_command *stop; // 结束命令,怎么用的后续再补充
  6. struct completion completion; // 完成量
  7. void (*done)(struct mmc_request *);/* completion function */ // 传输结束后的回调函数
  8. struct mmc_host *host; // 所属host
  9. };
  • mmc_async_req
  1. struct mmc_async_req {
  2. /* active mmc request */
  3. struct mmc_request *mrq;
  4. unsigned int cmd_flags; /* copied from struct request */
  5. /*
  6. * Check error status of completed mmc request.
  7. * Returns 0 if success otherwise non zero.
  8. */
  9. int (*err_check) (struct mmc_card *, struct mmc_async_req *);
  10. /* Reinserts request back to the block layer */
  11. void (*reinsert_req) (struct mmc_async_req *);
  12. /* update what part of request is not done (packed_fail_idx) */
  13. int (*update_interrupted_req) (struct mmc_card *,
  14. struct mmc_async_req *);
  15. };

1、mmc_wait_for_req

发起mmc_request请求并且等待其处理完成。由其他需要发起mmc请求的模块调用。

可以结合后面的mmc_request_done来看。

  1. void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
  2. {
  3. #ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME
  4. if (mmc_bus_needs_resume(host))
  5. mmc_resume_bus(host);
  6. #endif
  7. __mmc_start_req(host, mrq); // 开始发起mmc_request请求
  8. mmc_wait_for_req_done(host, mrq); // 等待mmc_request处理完成
  9. }
  10. //-----------------------------------__mmc_start_req说明,开始发起mmc_request请求
  11. static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq)
  12. {
  13. /* 发起mmc_request前的一些初始化工作,包括完成量和处理完成的回调函数的设置 */
  14. init_completion(&mrq->completion); // 初始化完成量,在mmc_wait_for_req_done中会去等待这个完成量
  15. mrq->done = mmc_wait_done;
  16. // 设置mmc_request处理完成的回调函数,会调用complete(&mrq->completion);来设置完成量
  17. // host controller会调用mmc_request_done来执行这个回调函数,具体在后面分析
  18. if (mmc_card_removed(host->card)) { // 检测card是否存在
  19. mrq->cmd->error = -ENOMEDIUM;
  20. complete(&mrq->completion);
  21. return -ENOMEDIUM;
  22. }
  23. /* 调用mmc_start_request发起mmc请求 */
  24. mmc_start_request(host, mrq); // 开始处理mmc_request请求
  25. return 0;
  26. }
  27. static void
  28. mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
  29. {
  30. WARN_ON(!host->claimed);
  31. /* 以下对mmc_request的各个成员,包括cmd、data、stop做验证操作和关联操作 */
  32. mrq->cmd->error = 0;
  33. mrq->cmd->mrq = mrq;
  34. if (mrq->data) {
  35. BUG_ON(mrq->data->blksz > host->max_blk_size);
  36. BUG_ON(mrq->data->blocks > host->max_blk_count);
  37. BUG_ON(mrq->data->blocks * mrq->data->blksz >
  38. host->max_req_size);
  39. mrq->cmd->data = mrq->data; // 也就是说mmc_request的data和其cmd中的data是一一样的
  40. mrq->data->error = 0;
  41. mrq->data->mrq = mrq;
  42. if (mrq->stop) {
  43. mrq->data->stop = mrq->stop;
  44. mrq->stop->error = 0;
  45. mrq->stop->mrq = mrq;
  46. }
  47. #ifdef CONFIG_MMC_PERF_PROFILING
  48. if (host->perf_enable)
  49. host->perf.start = ktime_get();
  50. #endif
  51. }
  52. /* 获取时钟 */
  53. mmc_host_clk_hold(host);
  54. /* 调用host controller的request方法来处理mmc_request请求 */
  55. host->ops->request(host, mrq);
  56. // host->ops->request也就是host controller的request方法,对于sdhci类型的host来说,就是sdhci_request
  57. }
  58. //-----------------------------------mmc_wait_for_req_done说明,等待mmc_request处理完成
  59. static void mmc_wait_for_req_done(struct mmc_host *host,
  60. struct mmc_request *mrq)
  61. {
  62. struct mmc_command *cmd;
  63. while (1) {
  64. wait_for_completion_io(&mrq->completion); // 在这里休眠,等待mrq->completion完成量,在__mmc_start_req中初始化的
  65. cmd = mrq->cmd; // 获取对应的command
  66. /*
  67. * If host has timed out waiting for the commands which can be
  68. * HPIed then let the caller handle the timeout error as it may
  69. * want to send the HPI command to bring the card out of
  70. * programming state.
  71. */
  72. if (cmd->ignore_timeout && cmd->error == -ETIMEDOUT)
  73. break;
  74. if (!cmd->error || !cmd->retries || mmc_card_removed(host->card))
  75. // 如果command正常处理完成,或者失败重复尝试次数为0,或者card被移除了,直接退出循环返回
  76. break;
  77. // 以下处理失败重复尝试的情况
  78. pr_debug("%s: req failed (CMD%u): %d, retrying...\n",
  79. mmc_hostname(host), cmd->opcode, cmd->error);
  80. cmd->retries--;
  81. cmd->error = 0;
  82. host->ops->request(host, mrq);
  83. }
  84. }

会调用host->ops->request来对mmc_request进行处理,对于sdhci类型的host,对应就是sdhci_request。

这个方法就是mmc_request实际被处理的核心。

2、mmc_request_done

通知mmc core某个mmc_request已经处理完成,由host controller调用。

以sdhci类型的host为例,处理完一个mmc_request之后,会执行sdhci_tasklet_finish,而在sdhci_tasklet_finish中会调用mmc_request_done来通知host某个mmc_request已经处理完成了。

  1. void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq)
  2. {
  3. struct mmc_command *cmd = mrq->cmd;
  4. int err = cmd->error;
  5. if (host->card)
  6. mmc_update_clk_scaling(host);
  7. if (err && cmd->retries && !mmc_card_removed(host->card)) {
  8. // command执行出错,如果还需要重复尝试的话,这里不释放clock,只是通知mmc core
  9. if (mrq->done)
  10. mrq->done(mrq);
  11. // 执行mmc_request的回调函数来通知mmc core,
  12. // 对于__mmc_start_req发起的request来说,就是mmc_wait_done,上面已经说明过了
  13. // 对于__mmc_start_data_req发起的request来说,就是mmc_wait_data_done,后面会说明
  14. } else {
  15. mmc_should_fail_request(host, mrq);
  16. // 用于模拟data传输概率出错的情况
  17. // 具体参考http://blog.csdn.net/luckywang1103/article/details/52224160
  18. if (mrq->done)
  19. mrq->done(mrq);
  20. // 执行mmc_request的回调函数来通知mmc core,对于__mmc_start_req发起的request来说,就是mmc_wait_done,上面已经说明过了
  21. mmc_host_clk_release(host);
  22. }
  23. }

通过上述,mrq->done被调度,mmc_wait_done被执行,mrq->completion被设置。

然后等待mrq->completion的mmc_wait_for_req_done就会继续往下执行。

3、mmc_wait_for_cmd

mmc_wait_for_cmd用于处理一个不带数据请求的命令。

会被封装到mmc_request中,通过调用mmc_wait_for_req来发起请求。

  1. int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)
  2. {
  3. struct mmc_request mrq = {NULL};
  4. WARN_ON(!host->claimed);
  5. memset(cmd->resp, 0, sizeof(cmd->resp)); // 清空command的response
  6. cmd->retries = retries; // 失败时的重复尝试次数
  7. mrq.cmd = cmd; // 封装到mmc_request中
  8. cmd->data = NULL; // 不带数据包的命令,故清空data
  9. mmc_wait_for_req(host, &mrq); // 调用mmc_wait_for_req发起mmc请求并且等待其处理完成
  10. return cmd->error; // 返回错误码
  11. }

4、mmc_start_req(重要)

机制说明如下:mmc_start_req会先判断上一次的asycn_req是否处理完成,如果没有处理完成,则会等待其处理完成。

如果处理完成了,为当前要处理的asycn_req发起请求,但是并不会等待,而是直接返回。

注意:并不是说调用这个接口并不会阻塞,而是不会为了等待当前请求处理完成而阻塞,但是可能会等待上一次请求处理完成而阻塞。这样,可以利用等待的一部分时间来做其他操作。

为了方便理解这个函数,需要看一下其函数注释。

  • 要注意,在函数里面有两个异步请求:

    • areq:表示新的异步请求
    • host->areq:表示上一次发起的、正在处理、等待完成的异步请求

代码如下(为了方便理解,对代码进行了简化):

  1. /**
  2. * mmc_start_req - start a non-blocking request // 该函数用来发起一个不阻塞的请求
  3. * @host: MMC host to start command // 要发起对应请求的host
  4. * @areq: async request to start // 要发起的异步请求
  5. * @error: out parameter returns 0 for success, otherwise non zero // 返回值,返回0表示成功,返回非零表示失败
  6. *
  7. * Start a new MMC custom command request for a host. // 为host发起的一个新的mmc命令请求
  8. * If there is on ongoing async request wait for completion // 如果host已经有一个正在处理、等待完成的异步请求,那么会等待这个请求完成!!!
  9. * of that request and start the new one and return. // 然后发起新的请求,然后返回!!!
  10. * Does not wait for the new request to complete. // 并不会等待这个新的请求完成!!!
  11. *
  12. * Returns the completed request, NULL in case of none completed. // 会返回被完成的mmc请求(而不是新的mmc请求。)空表示没有mmc请求被完成。
  13. * Wait for the an ongoing request (previoulsy started) to complete and
  14. * return the completed request. If there is no ongoing request, NULL
  15. * is returned without waiting. NULL is not an error condition.
  16. // 等待上一次发起的mmc请求完成,然后把这个mmc请求返回。如果没有mmc请求正在处理,那么就直接返回而不会等待。空并不是错误条件。
  17. */
  18. struct mmc_async_req *mmc_start_req(struct mmc_host *host,
  19. struct mmc_async_req *areq, int *error)
  20. {
  21. int err = 0;
  22. int start_err = 0;
  23. struct mmc_async_req *data = host->areq;
  24. unsigned long flags;
  25. bool is_urgent;
  26. /* Prepare a new request */
  27. /* 为新的异步请求做准备处理 */
  28. if (areq) {
  29. /*
  30. * start waiting here for possible interrupt
  31. * because mmc_pre_req() taking long time
  32. */
  33. mmc_pre_req(host, areq->mrq, !host->areq);
  34. }
  35. /* 对上一次发起的、正在处理、等待完成的异步请求进行处理、等待操作 */
  36. if (host->areq) {
  37. err = mmc_wait_for_data_req_done(host, host->areq->mrq, areq); // 在这里等待正在处理的异步请求处理完成
  38. //.......以下过滤了错误处理的部分
  39. }
  40. /* 对新的异步请求进行发起操作 */
  41. if (!err && areq) {
  42. /* urgent notification may come again */
  43. spin_lock_irqsave(&host->context_info.lock, flags);
  44. is_urgent = host->context_info.is_urgent;
  45. host->context_info.is_urgent = false;
  46. spin_unlock_irqrestore(&host->context_info.lock, flags);
  47. if (!is_urgent || (areq->cmd_flags & REQ_URGENT)) {
  48. start_err = __mmc_start_data_req(host, areq->mrq); // 调用__mmc_start_data_req发起新的异步请求
  49. } else {
  50. /* previous request was done */
  51. err = MMC_BLK_URGENT_DONE;
  52. if (host->areq) {
  53. mmc_post_req(host, host->areq->mrq, 0);
  54. host->areq = NULL;
  55. }
  56. areq->reinsert_req(areq);
  57. mmc_post_req(host, areq->mrq, 0);
  58. goto exit;
  59. }
  60. }
  61. if (host->areq)
  62. mmc_post_req(host, host->areq->mrq, 0);
  63. /* Cancel a prepared request if it was not started. */
  64. if ((err || start_err) && areq)
  65. mmc_post_req(host, areq->mrq, -EINVAL);
  66. if (err)
  67. host->areq = NULL;
  68. else
  69. host->areq = areq;
  70. exit:
  71. if (error)
  72. *error = err;
  73. return data; // 反正上一次正常处理的异步请求
  74. }
  75. //-----------------------------------------------------------------------------------------------------------------------------
  76. static int __mmc_start_data_req(struct mmc_host *host, struct mmc_request *mrq)
  77. {
  78. mrq->done = mmc_wait_data_done;
  79. // 设置mmc_request处理完成的回调函数,会唤醒正在等待请求被完成的进程,后面说明
  80. // host controller会调用mmc_request_done来执行这个回调函数,具体前面分析过了
  81. mrq->host = host;
  82. mmc_start_request(host, mrq); // 开始处理mmc_request请求,前面已经说明过了
  83. return 0;
  84. }
  85. static void mmc_wait_data_done(struct mmc_request *mrq)
  86. {
  87. unsigned long flags;
  88. struct mmc_context_info *context_info = &mrq->host->context_info;
  89. spin_lock_irqsave(&context_info->lock, flags);
  90. mrq->host->context_info.is_done_rcv = true; // 设置is_done_rcv标识
  91. wake_up_interruptible(&mrq->host->context_info.wait); // 唤醒context_info上的等待进程
  92. spin_unlock_irqrestore(&context_info->lock, flags);
  93. }
  94. //-----------------------------------------------------------------------------------------------------------------------------
  95. static int mmc_wait_for_data_req_done(struct mmc_host *host,
  96. struct mmc_request *mrq,
  97. struct mmc_async_req *next_req)
  98. {
  99. // struct mmc_request *mrq:表示正在等待完成的请求
  100. // struct mmc_async_req *next_req:表示下一次要执行的异步请求
  101. struct mmc_command *cmd;
  102. struct mmc_context_info *context_info = &host->context_info;
  103. bool pending_is_urgent = false;
  104. bool is_urgent = false;
  105. bool is_done_rcv = false;
  106. int err, ret;
  107. unsigned long flags;
  108. while (1) {
  109. /* 在这里等待正在进行的请求完成,会在mmc_wait_data_done中被唤醒 */
  110. /* 有几种情况会唤醒等待进程 */
  111. ret = wait_io_event_interruptible(context_info->wait,(context_info->is_done_rcv || context_info->is_new_req || context_info->is_urgent));
  112. spin_lock_irqsave(&context_info->lock, flags);
  113. is_urgent = context_info->is_urgent;
  114. is_done_rcv = context_info->is_done_rcv;
  115. context_info->is_waiting_last_req = false;
  116. spin_unlock_irqrestore(&context_info->lock, flags);
  117. /* 对请求处理完成的处理 */
  118. if (is_done_rcv) {
  119. context_info->is_done_rcv = false;
  120. context_info->is_new_req = false;
  121. cmd = mrq->cmd;
  122. if (!cmd->error || !cmd->retries || mmc_card_removed(host->card)) {
  123. /* 请求正常处理完成,或者失败但是不需要重复尝试的情况的处理 */
  124. err = host->areq->err_check(host->card, host->areq);
  125. //.......
  126. break; /* return err */
  127. } else {
  128. /* 对请求处理出错并且需要重复尝试的情况的处理 */
  129. //.......
  130. }
  131. }
  132. }
  133. return err;
  134. }

6. [mmc subsystem] mmc core(第六章)——mmc core主模块的更多相关文章

  1. 10. [mmc subsystem] host(第四章)——host实例(sdhci-msm说明)

    一.说明 sdhci-msm是指高通的mmc host,其使用了标准SDHC标准.故可以使用前面说的<host(第二章)--sdhci>和<host(第三章)--sdhci-pltf ...

  2. 9. [mmc subsystem] host(第三章)——sdhci-pltfm说明

    一.sdhci-pltfm说明 sdhci-pltfm并不是实际某个host的driver. sdhci-pltfm是指在sdhci core的基础上,提供了统一对sdhci_host的必要属性进行解 ...

  3. 5. [mmc subsystem] mmc core(第五章)——card相关模块(mmc type card)

    零.说明(重要,需要先搞清楚概念有助于后面的理解) 1.mmc core card相关模块为对应card实现相应的操作,包括初始化操作.以及对应的总线操作集合.负责和对应card协议层相关的东西. 这 ...

  4. 4. [mmc subsystem] mmc core(第四章)——host模块说明

    零.说明 对应代码drivers/mmc/core/host.c,drivers/mmc/core/host.h. 为底层host controller driver实现mmc host的申请以及注册 ...

  5. 3. [mmc subsystem] mmc core(第三章)——bus模块说明

    零.说明 对应代码drivers/mmc/core/bus.c. 抽象出虚拟mmc bus,实现mmc bus的操作. 一.API总览 1.mmc bus相关 mmc_register_bus &am ...

  6. 8. [mmc subsystem] host(第二章)——sdhci

    一.sdhci core说明 1.sdhci说明 具体参考<host(第一章)--概述> SDHC:Secure Digital(SD) Host Controller,是指一套sd ho ...

  7. 7. [mmc subsystem] host(第一章)——概述

    一.host简单说明 host,也可以理解为host controller,是指mmc总线上的主机端,mmc总线的控制器,每个host controller对应一条mmc总线. host contro ...

  8. 2. [mmc subsystem] mmc core数据结构和宏定义说明

    一.host相关 1.struct mmc_host struct mmc_host是mmc core由host controller抽象出来的结构体,用于代表一个mmc host控制器. 数据结构如 ...

  9. 1. [mmc subsystem] 概念与框架

    一.概念 1.mmc的概念 mmc有很多种意义,具体如下: mmc MultiMedia Card,多媒体存储卡, 但后续泛指一个接口协定(一种卡式),能符合这接口的内存器都可称作mmc储存体. 主要 ...

随机推荐

  1. Maven 跳过Junit单元测试

    转载自:https://blog.csdn.net/arkblue/article/details/50974957 -DskipTests,不执行测试用例,但编译测试用例类生成相应的class文件至 ...

  2. 浅谈AMD与CMD

    AMD 是 RequireJS 在推广过程中对模块定义的规范化产出. CMD 是 SeaJS 在推广过程中对模块定义的规范化产出. 这些规范的目的都是为了 JavaScript 的模块化开发,特别是在 ...

  3. UVA11374 Airport Express 正反两次最短路

    问题描述 洛谷(有翻译) 吐槽 一道坑题. 如何对待商务票 因为商务票只有一张,所以在\(k\)条边中只有一条边会被选中,很显然,最后这条边会被枚举. 如何选择使用商务票的边 假设我们正在枚举这条边, ...

  4. kettle工具字符串替换

    原数据: 去掉括号内容(包括括号,或者替换为指定内容) 即可. 世界之大,这个东西,准确的说正则表达式,我搞了小半天!!!

  5. mysqltest语法整理

    1. mysqltest是mysql自带的测试引擎, 它实现了一种小语言,用来描述测试过程,并将测试结果与预期对比. 小语言按照语法大致分为三类:mysql command,sql,comment.s ...

  6. ORB-SLAM2 地图加载2

    补充SystemSetting和InitKeyFrame两个类的代码.实际上,由于是通过SystemSetting来读取的相机内参以及ORB特征参数,所以就可以将Tracking.cc中关于读取内参的 ...

  7. Python之爬虫-校花网

    Python之爬虫-校花网 #!/usr/bin/env python # -*- coding:utf-8 -*- import re import requests # 拿到校花网主页的内容 re ...

  8. PMP图表(必背)

  9. git push时出现 Username for 'https://github.com': 仅仅限于github

    使用git push origin master是出现如下问题:Username for 'https://github.com': 解决办法: git remote set-url origin g ...

  10. 1+x 证书 web 前端开发 HTML5 - 新增的元素,删除的元素

    官方QQ群 1+x 证书 web 前端开发 HTML5 - 新增的元素,删除的元素 http://blog.zh66.club/index.php/archives/197/