/* * Purpose: OSS device autodetection utility for Linux * */ /* * * This file is part of Open Sound System. * * Copyright (C) 4Front Technologies 1996-2008. * * This this source file is released under GPL v2 license (no other versions). * See the COPYING file included in the main directory of this source * distribution for the license terms and conditions. * */ #include #include #include #include #include #include #include #include #include #include #define PCI_PASS 0 #define USB_PASS 1 #define PSEUDO_PASS 2 #define MAX_PASS 3 #define OSSLIBDIRLEN 512 static char *osslibdir = NULL; static int usb_ok = 0; static int verbose = 0; typedef struct { char *key, *driver, *name; int is_3rdparty; int detected; int pass; } driver_def_t; typedef struct drv_slist { const char * drv_name; struct drv_slist * next; } drvlist_t; static drvlist_t * drvl = NULL; #define MAX_DRIVERS 1000 static driver_def_t drivers[MAX_DRIVERS]; static int ndrivers = 0; static int add_drv (const char *, int); static int decode_descriptor (unsigned char *, int); static void create_devlinks (mode_t); static char * get_mapname (void); static unsigned short get_uint16 (unsigned char *); static int is_audio (unsigned char *, int); static void load_license (const char *); static void load_devlist (const char *, int); static void pci_checkdevice (char *); static void pci_detect (char *); static drvlist_t * prepend_drvlist (const char *); static int remove_devlinks (const char *); static void usb_checkdevice (char *); static void usb_scandir (char *); static void usb_detect (void); static char * get_mapname (void) { FILE *f; static char name[OSSLIBDIRLEN], tmp[OSSLIBDIRLEN+11]; struct stat st; if ((f = fopen ("/etc/oss.conf", "r")) == NULL) { perror ("/etc/oss.conf"); goto oexit2; } while (fgets (tmp, sizeof (tmp), f) != NULL) { int l = strlen (tmp); if (l > 0 && tmp[l - 1] == '\n') tmp[l - 1] = '\0'; if (strncmp (tmp, "OSSLIBDIR=", 10) == 0) { l = snprintf (name, sizeof (name), "%s", &tmp[10]); if ((l >= OSSLIBDIRLEN) || (l < 0)) { fprintf (stderr, "String in /etc/oss.conf is too long!\n"); goto oexit; } if ((stat (name, &st) == -1) || !S_ISDIR(st.st_mode)) { fprintf (stderr, "Directory %s from /etc/oss.conf cannot " "be used!\n", name); goto oexit; } fclose (f); return name; } } fprintf (stderr, "OSSLIBDIR not set in /etc/oss.conf, using default " "/usr/lib/oss\n"); oexit: fclose (f); oexit2: snprintf (name, sizeof (name), "/usr/lib/oss"); return name; } static void load_license (const char *fname) { struct stat st; char cmd[2*OSSLIBDIRLEN]; int n; if (stat (fname, &st) == -1) return; /* Doesn't exist */ if (stat ("/usr/sbin/osslic", &st) == -1) return; /* No osslic utility in the system. No need to install license. */ n = snprintf (cmd, sizeof (cmd), "/usr/sbin/osslic -q %s/%s", osslibdir, fname); if (n >= sizeof (cmd) || n < 0) return; if (((n = system (cmd)) == -1)) fprintf (stderr, "Cannot run osslic!\n"); } static void load_devlist (const char *fname, int is_3rdparty) { FILE *f; char line[256], *p, rfname[2*OSSLIBDIRLEN]; char *driver, *key, *name; snprintf (rfname, sizeof (rfname), "%s/%s", osslibdir, fname); if ((f = fopen (rfname, "r")) == NULL) { perror (rfname); exit (-1); } while (fgets (line, sizeof (line), f) != NULL) { p = line; while (*p) { if (*p == '#' || *p == '\n') *p = '\0'; p++; } /* Drivers with upper case names are unsupported ones */ if ((*line >= 'A' && *line <= 'Z') || (*line == '\0')) continue; driver = line; p = line; while (*p && *p != '\t' && *p != ' ') p++; if (*p) *p++ = 0; key = p; while (*p && *p != '\t') p++; if (*p) *p++ = 0; name = p; if (verbose > 1) printf ("device=%s, name=%s, driver=%s\n", key, name, driver); if (ndrivers >= MAX_DRIVERS) { printf ("Too many drivers defined in drivers.list\n"); exit (-1); } drivers[ndrivers].key = strdup (key); drivers[ndrivers].driver = strdup (driver); drivers[ndrivers].name = strdup (name); drivers[ndrivers].is_3rdparty = is_3rdparty; drivers[ndrivers].detected = 0; ndrivers++; } fclose (f); } static int add_drv (const char * id, int pass) { int i; for (i = 0; i < ndrivers; i++) { if (strcmp (id, drivers[i].key) == 0) { if (verbose > 0) printf ("Detected %s\n", drivers[i].name); drivers[i].detected = 1; drivers[i].pass = pass; return 1; } } return 0; } static unsigned short get_uint16 (unsigned char *p) { return *p + (p[1] << 8); } static int decode_descriptor (unsigned char *d, int desclen) { switch (d[1]) { case 0x04: if (d[5] == 1) // Audio return 1; break; } return 0; } static int is_audio (unsigned char *desc, int datalen) { int l, pos, desclen; if (desc[0] == 9 && desc[1] == 2) /* Config descriptor */ { l = get_uint16 (desc + 2); if (datalen > l) datalen = l; } pos = 0; while (pos < datalen) { unsigned char *d; desclen = desc[pos]; if (desclen < 2 || desclen > (datalen - pos)) { return 0; } d = &desc[pos]; if (decode_descriptor (d, desclen)) return 1; pos += desclen; } return 0; } static void usb_checkdevice (char *fname) { int fd, l; unsigned char buf[4096]; char tmp[64]; int vendor, device; if ((fd = open (fname, O_RDONLY, 0)) == -1) { perror (fname); return; } if ((l = read (fd, buf, sizeof (buf))) == -1) { perror (fname); return; } close (fd); if (l < 12 || buf[1] != 1) return; vendor = buf[8] | (buf[9] << 8); device = buf[10] | (buf[11] << 8); sprintf (tmp, "usb%x,%x", vendor, device); if (add_drv (tmp, USB_PASS)) return; sprintf (tmp, "usbif%x,%x", vendor, device); if (add_drv (tmp, USB_PASS)) return; if (is_audio (buf, l)) /* USB audio class */ { if (add_drv ("usbif,class1", USB_PASS)) return; } } static void usb_scandir (char *dirname) { char path[PATH_MAX]; DIR *dr; struct dirent *de; struct stat st; if ((dr = opendir (dirname)) == NULL) { return; } while ((de = readdir (dr)) != NULL) { if (de->d_name[0] < '0' || de->d_name[0] > '9') /* Ignore non-numeric names */ continue; snprintf (path, sizeof (path), "%s/%s", dirname, de->d_name); if (stat (path, &st) == -1) continue; if (S_ISDIR (st.st_mode)) { usb_scandir (path); continue; } usb_checkdevice (path); } closedir (dr); } static void usb_detect (void) { struct stat st; #if 0 char path[OSSLIBDIRLEN + 25]; sprintf (path, "%s/modules/oss_usb.o", osslibdir); if (stat (path, &st) == -1) /* USB module not available */ { fprintf (stderr, "USB module not available, aborting USB detect\n"); return; } #endif if (stat ("/dev/bus/usb", &st) != -1) { usb_ok = 1; usb_scandir ("/dev/bus/usb"); } else if (stat ("/proc/bus/usb", &st) != -1) { usb_ok = 1; usb_scandir ("/proc/bus/usb"); } } static void pci_checkdevice (char *path) { unsigned char buf[256]; char id[32]; int fd; int vendor, device; int subvendor, subdevice; if ((fd = open (path, O_RDONLY, 0)) == -1) { perror (path); return; } if (read (fd, buf, sizeof (buf)) != sizeof (buf)) { perror (path); close (fd); return; } close (fd); vendor = buf[0] | (buf[1] << 8); device = buf[2] | (buf[3] << 8); subvendor = buf[0x2c] | (buf[0x2d] << 8); subdevice = buf[0x2e] | (buf[0x2f] << 8); sprintf (id, "pcs%x,%x", subvendor, subdevice); if (add_drv (id, PCI_PASS)) return; sprintf (id, "pci%x,%x", vendor, device); add_drv (id, PCI_PASS); } static void pci_detect (char *dirname) { char path[PATH_MAX]; DIR *dr; struct dirent *de; struct stat st; if (dirname == NULL) { dirname = "/proc/bus/pci"; } if ((dr = opendir (dirname)) == NULL) { return; } while ((de = readdir (dr)) != NULL) { if (de->d_name[0] < '0' || de->d_name[0] > '9') /* Ignore non-numeric names */ continue; snprintf (path, sizeof (path), "%s/%s", dirname, de->d_name); if (stat (path, &st) == -1) continue; if (S_ISDIR (st.st_mode)) { pci_detect (path); continue; } pci_checkdevice (path); } closedir (dr); } static int remove_devlinks (const char * dirname) { char path[PATH_MAX]; DIR * dr; struct dirent * de; struct stat st; if ((dr = opendir (dirname)) == NULL) { if (errno == ENONET) return 0; perror ("opendir"); return -1; } while ((de = readdir (dr)) != NULL) { if ((!strcmp (de->d_name, ".")) || (!strcmp (de->d_name, ".."))) continue; snprintf (path, sizeof (path), "%s/%s", dirname, de->d_name); if ((stat (path, &st) != -1) && (S_ISDIR (st.st_mode))) remove_devlinks (path); else { if (verbose > 2) fprintf (stderr, "Removing %s\n", path); unlink (path); } } closedir (dr); if (verbose > 2) fprintf (stderr, "Removing %s\n", path); if (rmdir (dirname) == -1) { fprintf (stderr, "Couldn't remove %s\n", path); return -1; } return 0; } static void create_devlinks (mode_t node_m) { FILE *f; char line[256], tmp[300], *p, *s; mode_t perm; int minor, major; if ((f = fopen ("/proc/opensound/devfiles", "r")) == NULL) { perror ("/proc/opensound/devfiles"); fprintf (stderr, "Cannot connect to the OSS kernel module.\n"); fprintf (stderr, "Perhaps you need to execute 'soundon' to load OSS\n"); exit (-1); } remove_devlinks ("/dev/oss"); perm = umask (0); mkdir ("/dev/oss", 0755); while (fgets (line, sizeof (line), f) != NULL) { char dev[64] = "/dev/"; s = strchr (line, '\n'); if (s) *s = '\0'; if (sscanf (line, "%s %d %d", dev + 5, &major, &minor) != 3) { fprintf (stderr, "Syntax error in /proc/opensound/devfiles\n"); fprintf (stderr, "%s\n", line); exit (-1); } /* * Check if the device is located in a subdirectory (say /dev/oss/sblive0/pcm0). */ strcpy (tmp, dev); s = tmp + 5; p = s; while (*s) { if (*s == '/') p = s; s++; } if (*p == '/') { *p = 0; /* Extract the directory name part */ mkdir (tmp, 0755); } unlink (dev); if (verbose) printf ("mknod %s c %d %d -m %o\n", dev, major, minor, node_m); if (mknod (dev, node_m, makedev (major, minor)) == -1) perror (dev); } umask (perm); fclose (f); } static drvlist_t * prepend_drvlist (const char * name) { drvlist_t * dp; dp = malloc (sizeof (drvlist_t)); if (dp == NULL) { fprintf (stderr, "Can't allocate memory!\n"); exit (-1); } dp->drv_name = name; dp->next = drvl; return dp; } int main (int argc, char *argv[]) { char instfname[2*OSSLIBDIRLEN], *p; int i, do_license = 0, make_devs = 0, pass; mode_t node_m = S_IFCHR | 0666; struct stat st; FILE *f; while ((i = getopt(argc, argv, "L:a:dilm:uv")) != EOF) switch (i) { case 'v': verbose++; break; case 'd': make_devs = 1; break; case 'i': drvl = prepend_drvlist ("oss_imux"); break; case 'u': drvl = prepend_drvlist ("oss_userdev"); break; case 'a': drvl = prepend_drvlist (optarg); break; case 'l': do_license = 1; break; case 'L': osslibdir = optarg; break; case 'm': p = optarg; node_m = 0; while ((*p >= '0') && (*p <= '7')) node_m = node_m * 8 + *p++ - '0'; if ((*p) || (node_m & ~(S_IRWXU|S_IRWXG|S_IRWXO))) { fprintf (stderr, "Invalid permissions: %s\n", optarg); exit(1); } node_m |= S_IFCHR; break; default: fprintf (stderr, "%s: bad usage\n", argv[0]); exit (-1); } if (osslibdir == NULL) osslibdir = get_mapname (); if (make_devs == 1) { create_devlinks (node_m); exit (0); } if (do_license == 1) { load_license ("etc/license.asc"); exit (0); } load_devlist ("etc/devices.list", 0); if (stat ("/etc/oss_3rdparty", &st) != -1) load_devlist ("/etc/oss_3rdparty", 1); pci_detect (NULL); usb_detect (); if (usb_ok) { if (verbose) printf ("USB support available in the system, adding USB driver\n"); add_drv ("usbif,class1", USB_PASS); } else if (verbose) printf ("No USB support detected in the system - skipping USB\n"); while (drvl != NULL) { drvlist_t * d = drvl; add_drv (drvl->drv_name, PSEUDO_PASS); drvl = drvl->next; free (d); } snprintf (instfname, sizeof (instfname), "%s/%s", osslibdir, "etc/installed_drivers"); if ((f = fopen (instfname, "w")) == NULL) { perror (instfname); exit (-1); } for (pass = 0; pass < MAX_PASS; pass++) for (i = 0; i < ndrivers; i++) if (drivers[i].pass == pass && drivers[i].detected) { fprintf (f, "%s #%s\n", drivers[i].driver, drivers[i].name); } fclose (f); exit (0); }