diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:32:55 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:32:55 -0800 |
commit | dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0 (patch) | |
tree | 2ba8d1a0846d69b18f623515e8d9b5d9fe38b590 /init/devices.c | |
parent | e54eebbf1a908d65ee8cf80bab62821c05666d70 (diff) | |
download | core-dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0.tar.gz core-dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0.tar.bz2 core-dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0.zip |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'init/devices.c')
-rw-r--r-- | init/devices.c | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/init/devices.c b/init/devices.c new file mode 100644 index 000000000..b1ef6ab2a --- /dev/null +++ b/init/devices.c @@ -0,0 +1,626 @@ +/* + * 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 <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <dirent.h> +#include <unistd.h> +#include <string.h> + +#include <sys/socket.h> +#include <sys/un.h> +#include <linux/netlink.h> +#include <private/android_filesystem_config.h> +#include <sys/time.h> +#include <asm/page.h> + +#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/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 }, + { 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; +} |