dev_pcmem.c revision 5c43205d04ce612cd2940e062ab48365a5b3d42a
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* the file is volmgt's interface to possible PCMCIA slots under Solaris
*
* this version talks to pcmciad directly through a named pipe (rather than
* pcmciad using SIGHUP to talk to vold)
*
* the name of the named pipe to use is gotten from the config file
* "use pcmpipe ..." line
*/
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <thread.h>
#include <synch.h>
#include "vold.h"
/*
* thread stack size
*/
/*
* size of buf read from named pipe
*/
#define PCMEM_PBUF_SZ 100
/*
* this is the seperator between params sent down the pipe
*/
#define PCMEM_PIPE_SEP ','
/*
* path of the named pipe that PCMCIA uses
*/
#define PCMEM_PIPE_PATH "/var/run/pcmcia/pcram"
/*
* symname used from proutine that reads from pipe as symname for pcmem
* cards
*
* must match symname on "use pcmem ..." line in config file
*/
#define PCMEM_SYMNAME "pcmem%d"
/*
* path to devices directories
*/
#define PCMEM_RDSK_PATH "/dev/rdsk"
#define PCMEM_DSK_PATH "/dev/dsk"
#define PCMEM_DEVICES_DIR "/devices/pcmcia"
#define PCMEM_NAMEPROTO_DEFD "%sd0s%d"
#define PCMEM_BASEPART 2
#define PCMEM_NAMEPROTO "%ss%d"
#define PCMEM_MODE_BUF_LEN 10
#define PCMEM_PIPE_ACTION "insert"
#define PCMEM_OVERLAP_TIMEOUT_SECS 15
static int pcmem_getfd(dev_t);
static void pcmem_devmap(vol_t *, int, int);
static void pcmem_close(char *, dev_t);
static bool_t pcmem_testpath(char *);
static int pcmem_check(struct devs *);
static void pcmem_eject(struct devs *);
extern bool_t support_nomedia;
static struct devsw pcmemdevsw = {
pcmem_use, /* d_use -- use a device */
pcmem_error, /* d_error -- handle an error */
pcmem_getfd, /* d_getfd -- return open fd for drive */
NULL, /* d_poll -- poll device */
pcmem_devmap, /* d_devmap -- map device */
pcmem_close, /* d_close -- end use of device */
pcmem_eject, /* d_eject -- eject media */
NULL, /* d_find */
pcmem_check, /* d_check -- volcheck device */
PCMEM_MTYPE, /* d_mtype -- media type */
DRIVE_CLASS, /* d_dtype -- drive type */
(ulong_t)0, /* d_flags -- default flags */
(uid_t)0, /* d_uid -- default user id */
(gid_t)0, /* d_gid -- default group id */
(mode_t)0, /* d_mode -- default file mode */
pcmem_testpath, /* d_test -- test for a named pipe */
pcmem_remount /* d_remount -- find the new default fd */
};
struct mem_priv {
char *mem_blockpath; /* block device for pcmem */
char *mem_rawpath; /* character device for pcmem */
int mem_tid; /* thread id of watcher thread */
int mem_fd; /* real file descriptor */
};
/*
* the struct passed to pcmp_thread
*/
struct pcmp_priv {
char *pcmp_symname;
};
/*
* this routine is called by dso_load() right after this library is loaded,
* i.e. a line of the form "use pcmem ..." has been seen
*
* put any library initialization here
*/
dev_init(void)
{
static void pipe_thread(void);
/* register our routines */
/* ensure a thread exists to watch the named pipe */
pipe_thread();
return (TRUE);
}
static int started = 0; /* Pipe thread running? */
/*
* start up a thread that watches the named pipe
*/
static void
pipe_thread(void)
{
static char *pcmp_dirname(char *);
static void pcmp_thread(struct pcmp_priv *);
void *pcmp_thr_stk;
char *p = PCMEM_PIPE_PATH;
char *bn;
if (started)
return;
/*
* we don't do an open for the named pipe, since pcmp_thread() does
* that. Instead, we just stat the device and make sure it's
* there and is a reasonable type
*/
/* just take a path if they hand it to us. */
debug(5,
"pipe_thread: stat failed for: %s; %m\n",
p);
goto dun;
}
/*
* the pipe doesn't exist -- create it (and the directory
* it goes in if needed)
*/
"makepath failed for: %s\n", bn);
goto dun;
}
}
p);
goto dun;
}
p);
goto dun;
}
} else {
goto dun;
}
}
/* create an empty pcmp-private data structure */
/* stick some good stuff in the device hierarchy */
/* create the pcmp priv struct (so we only pass one param to thread) */
goto dun;
}
started = 1;
#ifdef DEBUG
#endif
dun:
}
static void
{
static bool_t parse_pipe_str(char *, char *, char *, int *);
extern int vold_running;
extern cond_t running_cv;
extern mutex_t running_mutex;
int sym_num;
register char *cp;
int ret_val;
char mode_buf[PCMEM_MODE_BUF_LEN];
/* ensure that the main loop is ready */
(void) mutex_lock(&running_mutex);
while (vold_running == 0) {
}
(void) mutex_unlock(&running_mutex);
mem->mem_rawpath);
/* ensure the pipe is open */
}
mem->mem_rawpath);
goto errout;
}
/*CONSTCOND*/
for (;;) {
/*
* this blocks until pcmciad sends us a line
*/
#ifdef DEBUG
#endif
if (*cp == '\n') {
break;
}
break;
}
cp++;
goto open_again;
}
if (ret_val < 0) {
mem->mem_rawpath);
goto open_again;
}
if (ret_val == 0) {
mem->mem_rawpath);
goto open_again;
}
pipe_buf);
/* oh oh -- not in correct format! */
continue;
}
/* set up the symname to use */
/*LINTED var_fmt*/
/* set up the mode */
"0%o", DEFAULT_MODE);
/* register this device */
/* may be already managed */
debug(1,
"pcmp_thread: warning: dev_use on \"%s\" failed (already managed?)\n",
dev_buf);
}
} else {
gettext("pcmem: unknown message from pcmciad\n"));
}
}
/*NOTREACHED*/
/* this thread is exiting! */
}
started = 0;
}
/*
* return the directory part of a path (like dirname(1))
*
* assume we have a well formed path, i.e. no "/abc/" or "/"
*/
static char *
pcmp_dirname(char *path)
{
char *s;
register char *cpi;
register char *cpo;
/* find the last slash in the path */
return (NULL);
}
}
return (res);
}
/*
* called because a line of the form "use pcmem ... PATH_REGEX ..." was
* found in the config file, and a path has been found that
* matches PATH_REGEX
*
* return true if the path points at a pcmem device,
* as it's understood by this code
*/
static bool_t
pcmem_testpath(char *p)
{
int fd = -1;
/* stat it (if we can) */
goto mem_dun;
}
/* see if device already being used */
return (TRUE);
} else {
return (FALSE);
}
}
/* make sure our path is a raw device */
goto mem_dun;
}
/* try to open it */
goto mem_dun;
}
/* check for memird octl handling */
rp);
goto mem_dun;
}
goto mem_dun;
}
/* we suceeded */
/* clean up if needed */
if (fd >= 0) {
}
if (rp) {
}
return (res);
}
static bool_t
{
/*
* There's no need to find the new default file
* descriptor for a PCMCIA card after it has been
* formatted and repartitioned. The default
* file descriptor for a PCMCIA card never changes.
*/
/*
* We need to confound lint while creating a dummy
* function that does nothing with its argument.
*/
return (TRUE);
} else {
return (FALSE);
}
}
/*
* called for one of two reasons:
* (1) the pcmcia daemon has sent a message down the named pipe
* saying to use a particular device, or
* (2) pcmem_testpath() has found a suitable pcmem pathname
*/
/*ARGSUSED*/
static bool_t
{
static void pcmem_thread(struct devs *);
void *pcmem_thr_stk;
char full_path[MAXPATHLEN];
char *s;
char *p;
char path_save[MAXPATHLEN];
int fd;
/*
* we don't do an open for the pcmem because it returns ENODEV
* if there isn't a device there. Instead, we just stat the
* device and make sure it's there and is a reasonable type
*/
/* just take a path if they hand it to us */
/*
* We expect a path of the form:
* /dev/{rdsk, dsk}/c#t#
* We fill in the rest.
*/
goto dun;
}
/* the device was found */
} else {
/*
* got a good path -- truncate the "slice" part of the name
*
* XXX: assume all PCMEM pathnames end in "sN"
*/
/* the full path didn't have a "s" in it! */
path);
goto dun;
}
/* XXX: should make sure a slice number follows */
*s = NULLC;
#ifdef DEBUG
#endif
}
/* check to see if this guy is already configured */
/*
* Remove "nomedia" node if support has changed.
* By sending a HUP signal to vold, dev_use()
* will call here.
*/
}
/*
* Create "nomedia" node if support has changed.
*/
}
return (TRUE);
} else {
return (FALSE);
}
}
/* ensure it's a block or char spcl device */
"pcmem: %s not block or char device (mode 0x%x)\n"),
goto dun;
}
/* create an empty memory-private data structure */
/* save a copy of the pathname */
/* stick some good stuff in the device hierarchy */
/* got a raw path (i.e. "rdsk" in it) */
/* save a pointer to the raw vv-node */
/* create the name for rawpath */
/* get the block path now from the raw one */
/* skip past "rdsk/" */
if ((p = strchr(s, '/')) != 0) {
p++;
"%s/%s", PCMEM_DSK_PATH, p);
} else {
/* no slash after rdsk? */
debug(1,
"pcmem_use: using malformed pathname (no '/') \"%s\"\n",
/* what else can we do? */
}
/* get the block vv-node */
/* create the name for blockpath */
/* he gave us the block path (i.e. "dsk" in it) */
/* save pointer to block vv-node */
/* create the name for blockpath */
/* get the chr patch now from the block one */
/* skip past "dsk/" */
if ((p = strchr(s, '/')) != 0) {
p++;
"%s/%s", PCMEM_RDSK_PATH, p);
} else {
/* no slash after "dsk"? */
debug(1,
"pcmem_use: using malformed pathname (no '/') \"%s\"\n",
path);
/* what else can we do? */
}
/* save a pointer to the raw vv-node */
/* create the name for rawpath */
} else {
debug(1,
"pcmem_use: malformed pathname (no '[r]dsk') \"%s\"\n",
path);
goto dun;
}
mem->mem_rawpath);
goto dun;
}
/*
* Get socket number information which is from
* dki_unit in DKIOCINFO structure
*/
goto dun;
}
goto dun;
}
/* create a thread to manage this device */
goto dun;
}
#ifdef DEBUG
#endif
dun:
return (res);
}
/*ARGSUSED*/
static void
{
/*
* don't really need to lock mem here, since rawpath doesn't
* change much
*/
}
static int
{
int fd;
return (fd);
}
/*ARGSUSED*/
static bool_t
{
return (TRUE);
}
/*
* State that must be cleaned up:
* name in the name space
* the "dp"
* any pointers to the media
* eject any existing media
* the priv structure
*/
/*
* XXX: a bug still exists here. we have a thread polling on this
* XXX: device in the kernel, we need to get rid of this also.
* XXX: since we're going to move the waiter thread up to the
* XXX: user level, it'll be easier to kill off as part of the
* XXX: cleanup of the device private data.
*/
static void
{
char namebuf[MAXPATHLEN];
namebuf);
return;
}
} else {
}
} else {
}
return;
}
/* get our private data */
/* take care of the listner thread */
/* apparently we have to kick it out of the cond_wait */
/*
* XXX: NOTE: we still have the pipe thread running
*/
/*
* there is no longer a listener thread, so the mutex
* no longer needs to be acquired
*/
/* if there is a volume inserted in this device ... */
if (dev_present(dp)) {
/*
* clean up the name space and the device maps
* to remove references to any volume that might
* be in the device right now
*
* this crap with the flags is to keep the
* "poll" from being relaunched by this function
*
* yes, its a hack and there should be a better way.
*/
} else {
}
return;
}
}
/* clean up the names in the name space */
/* close the file descriptor we're holding open */
/* free the private data we've allocated */
if (mem->mem_blockpath) {
}
if (mem->mem_rawpath) {
}
/* free the dp, so no one points at us anymore */
dev_freedp(dp);
}
static void
{
}
/*
* check for a PCMCIA memory card
*
* return:
* 0 if we didn't find any media
* 1 if we already knew media was there
* 2 if we found media and generated an event
*
* NOTE: this routine hogs the mem mutex for it's whole existence,
* so be sure not to call any routine that may need it (with a little
* work this could hog the mutex a lot less).
*/
static int
{
struct vioc_event vie;
extern int vold_running;
extern cond_t running_cv;
extern mutex_t running_mutex;
int rval = 0;
int ret_val = 0; /* default return value is 0 */
#ifdef DEBUG
#endif
/* ensure the vold main loop is running */
(void) mutex_lock(&running_mutex);
while (vold_running == 0) {
}
(void) mutex_unlock(&running_mutex);
/* if we know the media is there there's no need to check again */
if (mem->mem_inserted) {
ret_val = 1;
goto out;
}
/* ensure media is open */
/* an error [re]opening */
goto out;
}
}
/* try to see if media is present */
goto out;
}
#ifdef DEBUG
#endif
if ((rval & FDGC_CURRENT) == 0) {
/*
* A memory card is IN the drive
* and we didn't know therewas anything there ...
*/
}
out:
#ifdef DEBUG
#endif
return (ret_val);
}
static void
{
static void wait_til_signalled(struct devs *);
#ifdef DEBUG
static char *state_to_str(enum dkio_state);
#endif
extern int vold_running;
extern cond_t running_cv;
extern mutex_t running_mutex;
enum dkio_state mem_state;
struct vioc_event vie;
/* ensure that the main loop is ready */
(void) mutex_lock(&running_mutex);
while (vold_running == 0) {
}
(void) mutex_unlock(&running_mutex);
/* ensure the memory card is open */
goto errout;
}
/* check to make sure this is a PCMCIA memory card */
mem->mem_rawpath);
goto errout;
}
mem->mem_rawpath);
goto errout;
}
/* see how device ejects */
} else {
if (fdchar.fdd_ejectable == 0) {
mem->mem_rawpath);
}
}
/*CONSTCOND*/
while (1) {
/*
* this ioctl blocks until state changes
*/
#ifdef DEBUG
#else
mem->mem_rawpath);
#endif
/* normally we are blocked here waiting for state to change */
break;
}
continue;
}
#ifdef DEBUG
#endif
continue; /* steady state -- ignore */
}
/* prepare to create an event */
/*
* We have media in the drive -- generate
* an "insert" event
*/
/*
* [re]open failed -- could be because
* card was removed *immediately* after
* insertion
*/
break;
}
/* generate an insert event */
debug(2,
"pcmem_thread: generating INSERT event for (%d,%d)\n",
} else if (mem_state == DKIO_EJECTED) {
/*
* We have NO media in the drive (it's just
* been ejected), so generate an "eject" event
* if we already know about the ejection,
* then just continue on our happy loop
*/
#ifdef DEBUG
debug(1,
"pcmem_thread: handling EJECT state: dp=%#x, dp->dp_vol = %#x\n",
#endif
/* keep track of removal */
(void) mutex_lock(&vold_main_mutex);
if (dev_present(dp)) {
/* generate an eject event */
debug(2,
"pcmem_thread: generating EJECT event for (%d,%d)\n",
(void) mutex_unlock(&vold_main_mutex);
/* wait til ejection is done */
/* check for all okay */
if (dev_present(dp)) {
/* timeout or other problem */
debug(1,
"pcmem_thread: EJECT didn't complete\n");
continue; /* try again */
}
} else {
/*
* Create "nomedia" device node for empty
* removable media device.
*/
}
(void) mutex_unlock(&vold_main_mutex);
debug(2,
"pcmem_thread: EJECT but vol already gone\n");
}
}
}
/* this thread is exiting! */
}
/* clean up the names in the name space */
/* free the dp, so no one points at us anymore */
dev_freedp(dp);
/* XXX: should we free "mem" ? */
}
/*
* wait until signaled (or timeout occurs) that the dev is no longer
* prsent
*/
static void
{
int ret_val;
/* if dev is already done then we're already done */
if (!dev_present(dp)) {
return;
}
/* set up time delay */
debug(10,
"wait_til_signalled: waiting for ejection (%d sec timeout)\n",
ret_val);
return;
}
}
/*
* [re]open the memory card, returning whether or not it's write protected
*
* NOTE: should normally be called with the memory structure locked
*/
static bool_t
{
/*
* when a pcmem card is openend O_NDELAY and no memory card is there,
* but then a read-only card is inserted, this is needed to
* reopen the devicd
*/
#ifdef DEBUG
#endif
/* ensure it's closed to start with */
}
/* try a blocking open, in read-write mode */
/* try read-only mode */
}
}
mem->mem_rawpath);
/* if we get EAGAIN then try again -- once */
if (try_again) {
(void) sleep(1);
goto again;
}
}
} else {
}
#ifdef DEBUG
#endif
return (rdonly);
}
#ifdef DEBUG
static char *
{
static char state_buf[30];
switch (st) {
case DKIO_NONE:
break;
case DKIO_INSERTED:
break;
case DKIO_EJECTED:
break;
default:
break;
}
return (state_buf);
}
#endif /* DEBUG */
/*
* parse the line read from the pipe, in the format:
* "ACTION, PATH, SOCKET_NUM"
* e.g.:
*/
static bool_t
int *socket_num_ptr)
{
register char *cpo;
/* ensure there's a seperator */
debug(1,
"parse_pipe_str: malformed pipe str \"%s\" (no '%c' seperator)\n",
goto dun;
}
/* copy up to the seperator into the action buf */
if (*cpi == PCMEM_PIPE_SEP) {
cpi++; /* skip the seperator */
break;
}
}
/* skip white space */
break;
}
cpi++;
}
debug(1,
"parse_pipe_str: malformed pipe str \"%s\" (premature EOF)\n",
pipe_buf);
goto dun;
}
/* ensure there's a seperator */
debug(1,
"parse_pipe_str: malformed pipe str \"%s\" (no '%c' seperator)\n",
goto dun;
}
/* copy up to the seperator into the action buf */
if (*cpi == PCMEM_PIPE_SEP) {
cpi++; /* skip the seperator */
break;
}
}
/* skip white space */
break;
}
cpi++;
}
debug(1,
"parse_pipe_str: malformed pipe str \"%s\" (premature EOF)\n",
pipe_buf);
goto dun;
}
/* get the last param: socket number */
debug(1,
"parse_pipe_str: malformed pipe str \"%s\" (no socket num?)\n",
pipe_buf);
goto dun;
}
/* we have the info! */
dun:
#ifdef DEBUG
if (res) {
}
#endif
return (res);
}