msm平台,AP和CP封装在一起,公用一块内存。所以AP需要负责把整个modem, TZ , rpm等binary拷贝到内存中以供modem等subsystem去运行。那AP这边是怎么分配这些内存,又是怎么读出来相关的binary,又如何把binary上传上去的呢??

相关的feature

  1. CONFIG_FW_LOADER
  2. CONFIG_FW_LOADER_USER_HELPER
    • 1
    • 2

modem使用的内存申请

要设置modem的内存大小,必须首先需要确认modem binary的大小,modem需要使用的内存大小等。这个在CMA相关的内容中说过。这里在说一下高通msm8916平台,modem大小检查以及修改方法。 
1) modem binary的大小可以从以下编译的log里边看出来!!(modem_proc/build/ms目录下的pplk-XXX.log或者build_xxxx.log)。 
根据大小对齐1MB大小,就是modem binary需要流出来的大小。看如下例子里边的log,总的大小是77.04, 
所以需要在上面的dtsi文件中留出来78MB就可以。

  1. Image loaded at virtual address 0xc0000000
  2. Image: 55.44 MiB
  3. AMSS Heap: 7.50 MiB (dynamic)
  4. MPSS Heap: 4.00 MiB (dynamic)
  5. DSM Pools: 5.06 MiB
  6. Q6Zip RO, Swap Pool: 2.00 MiB (dynamic)
  7. Q6Zip RW, Swap Pool: 1.00 MiB (dynamic)
  8. Q6Zip RW, dlpager Heap: 1.00 MiB
  9. Extra: 0.54 MiB
  10. Pad ding: 0.37 MiB
  11. End Address Alignment: 0.13 MiB
  12. Total: 77.04 MiB
  13. Available: 7.96 MiB
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

2) 然后去修改modem_proc/config/xxx/ 目录下的cust_config.xml文件中修改modem大小

  1. <!-- 85 MB of physical pool-->
  2. <physical_pool name="DEFAULT_PHYSPOOL">
  3. <region base="0x88000000" size="0x5500000" />
  4. <region base="0x88000000" size="0x4E00000" />
  5. </physical_pool>
    • 1
    • 2
    • 3
    • 4
    • 5

以下是modem相关的device tree的设置。这些内容也在CMA和ion内存相关的帖子里边都讲过。 
但之前有一个疑问就是,在CMA预留了一段内存之后,会把这个赋值给modem的dev->cma_area,然后在分配需要使用的内存的时候从dev->cma_area中取出来,那这个过程好像跟ion内存没有什么关系。能不能去掉下面msmxxx-ion.dtsi中 
modem_adsp_mem相关的设置呢?? 
是可以的!!!其他几个DMA区域,如果直接从CMA分配的话,应该都可以从msmxxx-ion.dtsi文件中去掉!! 
也就是说下面qcom,ion-heap-type = “DMA”的部分其实都可以从msm8916-ion.dtsi文件中去掉,不影响。

  1. //modem相关内存的device tree设置
  2. //pil设备相关的device tree定义
  3. qcom,mss@4080000 {
  4. compatible = "qcom,pil-q6v56-mss";
  5. ....
  6. linux,contiguous-region = <&modem_adsp_mem>;
  7. };
  8. //msmxxx-ion.dtsi定义了如下,上面说了这个部分其实是可以去掉的,不会影响相关内存的分配!!
  9. qcom,ion-heap@26 { /* MODEM HEAP */
  10. compatible = "qcom,msm-ion-reserve";
  11. reg = <26>;
  12. linux,contiguous-region = <&modem_adsp_mem>;
  13. qcom,ion-heap-type = "DMA";
  14. };
  15. //msmxxx-memory.dtsi定义了如下内容
  16. modem_adsp_mem: modem_adsp_region@0 {
  17. linux,reserve-contiguous-region;
  18. linux,reserve-region;
  19. linux,remove-completely;
  20. reg = <0x0 0x86800000 0x0 0x05800000>;
  21. label = "modem_adsp_mem";
  22. };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

modem_adsp_mem指定的区域,需要分配出来,以供下载modem binary。

  1. //pil_mss_driver_probe()->pil_subsys_init()
  2. static int pil_subsys_init(struct modem_data *drv,
  3. struct platform_device *pdev)
  4. {
  5. ...
  6. drv->subsys_desc.name = "modem";
  7. drv->subsys_desc.dev = &pdev->dev;
  8. drv->subsys_desc.owner = THIS_MODULE;
  9. drv->subsys_desc.shutdown = modem_shutdown;
  10. drv->subsys_desc.powerup = modem_powerup;
  11. drv->subsys_desc.ramdump = modem_ramdump;
  12. drv->subsys_desc.free_memory = modem_free_memory;
  13. drv->subsys_desc.crash_shutdown = modem_crash_shutdown;
  14. drv->subsys_desc.err_fatal_handler = modem_err_fatal_intr_handler;
  15. drv->subsys_desc.stop_ack_handler = modem_stop_ack_intr_handler;
  16. drv->subsys_desc.wdog_bite_handler = modem_wdog_bite_intr_handler;
  17. drv->subsys = subsys_register(&drv->subsys_desc);
  18. drv->ramdump_dev = create_ramdump_device("modem", &pdev->dev);
  19. ...
  20. return ret;
  21. }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

之后在modem_powerup()的时候,会先根据modem binary的elf结构独处modem的大小等,然后计算出align之后应该的大小。 
pil_boot()-> request_firmware()读出elf头并计算大小等。 
在pil_setup_region()->pil_alloc_region()的时候,传进去的大小就是从上面读出来的大小。

  1. pil_alloc_region min_addr = 0xc0000000 , max_addr = 0xc2b00000 , aligned_size = 0x2b00000
    • 1

这里看着和实际的内存大小一致!! 可能是因为留出来的CMA区域的大小正好和这个大小一致才这样的。 
在实际调试过程中,也可以打印这个大小之后,调整CMA大小。

再看看实际的CMA大小是怎么申请的。

  1. //调用顺序
  2. pil_boot()->pil_init_mmap()->pil_setup_region()->pil_alloc_region()->
  3. dma_alloc_attrs()->arm_dma_alloc()->__dma_alloc()->__alloc_from_contiguous()->
    • 1
    • 2
    • 3

这个调用的顺序,一步一步往下看可以看到,实际上分配的区域是一块CMA区域,而且就是在CMA注册之后,在相应的platform设备注册的时候保存到dev->cma_area中的区域。 
在相应的设备注册的时候,如果设备的device tree中有”linux,contiguous-region”的时候,就会寻找相应的CMA区域并进行保留。这都是因为注册了platform_bus_typ的notifier函数

  1. bus_register_notifier(&platform_bus_type, &cma_dev_init_nb);
    • 1

看下面的log。

  1. <6>[0.487642] [0:swapper/0:1] cma: Assigned CMA region at 0 to 1de0000.qcom,venus device
  2. <6>[0.489469] [0:swapper/0:1] cma: Assigned CMA region at 0 to 4080000.qcom,mss device
  3. <6>[0.490756] [0:swapper/0:1] cma: Assigned CMA region at 0 to a21b000.qcom,pronto device
  4. <6>[1.125342] [0:swapper/0:1] cma: Assigned CMA region at 0 to 8.qcom,ion-heap device
  5. <6>[1.125793] [0:swapper/0:1] cma: Assigned CMA region at 0 to 1b.qcom,ion-heap device
  6. <6>[1.126233] [0:swapper/0:1] cma: Assigned CMA region at 0 to 1c.qcom,ion-heap device
  7. <6>[1.126671] [0:swapper/0:1] cma: Assigned CMA region at 0 to 17.qcom,ion-heap device
  8. <6>[1.127298] [0:swapper/0:1] cma: Assigned CMA region at 0 to 1a.qcom,ion-heap device
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

这里看到4080000.qcom,mss这个device相应的区域已经保留了CMA区域。 
然后在上面进行分配的时候,在 
__alloc_from_contiguous()->dma_alloc_from_contiguous()->dev_get_cma_area()函数中取到 
相应的dev->cma_area。

modem相关内存的使用和下载

pil_load_seg()->request_firmware_direct()->_request_firmware()函数身生成相应的device节点,并通知ueventd去读取相应的binary然后下载。以下是pil_load_seg里边打印的正在试图下载的binary。

  1. <6>[29.129737] [1:init:1] pil_load_seg fw_name = modem.b02
  2. <6>[29.157808] [1:init:1] pil_load_seg fw_name = modem.b07
  3. <6>[29.191477] [1:init:1] pil_load_seg fw_name = modem.b17
  4. <6>[29.348480] [1:init:1] pil_load_seg fw_name = modem.b19
  5. <6>[29.409733] [1:init:1] pil_load_seg fw_name = modem.b20
  6. <6>[29.489639] [1:init:1] pil_load_seg fw_name = modem.b23
  7. <6>[29.519624] [1:init:1] pil_load_seg fw_name = modem.b24
  8. <6>[29.549829] [1:init:1] pil_load_seg fw_name = modem.b25
  9. <6>[29.591918] [1:init:1] pil_load_seg fw_name = modem.b27
  10. <6>[31.997036] [0:wcnss_service:307] pil_load_seg fw_name = wcnss.b02
  11. <6>[32.658390] [0:wcnss_service:307] pil_load_seg fw_name = wcnss.b04
  12. <6>[32.693754] [0:wcnss_service:307] pil_load_seg fw_name = wcnss.b06
  13. <6>[32.848104] [3:wcnss_service:307] pil_load_seg fw_name = wcnss.b09
  14. <6>[32.854061] [3:wcnss_service:307] pil_load_seg fw_name = wcnss.b10
  15. <6>[32.876115] [3:wcnss_service:307] pil_load_seg fw_name = wcnss.b11
  16. <6>[37.384287] [1:TimedEventQueue:771] pil_load_seg fw_name = venus.b02
  17. <6>[37.438222] [1:TimedEventQueue:771] pil_load_seg fw_name = venus.b03
  18. <6>[37.484909] [2:TimedEventQueue:771] pil_load_seg fw_name = venus.b04
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

在 _request_firmware()->fw_load_from_user_helper()->_request_firmware_load()函数中就在生成相应的dev节点,并通知ueventd。

  1. static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
  2. long timeout)
  3. {
  4. int retval = 0;
  5. struct device *f_dev = &fw_priv->dev;
  6. struct firmware_buf *buf = fw_priv->buf;
  7. struct bin_attribute *fw_attr_data = buf->dest_addr ?
  8. &firmware_direct_attr_data : &firmware_attr_data;
  9. /* fall back on userspace loading */
  10. buf->is_paged_buf = buf->dest_addr ? false : true;
  11. dev_set_uevent_suppress(f_dev, true);
  12. /* Need to pin this module until class device is destroyed */
  13. __module_get(THIS_MODULE);
  14. retval = device_add(f_dev);
  15. //以下生成的data和loading节点,用于ueventd读取相应的binary,然后通过节点加载到内存的。
  16. //用于下载的节点,
  17. retval = device_create_bin_file(f_dev, fw_attr_data);
  18. //生成一个loading的节点,loading节点用于控制的
  19. retval = device_create_file(f_dev, &dev_attr_loading);
  20. if (retval) {
  21. dev_err(f_dev, "%s: device_create_file failed\n", __func__);
  22. goto err_del_bin_attr;
  23. }
  24. if (uevent) { //这里正在通知ueventd
  25. dev_set_uevent_suppress(f_dev, false);
  26. dev_dbg(f_dev, "firmware: requesting %s\n", buf->fw_id);
  27. if (timeout != MAX_SCHEDULE_TIMEOUT)
  28. schedule_delayed_work(&fw_priv->timeout_work, timeout);
  29. kobject_uevent(&fw_priv->dev.kobj, KOBJ_ADD);
  30. }
  31. wait_for_completion(&buf->completion);
  32. cancel_delayed_work_sync(&fw_priv->timeout_work);
  33. }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

_request_firmware() -> assign_firmware_buf() 这是做什么的??

来看一下ueventd.c文件中是怎么检测这个然后读binary,通过loading节点加载binary的。

  1. int ueventd_main(int argc, char **argv){
  2. ...
  3. while(1) {
  4. ufd.revents = 0;
  5. nr = poll(&ufd, 1, -1);
  6. if (nr <= 0)
  7. continue;
  8. if (ufd.revents & POLLIN)
  9. handle_device_fd();
  10. }
  11. }
  12. void handle_device_fd(){
  13. ...
  14. handle_firmware_event(&uevent);//process_firmware_event()
  15. }
  16. #define SYSFS_PREFIX "/sys"
  17. #define FIRMWARE_DIR1 "/etc/firmware"
  18. #define FIRMWARE_DIR2 "/vendor/firmware"
  19. #define FIRMWARE_DIR3 "/firmware/image"
  20. #define FIRMWARE_DIR4 "/firmware-modem/image"
  21. #define DEVICES_BASE "/devices/soc.0"
  22. static void process_firmware_event(struct uevent *uevent){
  23. ...
  24. l = asprintf(&root, SYSFS_PREFIX"%s/", uevent->path);
  25. l = asprintf(&loading, "%sloading", root);
  26. l = asprintf(&file1, FIRMWARE_DIR1"/%s", uevent->firmware);
  27. l = asprintf(&file2, FIRMWARE_DIR2"/%s", uevent->firmware);
  28. l = asprintf(&file3, FIRMWARE_DIR3"/%s", uevent->firmware);
  29. l = asprintf(&file4, FIRMWARE_DIR4"/%s", uevent->firmware);
  30. loading_fd = open(loading, O_WRONLY);
  31. ...
  32. if(!load_firmware(fw_fd, loading_fd, data_fd)) //加载binary
  33. INFO("firmware: copy success { '%s', '%s' }\n", root, uevent->firmware);
  34. else
  35. INFO("firmware: copy failure { '%s', '%s' }\n", root, uevent->firmware);
  36. }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

以下看看ueventd中,真正把读到的binary,传给kernel的函数

  1. static int load_firmware(int fw_fd, int loading_fd, int data_fd)
  2. {
  3. struct stat st;
  4. long len_to_copy;
  5. int ret = 0;
  6. //fstat查看binary的信息,读出来size等
  7. if(fstat(fw_fd, &st) < 0)
  8. return -1;
  9. len_to_copy = st.st_size;
  10. write(loading_fd, "1", 1); /* start transfer */
  11. while (len_to_copy > 0) {
  12. char buf[PAGE_SIZE];
  13. ssize_t nr;
  14. //读
  15. nr = read(fw_fd, buf, sizeof(buf));
  16. if(!nr)
  17. break;
  18. if(nr < 0) {
  19. ret = -1;
  20. break;
  21. }
  22. len_to_copy -= nr;
  23. while (nr > 0) {
  24. ssize_t nw = 0;
  25. //写到data节点
  26. nw = write(data_fd, buf + nw, nr);
  27. if(nw <= 0) {
  28. ret = -1;
  29. goto out;
  30. }
  31. nr -= nw;
  32. }
  33. }
  34. out:
  35. if(!ret) //loading节点用于通知kernel加载情况!!
  36. write(loading_fd, "0", 1); /* successful end of transfer */
  37. else
  38. write(loading_fd, "-1", 2); /* abort transfer */
  39. return ret;
  40. }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

内核中,data节点出来write的函数在_request_firmware_load()中根据buf->dest_addr的值有所不同

  1. static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
  2. long timeout)
  3. {
  4. ...
  5. struct bin_attribute *fw_attr_data = buf->dest_addr ?
  6. &firmware_direct_attr_data : &firmware_attr_data;
  7. ...
  8. }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

在下载modem.bxx的时候应该都是有buf->dest_addr才对

  1. <6>[29.355860] [0:init:1] pil_load_seg fw_name = modem.b02
  2. <6>[29.361829] [0:init:1] fw_load_from_user_helper start
  3. <6>[29.368051] [0:init:1] _request_firmware_load buf->dest_addr = 0x86800000
  4. <6>[29.380308] [0:ueventd:230] firmware_loading_store started
  5. ...
  6. <6>[29.391942] [0:init:1] pil_load_seg fw_name = modem.b07
  7. <6>[29.398801] [0:init:1] fw_load_from_user_helper start
  8. <6>[29.404996] [0:init: 1] _request_firmware_load buf->dest_addr = 0x86840000
  9. ...
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  1. //write(data_fd, buf + nw, nr); buf对应buffer? offset? count对应nr??
  2. static ssize_t firmware_direct_write(struct file *filp, struct kobject *kobj,
  3. struct bin_attribute *bin_attr,
  4. char *buffer, loff_t offset, size_t count)
  5. {
  6. struct device *dev = kobj_to_dev(kobj);
  7. struct firmware_priv *fw_priv = to_firmware_priv(dev);
  8. //获取uevent读取modem binary时候读到的内容,保存在firmware_priv中。firmware_priv中的firmware_buf保存了binary的物理地址,大小等等信息
  9. struct firmware *fw;
  10. ssize_t retval;
  11. if (!capable(CAP_SYS_RAWIO))
  12. return -EPERM;
  13. mutex_lock(&fw_lock);
  14. fw = fw_priv->fw;
  15. if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->buf->status)) {
  16. retval = -ENODEV;
  17. goto out;
  18. }
  19. retval = __firmware_data_rw(fw_priv, buffer, &offset, count, 0);
  20. if (retval < 0)
  21. goto out;
  22. fw_priv->buf->size = max_t(size_t, offset, fw_priv->buf->size);
  23. out:
  24. mutex_unlock(&fw_lock);
  25. return retval;
  26. }
  27. static int __firmware_data_rw(struct firmware_priv *fw_priv, char *buffer,
  28. loff_t *offset, size_t count, int read)
  29. {
  30. u8 __iomem *fw_buf;
  31. struct firmware_buf *buf = fw_priv->buf;
  32. int retval = count;
  33. if ((*offset + count) > buf->dest_size) {
  34. pr_debug("%s: Failed size check.\n", __func__);
  35. retval = -EINVAL;
  36. goto out;
  37. }
  38. //fw_buf 就是要拷贝到内存中的modem binary物理地址对应的虚拟地址。
  39. //map_fw_mem函数中,会根据虚拟地址以及需要拷贝的大小,map出一段虚拟地址。
  40. //map一段物理地址,然后返回内核可以访问的虚拟地址,,这个是通过ioremap相关的函数实现的
  41. fw_buf = buf->map_fw_mem(buf->dest_addr + *offset, count,
  42. buf->map_data);
  43. if (!fw_buf) {
  44. pr_debug("%s: Failed ioremap.\n", __func__);
  45. retval = -ENOMEM;
  46. goto out;
  47. }
  48. //读写,直接拷贝就可以
  49. if (read)
  50. memcpy(buffer, fw_buf, count);
  51. else
  52. memcpy(fw_buf, buffer, count);
  53. *offset += count;
  54. buf->unmap_fw_mem(fw_buf, count, buf->map_data);
  55. out:
  56. return retval;
  57. }
  58. static void *map_fw_mem(phys_addr_t paddr, size_t size, void *data)
  59. {
  60. struct pil_map_fw_info *info = data;
  61. return dma_remap(info->dev, info->region, paddr, size,
  62. &info->attrs);
  63. }
  64. static inline void *dma_remap(struct device *dev, void *cpu_addr,
  65. dma_addr_t dma_handle, size_t size, struct dma_attrs *attrs)
  66. {
  67. const struct dma_map_ops *ops = get_dma_ops(dev);
  68. BUG_ON(!ops);
  69. if (!ops->remap) {
  70. WARN_ONCE(1, "Remap function not implemented for %pS\n",
  71. ops->remap);
  72. return NULL;
  73. }
  74. return ops->remap(dev, cpu_addr, dma_handle, size, attrs);
  75. }
  76. static void *arm_dma_remap(struct device *dev, void *cpu_addr,
  77. dma_addr_t handle, size_t size,
  78. struct dma_attrs *attrs)
  79. {
  80. struct page *page = pfn_to_page(dma_to_pfn(dev, handle));
  81. pgprot_t prot = __get_dma_pgprot(attrs, PAGE_KERNEL);
  82. unsigned long offset = handle & ~PAGE_MASK;
  83. size = PAGE_ALIGN(size + offset);
  84. return __dma_alloc_remap(page, size, GFP_KERNEL, prot,
  85. __builtin_return_address(0)) + offset;
  86. }
  87. static void *
  88. __dma_alloc_remap(struct page *page, size_t size, gfp_t gfp, pgprot_t prot,
  89. const void *caller)
  90. {
  91. struct vm_struct *area;
  92. unsigned long addr;
  93. /*
  94. * DMA allocation can be mapped to user space, so lets
  95. * set VM_USERMAP flags too.
  96. */
  97. //得到一段满足要求的vm_struct。这里
  98. area = get_vm_area_caller(size, VM_ARM_DMA_CONSISTENT | VM_USERMAP,
  99. caller);
  100. if (!area)
  101. return NULL;
  102. addr = (unsigned long)area->addr;
  103. area->phys_addr = __pfn_to_phys(page_to_pfn(page));
  104. //addr是得到的vm_struct对应的虚拟地址,内核可以访问的
  105. //所以根据物理地址以及对应的虚拟地址以及大小等情况,ioremap_page_range会做一个page table
  106. //这样内核就可以直接访问这段内存
  107. if (ioremap_page_range(addr, addr + size, area->phys_addr, prot)) {
  108. vunmap((void *)addr);
  109. return NULL;
  110. }
  111. return (void *)addr;//返回虚拟内存,现在这个虚拟内存就可以直接访问了
  112. }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136

和ioremap_page_range()比较像。

Linux驱动基础:msm平台,modem等framework加载的更多相关文章

  1. linux驱动基础系列--linux spi驱动框架分析

    前言 主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型等也不进行详细说明原理.如果有任何错误地方,请指出,谢谢! spi ...

  2. linux驱动基础系列--linux spi驱动框架分析(续)

    前言 这篇文章是对linux驱动基础系列--linux spi驱动框架分析的补充,主要是添加了最新的linux内核里设备树相关内容. spi设备树相关信息 如之前的文章里所述,控制器的device和s ...

  3. Entity Framework加载相关实体——延迟加载Lazy Loading、贪婪加载Eager Loading、显示加载Explicit Loading

    Entity Framework提供了三种加载相关实体的方法:Lazy Loading,Eager Loading和Explicit Loading.首先我们先来看一下MSDN对三种加载实体方法的定义 ...

  4. []转帖]linux 上修改了nginx.conf 怎么重新加载配置文件生效

    linux 上修改了nginx.conf 怎么重新加载配置文件生效 https://www.cnblogs.com/zhuyeshen/ 步骤如下先利用/usr/local/nginx/sbin/ng ...

  5. Linux驱动基础:MSM平台AP/CP通信机制

    点击打开链接 概述 MSM平台AP和CP封装到一个芯片,共享内容.所以之前也说过,高通的MSM解决方案中,CP的代码都是由AP放到指定地址的内存中以供CP运行.那上传完代码,CP开始跑之后,AP/CP ...

  6. linux驱动基础系列--Linux mmc sd sdio驱动分析

    前言 主要是想对Linux mmc子系统(包含mmc sd sdio)驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.块设备驱动.设备模型等也不进行详细说明原 ...

  7. linux驱动基础系列--Linux 串口、usb转串口驱动分析

    前言 主要是想对Linux 串口.usb转串口驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如字符设备驱动.平台驱动等也不进行详细说明原理.如果有任何错误地方,请指出, ...

  8. linux驱动基础系列--Linux下Spi接口Wifi驱动分析

    前言 本文纯粹的纸上谈兵,我并未在实际开发过程中遇到需要编写或调试这类驱动的时候,本文仅仅是根据源码分析后的记录!基于内核版本:2.6.35.6 .主要是想对spi接口的wifi驱动框架有一个整体的把 ...

  9. linux驱动基础系列--Linux I2c驱动分析

    前言 主要是想对Linux I2c驱动框架有一个整体的把控,因此会忽略协议上的某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型.sysfs等也不进行详细说明原理,涉及到i2c协议部分也只 ...

随机推荐

  1. __str__与__repr__

    在讲解之前,我们先来了解下str和repr的区别:两者都是用来将数字,列表等类型的数据转化为字符串的形式.不同之处在于str更加类似于C语言中使用printf输出的内容,而repr输出的内容会直接将变 ...

  2. Linux下常用的配置

    本文主要给出的都是一些常用的Linux配置,系统版本是基于CentOs6.3,供自己复习和新人学习,不当之处还请指正. vmware tools安装 虚拟机--->安装vmware tools ...

  3. Go 语言教程

    Go 语言教程 Go 是一个开源的编程语言,它能让构造简单.可靠且高效的软件变得容易. Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发, ...

  4. Docker常见仓库MySQL

    MySQL 基本信息 MySQL 是开源的关系数据库实现. 该仓库提供了 MySQL 各个版本的镜像,包括 5.6 系列.5.7 系列等. 使用方法 默认会在 3306 端口启动数据库. $ sudo ...

  5. 【事务】<查询不到同一调用方法其它事务提交的更新>解决方案

    最近遇到一个很棘手的问题,至今也解释不清楚原因,不过已经找到了解决方案. 先来看看Propagation属性的值含义,@Transactional中Propagation属性有7个选项可供选择: Pr ...

  6. iOS控制反转(IoC)与依赖注入(DI)的实现

    背景 最近接触了一段时间的SpringMVC,对其控制反转(IoC)和依赖注入(DI)印象深刻,此后便一直在思考如何使用OC语言较好的实现这两个功能.Java语言自带的注解特性为IoC和DI带来了极大 ...

  7. vuejs关于函数式组件的探究

    所以,在控制台里app1.exist 或app2.exist都可以控制是否显示字母. <!DOCTYPE html> <html lang='zh'> <head> ...

  8. Web Worker Best Practices

    使用Web Worker可以把一些比较计算量相对大的阻塞浏览器响应的计算放在单独的线程里计算. 请求优化 构造Worker的时候需要给定js的链接URL,worker内部请求js运行代码.假如work ...

  9. Android app内存管理的16点建议

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiopshared memory(共享内存) Android通过下面几个方式在不同的Process中来共享RAM: 每一个app的proc ...

  10. Hazelcast源码剖析之Eviction

    v:* { } o:* { } w:* { } .shape { }p.MsoNormal,li.MsoNormal,div.MsoNormal { margin: 0cm; margin-botto ...