Chinaunix首页 | 论坛 | 博客
  • 博客访问: 344161
  • 博文数量: 41
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 476
  • 用 户 组: 普通用户
  • 注册时间: 2016-09-01 19:08
个人简介

Android/Linux/音频/驱动

文章分类

全部博文(41)

文章存档

2017年(21)

2016年(20)

我的朋友

分类: Android平台

2017-03-29 08:53:10

【前言】

  Android系统为了保证各商业公司的利益,允许在系统中使用不开源的固件。因此我们可以看到比如用于音频处理的、用于键盘支持的等等大量固件。既然这么多模块要用到固件,那么我们也有必要来了解一下固件在崩溃后的自恢复过程。这篇博文是基于 Intel x86 平台写的,所分析的固件是 Intel 平台的一款音频 DSP 固件,使用 uevent 机制来收发固件恢复消息所以如果读者使用的其它平台或其它固件,在内容上也许会有所出入,但这不影响我们的分析思路。


【填充并发送uevent消息】

  在设备检测到固件崩溃之后,会调用恢复函数。在我使用的平台下,这对应的是 sst_do_recovery() 函数。在这个函数中,主要完成 填充uevent消息、dump固件崩溃信息、重置Intel平台音频配置、清除内存中旧的固件信息、发送uevent消息 这5件事。代码如下:


  1. void sst_do_recovery(struct intel_sst_drv *sst)
  2. {
  3.     char iram_event[IRAM_EVENT_SIZE_MAX], dram_event[DRAM_EVENT_SIZE_MAX];
  4.     char ddr_imr_event[DDR_EVENT_SIZE_MAX], event_type[EVENT_TYPE_SIZE_MAX];
  5.     char *envp[NUM_EVENT_MAX];
  6.     int env_offset = 0;

  7.     pr_err("Audio: Intel SST engine encountered an unrecoverable error\n");
  8.     snprintf(event_type, sizeof(event_type), "EVENT_TYPE=SST_CRASHED"); // 填充uevent消息
  9.     envp[env_offset++] = event_type;
  10.     snprintf(iram_event, sizeof(iram_event), "IRAM_DUMP_SIZE=%d", // 填充uevent消息
  11.             sst->dump_buf.iram_buf.size);
  12.     envp[env_offset++] = iram_event;
  13.     snprintf(dram_event, sizeof(dram_event), "DRAM_DUMP_SIZE=%d", // 填充uevent消息
  14.             sst->dump_buf.dram_buf.size);
  15.     envp[env_offset++] = dram_event;

  16.     if (sst->ddr != NULL) {
  17.         snprintf(ddr_imr_event, sizeof(ddr_imr_event),
  18.         "DDR_IMR_DUMP_SIZE=%d DDR_IMR_ADDRESS=%p", (sst->ddr_end - sst->ddr_base), sst->ddr);
  19.         envp[env_offset++] = ddr_imr_event;
  20.     }
  21.     envp[env_offset] = NULL;
  22.     kobject_uevent_env(&sst->dev->kobj, KOBJ_CHANGE, envp); // 发送uevent消息向上层报告固件已崩溃
  23.     pr_err("SST Crash Uevent Sent!!\n");

  24.     /*
  25.      * setting firmware state as RESET so that the firmware will get
  26.      * redownloaded on next request.This is because firmare not responding
  27.      * for 1 sec is equalant to some unrecoverable error of FW.
  28.      */
  29.     pr_err("Audio: trying to reset the dsp now\n");
  30.     mutex_lock(&sst->sst_lock);
  31.     sst->sst_state = SST_RECOVERY; // 将Intel平台的当前状态置为“恢复中”
  32.     mutex_unlock(&sst->sst_lock);

  33.     dump_stack(); // dump信息
  34.     dump_sst_shim(sst); // dump信息

  35.     mutex_lock(&sst->sst_lock);
  36.     sst_stall_lpe_n_wait(sst);
  37.     mutex_unlock(&sst->sst_lock);

  38.     /* dump mailbox and sram */
  39.     pr_debug("Audio: Dumping Mailbox IA to LPE...\n");
  40.     dump_buffer_fromio(sst->ipc_mailbox, NUM_DWORDS); // dump信息
  41.     pr_debug("Audio: Dumping Mailbox LPE to IA...\n");
  42.     dump_buffer_fromio((sst->ipc_mailbox + sst->mailbox_recv_offset), // dump信息
  43.         NUM_DWORDS);
  44.     pr_debug("Audio: Dumping SRAM CHECKPOINT...\n");
  45.     dump_buffer_fromio((sst->mailbox +
  46.             sst->pdata->debugfs_data->checkpoint_offset),
  47.             DUMP_SRAM_CHECKPOINT_DWORDS);

  48.     if (sst_drv_ctx->ops->set_bypass) {
  49.         mutex_lock(&sst->sst_lock);
  50.         sst_drv_ctx->ops->set_bypass(true);
  51.         dump_ram_area(sst, &(sst->dump_buf), SST_IRAM); // dump信息
  52.         dump_ram_area(sst, &(sst->dump_buf), SST_DRAM); // dump信息
  53.         sst_drv_ctx->ops->set_bypass(false);
  54.         mutex_unlock(&sst->sst_lock);
  55.     }

  56.     /* Send IPC to SCU to power gate and reset the LPE */
  57.     sst_send_scu_reset_ipc(sst); // 重置Intel平台配置

  58.     pr_err("reset the pvt id from val %d\n", sst_drv_ctx->pvt_id);
  59.     spin_lock(&sst_drv_ctx->block_lock);
  60.     sst_drv_ctx->pvt_id = 0;
  61.     spin_unlock(&sst_drv_ctx->block_lock);
  62.     sst_dump_ipc_dispatch_lists(sst_drv_ctx); // dump信息
  63.     sst_dump_rx_lists(sst_drv_ctx); // dump信息

  64.     if (sst_drv_ctx->fw_in_mem) {
  65.         pr_err("Clearing the cached FW copy...\n");
  66.         kfree(sst_drv_ctx->fw_in_mem); // 清除内存中旧的固件内容
  67.         sst_drv_ctx->fw_in_mem = NULL; // 清除内存中旧的固件内容
  68.         sst_memcpy_free_resources(); // 清除内存中旧的固件内容
  69.         kfree(sst_drv_ctx->fw_sg_list.src); // 清除内存中旧的固件内容
  70.         kfree(sst_drv_ctx->fw_sg_list.dst); // 清除内存中旧的固件内容
  71.         sst_drv_ctx->fw_sg_list.list_len = 0; // 清除内存中旧的固件内容
  72.     }

  73.     mutex_lock(&sst->sst_lock);
  74.     sst->sst_state = SST_RESET; // 将Intel平台的当前状态置为“正在重置”
  75.     sst_stream_recovery(sst); // 重置Intel平台音频配置
  76.     mutex_unlock(&sst->sst_lock);

  77.     /* Delay is to ensure that the stream is closed before
  78.      * powering on DAPM widget
  79.      */
  80.     usleep_range(STREAM_CLOSE_DELAY_MIN, STREAM_CLOSE_DELAY_MAX);

  81.     env_offset = 0;
  82.     snprintf(event_type, sizeof(event_type), "EVENT_TYPE=SST_RECOVERY"); // 填充uevent消息
  83.     envp[env_offset++] = event_type;
  84.     envp[env_offset] = NULL;
  85.     kobject_uevent_env(&sst->dev->kobj, KOBJ_CHANGE, envp); // 发送uevent消息通知上层开始重载固件
  86.     pr_err("SST Recovery Uevent Sent!!\n");

  87. }


【接收并处理uevent消息】

  在 Android 系统中,底层发送的 uevent 消息在上层由 ueventd 进行接收和处理。ueventd 是 Android 系统启动后就运行的一个服务进程,它通过 while死循环 不断检查系统是否接收到新的 uevent 消息,如果有就调用 handle_device_fd() 函数进行处理。我们可以在 system/core/init/ueventd.cpp 中找到 ueventd 的主函数。代码如下:


  1. int ueventd_main(int argc, char **argv)
  2. {
  3.     /*
  4.      * init sets the umask to 077 for forked processes. We need to
  5.      * create files with exact permissions, without modification by
  6.      * the umask.
  7.      */
  8.     umask(000);

  9.     /* Prevent fire-and-forget children from becoming zombies.
  10.      * If we should need to wait() for some children in the future
  11.      * (as opposed to none right now), double-forking here instead
  12.      * of ignoring SIGCHLD may be the better solution.
  13.      */
  14.     signal(SIGCHLD, SIG_IGN);

  15.     open_devnull_stdio();
  16.     klog_init();
  17.     klog_set_level(KLOG_NOTICE_LEVEL);

  18.     NOTICE("ueventd started!\n");

  19.     selinux_callback cb;
  20.     cb.func_log = selinux_klog_callback;
  21.     selinux_set_callback(SELINUX_CB_LOG, cb);

  22.     std::string hardware = property_get("ro.hardware");

  23.     ueventd_parse_config_file("/ueventd.rc");
  24.     ueventd_parse_config_file(android::base::StringPrintf("/ueventd.%s.rc", hardware.c_str()).c_str());

  25.     device_init();

  26.     pollfd ufd;
  27.     ufd.events = POLLIN;
  28.     ufd.fd = get_device_fd();

  29.     while (true) { // 使用死循环,不断查询是否有新的消息需要处理
  30.         ufd.revents = 0;
  31.         int nr = poll(&ufd, 1, -1);
  32.         if (nr <= 0) {
  33.             continue;
  34.         }
  35.         if (ufd.revents & POLLIN) {
  36.             handle_device_fd(); // 如果检查到有待处理的消息,在这里进行处理
  37.         }
  38.     }

  39.     return 0;
  40. }


  handle_device_fd()函数主要负责解析 uevent 消息,然后将解析出的消息分别传递给 handle_device_event() 函数和 handle_firmware_event() 函数。后2者会分别检查 uevent 消息是否是 device 类型或 firmware 类型,并且在满足检验条件的情况下进行相应操作。这些函数都可以在 /system/core/init/devices.cpp 文件中找到,代码如下:


  1. void handle_device_fd()
  2. {
  3.     char msg[UEVENT_MSG_LEN+2];
  4.     int n;
  5.     while ((n = uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN)) > 0) {
  6.         if(n >= UEVENT_MSG_LEN) /* overflow -- discard */
  7.             continue;

  8.         msg[n] = '\0';
  9.         msg[n+1] = '\0';

  10.         struct uevent uevent;
  11.         parse_event(msg, &uevent); // 从消息中解析出uevent事件,保存在uevent结构体变量中

  12.         if (selinux_status_updated() > 0) {
  13.             struct selabel_handle *sehandle2;
  14.             sehandle2 = selinux_android_file_context_handle();
  15.             if (sehandle2) {
  16.                 selabel_close(sehandle);
  17.                 sehandle = sehandle2;
  18.             }
  19.         }

  20.         handle_device_event(&uevent);
  21.         handle_firmware_event(&uevent); // 检查是否需要处理固件uevent事件
  22.     }
  23. }


  因为我们是分析固件崩溃后重载的过程,所以来看看 handle_firmware_event() 函数。这个函数的内容比较简洁,在检查传递来的 uevent 消息属于 firmware 子系统和 add 操作后,创建一个子线程并调用 process_firmware_event() 函数对 uevent 消息进行最终的处理。代码如下:


  1. static void handle_firmware_event(struct uevent *uevent)
  2. {
  3.     pid_t pid;

  4.     if(strcmp(uevent->subsystem, "firmware")) // 固件uevent事件所属的子系统参数值必须要是"firmware"
  5.         return;

  6.     if(strcmp(uevent->action, "add")) // 固件uevent事件所属的动作参数值必须要是"add"
  7.         return;

  8.     /* we fork, to avoid making large memory allocations in init proper */
  9.     pid = fork();
  10.     if (!pid) {
  11.         process_firmware_event(uevent); // 开始处理固件事件
  12.         _exit(EXIT_SUCCESS);
  13.     } else if (pid < 0) {
  14.         ERROR("could not fork to process firmware event: %s\n", strerror(errno));
  15.     }
  16. }


  不出意外地,在 proces_firmware_event() 函数中进行了读写文件节点和加载固件的操作。代码如下:

  1. static void process_firmware_event(struct uevent *uevent)
  2. {
  3.     char *root, *loading, *data;
  4.     int l, loading_fd, data_fd, fw_fd;
  5.     size_t i;
  6.     int booting = is_booting();

  7.     INFO("firmware: loading '%s' for '%s'\n",
  8.          uevent->firmware, uevent->path);

  9.     l = asprintf(&root, SYSFS_PREFIX"%s/", uevent->path);
  10.     if (l == -1)
  11.         return;

  12.     l = asprintf(&loading, "%sloading", root); // 获取loading文件的路径
  13.     if (l == -1)
  14.         goto root_free_out;

  15.     l = asprintf(&data, "%sdata", root); // 获取data文件的路径
  16.     if (l == -1)
  17.         goto loading_free_out;

  18.     loading_fd = open(loading, O_WRONLY|O_CLOEXEC); // 打开loading文件
  19.     if(loading_fd < 0)
  20.         goto data_free_out;

  21.     data_fd = open(data, O_WRONLY|O_CLOEXEC); // 打开data文件
  22.     if(data_fd < 0)
  23.         goto loading_close_out;

  24. try_loading_again:
  25.     for (i = 0; i < ARRAY_SIZE(firmware_dirs); i++) {
  26.         char *file = NULL;
  27.         l = asprintf(&file, "%s/%s", firmware_dirs[i], uevent->firmware); // 获取固件文件路径
  28.         if (l == -1)
  29.             goto data_free_out;
  30.         fw_fd = open(file, O_RDONLY|O_CLOEXEC); // 打开固件文件
  31.         free(file);
  32.         if (fw_fd >= 0) {
  33.             if(!load_firmware(fw_fd, loading_fd, data_fd)) // 加载固件
  34.                 INFO("firmware: copy success { '%s', '%s' }\n", root, uevent->firmware);
  35.             else
  36.                 INFO("firmware: copy failure { '%s', '%s' }\n", root, uevent->firmware);
  37.             break;
  38.         }
  39.     }
  40.     if (fw_fd < 0) {
  41.         if (booting) {
  42.             /* If we're not fully booted, we may be missing
  43.              * filesystems needed for firmware, wait and retry.
  44.              */
  45.             usleep(100000); // 如果固件加载失败,并且系统仍处于启动过程中,那么等待100ms后尝试重新加载固件
  46.             booting = is_booting();
  47.             goto try_loading_again; // 重新加载固件
  48.         }
  49.         INFO("firmware: could not open '%s': %s\n", uevent->firmware, strerror(errno));
  50.         write(loading_fd, "-1", 2);
  51.         goto data_close_out;
  52.     }

  53.     close(fw_fd);
  54. data_close_out:
  55.     close(data_fd);
  56. loading_close_out:
  57.     close(loading_fd);
  58. data_free_out:
  59.     free(data);
  60. loading_free_out:
  61.     free(loading);
  62. root_free_out:
  63.     free(root);
  64. }


阅读(4030) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~