/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "init.h" #include "devices.h" #define CMDLINE_PREFIX "/dev" #define SYSFS_PREFIX "/sys" #define FIRMWARE_DIR "/etc/firmware" #define MAX_QEMU_PERM 6 struct uevent { const char *action; const char *path; const char *subsystem; const char *firmware; int major; int minor; }; static int open_uevent_socket(void) { struct sockaddr_nl addr; int sz = 64*1024; // XXX larger? udev uses 16MB! int s; memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_pid = getpid(); addr.nl_groups = 0xffffffff; s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); if(s < 0) return -1; setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)); if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { close(s); return -1; } return s; } struct perms_ { char *name; mode_t perm; unsigned int uid; unsigned int gid; unsigned short prefix; }; static struct perms_ devperms[] = { { "/dev/null", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/zero", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/full", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/ptmx", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/tty", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/random", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/urandom", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/ashmem", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/binder", 0666, AID_ROOT, AID_ROOT, 0 }, /* logger should be world writable (for logging) but not readable */ { "/dev/log/", 0662, AID_ROOT, AID_LOG, 1 }, /* these should not be world writable */ { "/dev/android_adb", 0660, AID_ADB, AID_ADB, 0 }, { "/dev/android_adb_enable", 0660, AID_ADB, AID_ADB, 0 }, { "/dev/ttyMSM0", 0600, AID_BLUETOOTH, AID_BLUETOOTH, 0 }, { "/dev/ttyHS0", 0600, AID_BLUETOOTH, AID_BLUETOOTH, 0 }, { "/dev/uinput", 0600, AID_BLUETOOTH, AID_BLUETOOTH, 0 }, { "/dev/alarm", 0664, AID_SYSTEM, AID_RADIO, 0 }, { "/dev/tty0", 0660, AID_ROOT, AID_SYSTEM, 0 }, { "/dev/graphics/", 0660, AID_ROOT, AID_GRAPHICS, 1 }, { "/dev/hw3d", 0660, AID_SYSTEM, AID_GRAPHICS, 0 }, { "/dev/input/", 0660, AID_ROOT, AID_INPUT, 1 }, { "/dev/eac", 0660, AID_ROOT, AID_AUDIO, 0 }, { "/dev/cam", 0660, AID_ROOT, AID_CAMERA, 0 }, { "/dev/pmem", 0660, AID_SYSTEM, AID_GRAPHICS, 0 }, { "/dev/pmem_gpu", 0660, AID_SYSTEM, AID_GRAPHICS, 1 }, { "/dev/pmem_adsp", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/pmem_camera", 0660, AID_SYSTEM, AID_CAMERA, 1 }, { "/dev/oncrpc/", 0660, AID_ROOT, AID_SYSTEM, 1 }, { "/dev/adsp/", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/mt9t013", 0660, AID_SYSTEM, AID_SYSTEM, 0 }, { "/dev/akm8976_daemon",0640, AID_COMPASS, AID_SYSTEM, 0 }, { "/dev/akm8976_aot", 0640, AID_COMPASS, AID_SYSTEM, 0 }, { "/dev/akm8976_pffd", 0640, AID_COMPASS, AID_SYSTEM, 0 }, { "/dev/msm_pcm_out", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_pcm_in", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_pcm_ctl", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_snd", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_mp3", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_audpre", 0660, AID_SYSTEM, AID_AUDIO, 0 }, { "/dev/htc-acoustic", 0660, AID_SYSTEM, AID_AUDIO, 0 }, { "/dev/smd0", 0640, AID_RADIO, AID_RADIO, 0 }, { "/dev/qemu_trace", 0666, AID_SYSTEM, AID_SYSTEM, 0 }, { "/dev/qmi", 0640, AID_RADIO, AID_RADIO, 0 }, { "/dev/qmi0", 0640, AID_RADIO, AID_RADIO, 0 }, { "/dev/qmi1", 0640, AID_RADIO, AID_RADIO, 0 }, { "/dev/qmi2", 0640, AID_RADIO, AID_RADIO, 0 }, { "/dev/tun", 0640, AID_VPN , AID_VPN, 0 }, { NULL, 0, 0, 0, 0 }, }; /* devperms_partners list and perm_node are for hardware specific /dev entries */ struct perm_node { struct perms_ dp; struct listnode plist; }; list_declare(devperms_partners); /* * Permission override when in emulator mode, must be parsed before * system properties is initalized. */ static int qemu_perm_count; static struct perms_ qemu_perms[MAX_QEMU_PERM + 1]; int add_devperms_partners(const char *name, mode_t perm, unsigned int uid, unsigned int gid, unsigned short prefix) { int size; struct perm_node *node = malloc(sizeof (struct perm_node)); if (!node) return -ENOMEM; size = strlen(name) + 1; if ((node->dp.name = malloc(size)) == NULL) return -ENOMEM; memcpy(node->dp.name, name, size); node->dp.perm = perm; node->dp.uid = uid; node->dp.gid = gid; node->dp.prefix = prefix; list_add_tail(&devperms_partners, &node->plist); return 0; } void qemu_init(void) { qemu_perm_count = 0; memset(&qemu_perms, 0, sizeof(qemu_perms)); } static int qemu_perm(const char* name, mode_t perm, unsigned int uid, unsigned int gid, unsigned short prefix) { char *buf; if (qemu_perm_count == MAX_QEMU_PERM) return -ENOSPC; buf = malloc(strlen(name) + 1); if (!buf) return -errno; strlcpy(buf, name, strlen(name) + 1); qemu_perms[qemu_perm_count].name = buf; qemu_perms[qemu_perm_count].perm = perm; qemu_perms[qemu_perm_count].uid = uid; qemu_perms[qemu_perm_count].gid = gid; qemu_perms[qemu_perm_count].prefix = prefix; qemu_perm_count++; return 0; } /* Permission overrides for emulator that are parsed from /proc/cmdline. */ void qemu_cmdline(const char* name, const char *value) { char *buf; if (!strcmp(name, "android.ril")) { /* cmd line params currently assume /dev/ prefix */ if (asprintf(&buf, CMDLINE_PREFIX"/%s", value) == -1) { return; } INFO("nani- buf:: %s\n", buf); qemu_perm(buf, 0660, AID_RADIO, AID_ROOT, 0); } } static int get_device_perm_inner(struct perms_ *perms, const char *path, unsigned *uid, unsigned *gid, mode_t *perm) { int i; for(i = 0; perms[i].name; i++) { if(perms[i].prefix) { if(strncmp(path, perms[i].name, strlen(perms[i].name))) continue; } else { if(strcmp(path, perms[i].name)) continue; } *uid = perms[i].uid; *gid = perms[i].gid; *perm = perms[i].perm; return 0; } return -1; } /* First checks for emulator specific permissions specified in /proc/cmdline. */ static mode_t get_device_perm(const char *path, unsigned *uid, unsigned *gid) { mode_t perm; if (get_device_perm_inner(qemu_perms, path, uid, gid, &perm) == 0) { return perm; } else if (get_device_perm_inner(devperms, path, uid, gid, &perm) == 0) { return perm; } else { struct listnode *node; struct perm_node *perm_node; struct perms_ *dp; /* Check partners list. */ list_for_each(node, &devperms_partners) { perm_node = node_to_item(node, struct perm_node, plist); dp = &perm_node->dp; if (dp->prefix) { if (strncmp(path, dp->name, strlen(dp->name))) continue; } else { if (strcmp(path, dp->name)) continue; } /* Found perm in partner list. */ *uid = dp->uid; *gid = dp->gid; return dp->perm; } /* Default if nothing found. */ *uid = 0; *gid = 0; return 0600; } } static void make_device(const char *path, int block, int major, int minor) { unsigned uid; unsigned gid; mode_t mode; dev_t dev; if(major > 255 || minor > 255) return; mode = get_device_perm(path, &uid, &gid) | (block ? S_IFBLK : S_IFCHR); dev = (major << 8) | minor; mknod(path, mode, dev); chown(path, uid, gid); } #ifdef LOG_UEVENTS static inline suseconds_t get_usecs(void) { struct timeval tv; gettimeofday(&tv, 0); return tv.tv_sec * (suseconds_t) 1000000 + tv.tv_usec; } #define log_event_print(x...) INFO(x) #else #define log_event_print(fmt, args...) do { } while (0) #define get_usecs() 0 #endif static void parse_event(const char *msg, struct uevent *uevent) { uevent->action = ""; uevent->path = ""; uevent->subsystem = ""; uevent->firmware = ""; uevent->major = -1; uevent->minor = -1; /* currently ignoring SEQNUM */ while(*msg) { if(!strncmp(msg, "ACTION=", 7)) { msg += 7; uevent->action = msg; } else if(!strncmp(msg, "DEVPATH=", 8)) { msg += 8; uevent->path = msg; } else if(!strncmp(msg, "SUBSYSTEM=", 10)) { msg += 10; uevent->subsystem = msg; } else if(!strncmp(msg, "FIRMWARE=", 9)) { msg += 9; uevent->firmware = msg; } else if(!strncmp(msg, "MAJOR=", 6)) { msg += 6; uevent->major = atoi(msg); } else if(!strncmp(msg, "MINOR=", 6)) { msg += 6; uevent->minor = atoi(msg); } /* advance to after the next \0 */ while(*msg++) ; } log_event_print("event { '%s', '%s', '%s', '%s', %d, %d }\n", uevent->action, uevent->path, uevent->subsystem, uevent->firmware, uevent->major, uevent->minor); } static void handle_device_event(struct uevent *uevent) { char devpath[96]; char *base, *name; int block; /* if it's not a /dev device, nothing to do */ if((uevent->major < 0) || (uevent->minor < 0)) return; /* do we have a name? */ name = strrchr(uevent->path, '/'); if(!name) return; name++; /* too-long names would overrun our buffer */ if(strlen(name) > 64) return; /* are we block or char? where should we live? */ if(!strncmp(uevent->subsystem, "block", 5)) { block = 1; base = "/dev/block/"; mkdir(base, 0755); } else { block = 0; /* this should probably be configurable somehow */ if(!strncmp(uevent->subsystem, "graphics", 8)) { base = "/dev/graphics/"; mkdir(base, 0755); } else if (!strncmp(uevent->subsystem, "oncrpc", 6)) { base = "/dev/oncrpc/"; mkdir(base, 0755); } else if (!strncmp(uevent->subsystem, "adsp", 4)) { base = "/dev/adsp/"; mkdir(base, 0755); } else if(!strncmp(uevent->subsystem, "input", 5)) { base = "/dev/input/"; mkdir(base, 0755); } else if(!strncmp(uevent->subsystem, "mtd", 3)) { base = "/dev/mtd/"; mkdir(base, 0755); } else if(!strncmp(uevent->subsystem, "misc", 4) && !strncmp(name, "log_", 4)) { base = "/dev/log/"; mkdir(base, 0755); name += 4; } else base = "/dev/"; } snprintf(devpath, sizeof(devpath), "%s%s", base, name); if(!strcmp(uevent->action, "add")) { make_device(devpath, block, uevent->major, uevent->minor); return; } if(!strcmp(uevent->action, "remove")) { unlink(devpath); return; } } static int load_firmware(int fw_fd, int loading_fd, int data_fd) { struct stat st; long len_to_copy; int ret = 0; if(fstat(fw_fd, &st) < 0) return -1; len_to_copy = st.st_size; write(loading_fd, "1", 1); /* start transfer */ while (len_to_copy > 0) { char buf[PAGE_SIZE]; ssize_t nr; nr = read(fw_fd, buf, sizeof(buf)); if(!nr) break; if(nr < 0) { ret = -1; break; } len_to_copy -= nr; while (nr > 0) { ssize_t nw = 0; nw = write(data_fd, buf + nw, nr); if(nw <= 0) { ret = -1; goto out; } nr -= nw; } } out: if(!ret) write(loading_fd, "0", 1); /* successful end of transfer */ else write(loading_fd, "-1", 2); /* abort transfer */ return ret; } static void process_firmware_event(struct uevent *uevent) { char *root, *loading, *data, *file; int l, loading_fd, data_fd, fw_fd; log_event_print("firmware event { '%s', '%s' }\n", uevent->path, uevent->firmware); l = asprintf(&root, SYSFS_PREFIX"%s/", uevent->path); if (l == -1) return; l = asprintf(&loading, "%sloading", root); if (l == -1) goto root_free_out; l = asprintf(&data, "%sdata", root); if (l == -1) goto loading_free_out; l = asprintf(&file, FIRMWARE_DIR"/%s", uevent->firmware); if (l == -1) goto data_free_out; loading_fd = open(loading, O_WRONLY); if(loading_fd < 0) goto file_free_out; data_fd = open(data, O_WRONLY); if(data_fd < 0) goto loading_close_out; fw_fd = open(file, O_RDONLY); if(fw_fd < 0) goto data_close_out; if(!load_firmware(fw_fd, loading_fd, data_fd)) log_event_print("firmware copy success { '%s', '%s' }\n", root, file); else log_event_print("firmware copy failure { '%s', '%s' }\n", root, file); close(fw_fd); data_close_out: close(data_fd); loading_close_out: close(loading_fd); file_free_out: free(file); data_free_out: free(data); loading_free_out: free(loading); root_free_out: free(root); } static void handle_firmware_event(struct uevent *uevent) { pid_t pid; if(strcmp(uevent->subsystem, "firmware")) return; if(strcmp(uevent->action, "add")) return; /* we fork, to avoid making large memory allocations in init proper */ pid = fork(); if (!pid) { process_firmware_event(uevent); exit(EXIT_SUCCESS); } } #define UEVENT_MSG_LEN 1024 void handle_device_fd(int fd) { char msg[UEVENT_MSG_LEN+2]; int n; while((n = recv(fd, msg, UEVENT_MSG_LEN, 0)) > 0) { struct uevent uevent; if(n == UEVENT_MSG_LEN) /* overflow -- discard */ continue; msg[n] = '\0'; msg[n+1] = '\0'; parse_event(msg, &uevent); handle_device_event(&uevent); handle_firmware_event(&uevent); } } /* Coldboot walks parts of the /sys tree and pokes the uevent files ** to cause the kernel to regenerate device add events that happened ** before init's device manager was started ** ** We drain any pending events from the netlink socket every time ** we poke another uevent file to make sure we don't overrun the ** socket's buffer. */ static void do_coldboot(int event_fd, DIR *d) { struct dirent *de; int dfd, fd; dfd = dirfd(d); fd = openat(dfd, "uevent", O_WRONLY); if(fd >= 0) { write(fd, "add\n", 4); close(fd); handle_device_fd(event_fd); } while((de = readdir(d))) { DIR *d2; if(de->d_type != DT_DIR || de->d_name[0] == '.') continue; fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY); if(fd < 0) continue; d2 = fdopendir(fd); if(d2 == 0) close(fd); else { do_coldboot(event_fd, d2); closedir(d2); } } } static void coldboot(int event_fd, const char *path) { DIR *d = opendir(path); if(d) { do_coldboot(event_fd, d); closedir(d); } } int device_init(void) { suseconds_t t0, t1; int fd; fd = open_uevent_socket(); if(fd < 0) return -1; fcntl(fd, F_SETFD, FD_CLOEXEC); fcntl(fd, F_SETFL, O_NONBLOCK); t0 = get_usecs(); coldboot(fd, "/sys/class"); coldboot(fd, "/sys/block"); coldboot(fd, "/sys/devices"); t1 = get_usecs(); log_event_print("coldboot %ld uS\n", ((long) (t1 - t0))); return fd; }