static int snd_ctl_hw_read(snd_ctl_t *handle, snd_ctl_event_t *event)
{
	snd_ctl_hw_t *hw = handle->private_data;
	ssize_t res = read(hw->fd, event, sizeof(*event));
	if (res <= 0)
		return -errno;
	if (CHECK_SANITY(res != sizeof(*event))) {
		SNDMSG("snd_ctl_hw_read: read size error (req:%d, got:%d)\n",
		       sizeof(*event), res);
		return -EINVAL;
	}
	return 1;
}
int snd_pcm_munmap(snd_pcm_t *pcm)
{
	int err;
	unsigned int c;
	assert(pcm);
	if (CHECK_SANITY(! pcm->mmap_channels)) {
		SNDMSG("Not mmapped");
		return -ENXIO;
	}
	if (pcm->mmap_shadow)
		return pcm->ops->munmap(pcm);
	for (c = 0; c < pcm->channels; ++c) {
		snd_pcm_channel_info_t *i = &pcm->mmap_channels[c];
		unsigned int c1;
		size_t size = i->first + i->step * (pcm->buffer_size - 1) + pcm->sample_bits;
		if (!i->addr)
			continue;
		for (c1 = c + 1; c1 < pcm->channels; ++c1) {
			snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
			size_t s;
			if (i1->addr != i->addr)
				continue;
			i1->addr = NULL;
			s = i1->first + i1->step * (pcm->buffer_size - 1) + pcm->sample_bits;
			if (s > size)
				size = s;
		}
		size = (size + 7) / 8;
		size = page_align(size);
		switch (i->type) {
		case SND_PCM_AREA_MMAP:
			err = munmap(i->addr, size);
			if (err < 0) {
				SYSERR("mmap failed");
				return -errno;
			}
			errno = 0;
			break;
		case SND_PCM_AREA_SHM:
			if (i->u.shm.area) {
				snd_shm_area_destroy(i->u.shm.area);
				i->u.shm.area = NULL;
				if (pcm->access == SND_PCM_ACCESS_MMAP_INTERLEAVED ||
				    pcm->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
					unsigned int c1;
					for (c1 = c + 1; c1 < pcm->channels; c1++) {
						snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
						if (i1->u.shm.area) {
							snd_shm_area_destroy(i1->u.shm.area);
							i1->u.shm.area = NULL;
						}
					}
				}
			}
			break;
		case SND_PCM_AREA_LOCAL:
			free(i->addr);
			break;
		default:
			assert(0);
		}
		i->addr = NULL;
	}
	err = pcm->ops->munmap(pcm);
	if (err < 0)
		return err;
	free(pcm->mmap_channels);
	free(pcm->running_areas);
	pcm->mmap_channels = NULL;
	pcm->running_areas = NULL;
	return 0;
}
int snd_pcm_mmap(snd_pcm_t *pcm)
{
	int err;
	unsigned int c;
	assert(pcm);
	if (CHECK_SANITY(! pcm->setup)) {
		SNDMSG("PCM not set up");
		return -EIO;
	}
	if (CHECK_SANITY(pcm->mmap_channels || pcm->running_areas)) {
		SNDMSG("Already mmapped");
		return -EBUSY;
	}
	err = pcm->ops->mmap(pcm);
	if (err < 0)
		return err;
	if (pcm->mmap_shadow)
		return 0;
	pcm->mmap_channels = calloc(pcm->channels, sizeof(pcm->mmap_channels[0]));
	if (!pcm->mmap_channels)
		return -ENOMEM;
	pcm->running_areas = calloc(pcm->channels, sizeof(pcm->running_areas[0]));
	if (!pcm->running_areas) {
		free(pcm->mmap_channels);
		pcm->mmap_channels = NULL;
		return -ENOMEM;
	}
	for (c = 0; c < pcm->channels; ++c) {
		snd_pcm_channel_info_t *i = &pcm->mmap_channels[c];
		i->channel = c;
		err = snd_pcm_channel_info(pcm, i);
		if (err < 0)
			return err;
	}
	for (c = 0; c < pcm->channels; ++c) {
		snd_pcm_channel_info_t *i = &pcm->mmap_channels[c];
		snd_pcm_channel_area_t *a = &pcm->running_areas[c];
		char *ptr;
		size_t size;
		unsigned int c1;
		if (i->addr) {
        		a->addr = i->addr;
        		a->first = i->first;
        		a->step = i->step;
		        continue;
                }
                size = i->first + i->step * (pcm->buffer_size - 1) + pcm->sample_bits;
		for (c1 = c + 1; c1 < pcm->channels; ++c1) {
			snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
			size_t s;
			if (i1->type != i->type)
				continue;
			switch (i1->type) {
			case SND_PCM_AREA_MMAP:
				if (i1->u.mmap.fd != i->u.mmap.fd ||
				    i1->u.mmap.offset != i->u.mmap.offset)
					continue;
				break;
			case SND_PCM_AREA_SHM:
				if (i1->u.shm.shmid != i->u.shm.shmid)
					continue;
				break;
			case SND_PCM_AREA_LOCAL:
				break;
			default:
				assert(0);
			}
			s = i1->first + i1->step * (pcm->buffer_size - 1) + pcm->sample_bits;
			if (s > size)
				size = s;
		}
		size = (size + 7) / 8;
		size = page_align(size);
		switch (i->type) {
		case SND_PCM_AREA_MMAP:
			ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, i->u.mmap.fd, i->u.mmap.offset);
			if (ptr == MAP_FAILED) {
				SYSERR("mmap failed");
				return -errno;
			}
			i->addr = ptr;
			break;
		case SND_PCM_AREA_SHM:
			if (i->u.shm.shmid < 0) {
				int id;
				/* FIXME: safer permission? */
				id = shmget(IPC_PRIVATE, size, 0666);
				if (id < 0) {
					SYSERR("shmget failed");
					return -errno;
				}
				i->u.shm.shmid = id;
				ptr = shmat(i->u.shm.shmid, 0, 0);
				if (ptr == (void *) -1) {
					SYSERR("shmat failed");
					return -errno;
				}
				/* automatically remove segment if not used */
				if (shmctl(id, IPC_RMID, NULL) < 0){
					SYSERR("shmctl mark remove failed");
					return -errno;
				}
				i->u.shm.area = snd_shm_area_create(id, ptr);
				if (i->u.shm.area == NULL) {
					SYSERR("snd_shm_area_create failed");
					return -ENOMEM;
				}
				if (pcm->access == SND_PCM_ACCESS_MMAP_INTERLEAVED ||
				    pcm->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
					unsigned int c1;
					for (c1 = c + 1; c1 < pcm->channels; c1++) {
						snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
						if (i1->u.shm.shmid < 0) {
							i1->u.shm.shmid = id;
							i1->u.shm.area = snd_shm_area_share(i->u.shm.area);
						}
					}
				}
			} else {
				ptr = shmat(i->u.shm.shmid, 0, 0);
				if (ptr == (void*) -1) {
					SYSERR("shmat failed");
					return -errno;
				}
			}
			i->addr = ptr;
			break;
		case SND_PCM_AREA_LOCAL:
			ptr = malloc(size);
			if (ptr == NULL) {
				SYSERR("malloc failed");
				return -errno;
			}
			i->addr = ptr;
			break;
		default:
			assert(0);
		}
		for (c1 = c + 1; c1 < pcm->channels; ++c1) {
			snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
			if (i1->type != i->type)
				continue;
			switch (i1->type) {
			case SND_PCM_AREA_MMAP:
				if (i1->u.mmap.fd != i->u.mmap.fd ||
                                    i1->u.mmap.offset != i->u.mmap.offset)
					continue;
				break;
			case SND_PCM_AREA_SHM:
				if (i1->u.shm.shmid != i->u.shm.shmid)
					continue;
				/* follow thru */
			case SND_PCM_AREA_LOCAL:
				if (pcm->access != SND_PCM_ACCESS_MMAP_INTERLEAVED &&
				    pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED)
				        continue;
				break;
			default:
				assert(0);
			}
			i1->addr = i->addr;
		}
		a->addr = i->addr;
		a->first = i->first;
		a->step = i->step;
	}
	return 0;
}
int snd_ctl_hw_open(snd_ctl_t **handle, const char *name, int card, int mode)
{
	int fd, ver;
	char filename[sizeof(SNDRV_FILE_CONTROL) + 10];
	int fmode;
	snd_ctl_t *ctl;
	snd_ctl_hw_t *hw;
	int err;

	*handle = NULL;	

	if (CHECK_SANITY(card < 0 || card >= 32)) {
		SNDMSG("Invalid card index %d", card);
		return -EINVAL;
	}
	sprintf(filename, SNDRV_FILE_CONTROL, card);
	if (mode & SND_CTL_READONLY)
		fmode = O_RDONLY;
	else
		fmode = O_RDWR;
	if (mode & SND_CTL_NONBLOCK)
		fmode |= O_NONBLOCK;
	if (mode & SND_CTL_ASYNC)
		fmode |= O_ASYNC;
	fd = snd_open_device(filename, fmode);
	if (fd < 0) {
		snd_card_load(card);
		fd = snd_open_device(filename, fmode);
		if (fd < 0)
			return -errno;
	}
	if (ioctl(fd, SNDRV_CTL_IOCTL_PVERSION, &ver) < 0) {
		err = -errno;
		close(fd);
		return err;
	}
	if (SNDRV_PROTOCOL_INCOMPATIBLE(ver, SNDRV_CTL_VERSION_MAX)) {
		close(fd);
		return -SND_ERROR_INCOMPATIBLE_VERSION;
	}
	hw = calloc(1, sizeof(snd_ctl_hw_t));
	if (hw == NULL) {
		close(fd);
		return -ENOMEM;
	}
	hw->card = card;
	hw->fd = fd;
	hw->protocol = ver;

	err = snd_ctl_new(&ctl, SND_CTL_TYPE_HW, name);
	if (err < 0) {
		close(fd);
		free(hw);
		return err;
	}
	ctl->ops = &snd_ctl_hw_ops;
	ctl->private_data = hw;
	ctl->poll_fd = fd;
	*handle = ctl;
	return 0;
}