minix/drivers/audio/framework/audio_fw.c
Cristiano Giuffrida cb176df60f New RS and new signal handling for system processes.
UPDATING INFO:
20100317:
        /usr/src/etc/system.conf updated to ignore default kernel calls: copy
        it (or merge it) to /etc/system.conf.
        The hello driver (/dev/hello) added to the distribution:
        # cd /usr/src/commands/scripts && make clean install
        # cd /dev && MAKEDEV hello

KERNEL CHANGES:
- Generic signal handling support. The kernel no longer assumes PM as a signal
manager for every process. The signal manager of a given process can now be
specified in its privilege slot. When a signal has to be delivered, the kernel
performs the lookup and forwards the signal to the appropriate signal manager.
PM is the default signal manager for user processes, RS is the default signal
manager for system processes. To enable ptrace()ing for system processes, it
is sufficient to change the default signal manager to PM. This will temporarily
disable crash recovery, though.
- sys_exit() is now split into sys_exit() (i.e. exit() for system processes,
which generates a self-termination signal), and sys_clear() (i.e. used by PM
to ask the kernel to clear a process slot when a process exits).
- Added a new kernel call (i.e. sys_update()) to swap two process slots and
implement live update.

PM CHANGES:
- Posix signal handling is no longer allowed for system processes. System
signals are split into two fixed categories: termination and non-termination
signals. When a non-termination signaled is processed, PM transforms the signal
into an IPC message and delivers the message to the system process. When a
termination signal is processed, PM terminates the process.
- PM no longer assumes itself as the signal manager for system processes. It now
makes sure that every system signal goes through the kernel before being
actually processes. The kernel will then dispatch the signal to the appropriate
signal manager which may or may not be PM.

SYSLIB CHANGES:
- Simplified SEF init and LU callbacks.
- Added additional predefined SEF callbacks to debug crash recovery and
live update.
- Fixed a temporary ack in the SEF init protocol. SEF init reply is now
completely synchronous.
- Added SEF signal event type to provide a uniform interface for system
processes to deal with signals. A sef_cb_signal_handler() callback is
available for system processes to handle every received signal. A
sef_cb_signal_manager() callback is used by signal managers to process
system signals on behalf of the kernel.
- Fixed a few bugs with memory mapping and DS.

VM CHANGES:
- Page faults and memory requests coming from the kernel are now implemented
using signals.
- Added a new VM call to swap two process slots and implement live update.
- The call is used by RS at update time and in turn invokes the kernel call
sys_update().

RS CHANGES:
- RS has been reworked with a better functional decomposition.
- Better kernel call masks. com.h now defines the set of very basic kernel calls
every system service is allowed to use. This makes system.conf simpler and
easier to maintain. In addition, this guarantees a higher level of isolation
for system libraries that use one or more kernel calls internally (e.g. printf).
- RS is the default signal manager for system processes. By default, RS
intercepts every signal delivered to every system process. This makes crash
recovery possible before bringing PM and friends in the loop.
- RS now supports fast rollback when something goes wrong while initializing
the new version during a live update.
- Live update is now implemented by keeping the two versions side-by-side and
swapping the process slots when the old version is ready to update.
- Crash recovery is now implemented by keeping the two versions side-by-side
and cleaning up the old version only when the recovery process is complete.

DS CHANGES:
- Fixed a bug when the process doing ds_publish() or ds_delete() is not known
by DS.
- Fixed the completely broken support for strings. String publishing is now
implemented in the system library and simply wraps publishing of memory ranges.
Ideally, we should adopt a similar approach for other data types as well.
- Test suite fixed.

DRIVER CHANGES:
- The hello driver has been added to the Minix distribution to demonstrate basic
live update and crash recovery functionalities.
- Other drivers have been adapted to conform the new SEF interface.
2010-03-17 01:15:29 +00:00

1060 lines
30 KiB
C

/* Best viewed with tabsize 4
*
* This file contains a standard driver for audio devices.
* It supports double dma buffering and can be configured to use
* extra buffer space beside the dma buffer.
* This driver also support sub devices, which can be independently
* opened and closed.
*
* The driver supports the following operations:
*
* m_type DEVICE IO_ENDPT COUNT POSITION ADRRESS
* -----------------------------------------------------------------
* | DEV_OPEN | device | proc nr | | | |
* |-------------+---------+---------+---------+---------+---------|
* | DEV_CLOSE | device | proc nr | | | |
* |-------------+---------+---------+---------+---------+---------|
* | DEV_READ_S | device | proc nr | bytes | | buf ptr |
* |-------------+---------+---------+---------+---------+---------|
* | DEV_WRITE_S | device | proc nr | bytes | | buf ptr |
* |-------------+---------+---------+---------+---------+---------|
* | DEV_IOCTL_S | device | proc nr |func code| | buf ptr |
* |-------------+---------+---------+---------+---------+---------|
* | DEV_STATUS | | | | | |
* |-------------+---------+---------+---------+---------+---------|
* | HARD_INT | | | | | |
* |-------------+---------+---------+---------+---------+---------|
* | SIG_STOP | | | | | |
* -----------------------------------------------------------------
*
* The file contains one entry point:
*
* main: main entry when driver is brought up
*
* October 2007 Updated audio framework to work with mplayer, added
* savecopies (Pieter Hijma)
* February 2006 Updated audio framework,
* changed driver-framework relation (Peter Boonstoppel)
* November 2005 Created generic DMA driver framework (Laurens Bronwasser)
* August 24 2005 Ported audio driver to user space
* (only audio playback) (Peter Boonstoppel)
* May 20 1995 SB16 Driver: Michel R. Prevenier
*/
#include "audio_fw.h"
#include <minix/endpoint.h>
#include <minix/ds.h>
FORWARD _PROTOTYPE( int msg_open, (int minor_dev_nr) );
FORWARD _PROTOTYPE( int msg_close, (int minor_dev_nr) );
FORWARD _PROTOTYPE( int msg_ioctl, (message *m_ptr) );
FORWARD _PROTOTYPE( void msg_write, (message *m_ptr) );
FORWARD _PROTOTYPE( void msg_read, (message *m_ptr) );
FORWARD _PROTOTYPE( void msg_hardware, (void) );
FORWARD _PROTOTYPE( void msg_status, (message *m_ptr) );
FORWARD _PROTOTYPE( int init_driver, (void) );
FORWARD _PROTOTYPE( int open_sub_dev, (int sub_dev_nr, int operation) );
FORWARD _PROTOTYPE( int close_sub_dev, (int sub_dev_nr) );
FORWARD _PROTOTYPE( void handle_int_write,(int sub_dev_nr) );
FORWARD _PROTOTYPE( void handle_int_read,(int sub_dev_nr) );
FORWARD _PROTOTYPE( void data_to_user, (sub_dev_t *sub_dev_ptr) );
FORWARD _PROTOTYPE( void data_from_user, (sub_dev_t *sub_dev_ptr) );
FORWARD _PROTOTYPE( int init_buffers, (sub_dev_t *sub_dev_ptr) );
FORWARD _PROTOTYPE( int get_started, (sub_dev_t *sub_dev_ptr) );
FORWARD _PROTOTYPE( void reply,(int code, int replyee, int process,int status));
FORWARD _PROTOTYPE( int io_ctl_length, (int io_request) );
FORWARD _PROTOTYPE( special_file_t* get_special_file, (int minor_dev_nr) );
FORWARD _PROTOTYPE( void tell_dev, (vir_bytes buf, size_t size, int pci_bus,
int pci_dev, int pci_func) );
PRIVATE char io_ctl_buf[_IOCPARM_MASK];
PRIVATE int irq_hook_id = 0; /* id of irq hook at the kernel */
PRIVATE int irq_hook_set = FALSE;
PRIVATE int device_available = 0;/*todo*/
/* SEF functions and variables. */
FORWARD _PROTOTYPE( void sef_local_startup, (void) );
FORWARD _PROTOTYPE( int sef_cb_init_fresh, (int type, sef_init_info_t *info) );
FORWARD _PROTOTYPE( void sef_cb_signal_handler, (int signo) );
EXTERN _PROTOTYPE( int sef_cb_lu_prepare, (int state) );
EXTERN _PROTOTYPE( int sef_cb_lu_state_isvalid, (int state) );
EXTERN _PROTOTYPE( void sef_cb_lu_state_dump, (int state) );
PUBLIC int is_status_msg_expected = FALSE;
PUBLIC void main(void)
{
int r, caller;
message mess, repl_mess;
/* SEF local startup. */
sef_local_startup();
/* Here is the main loop of the dma driver. It waits for a message,
carries it out, and sends a reply. */
while(1) {
sef_receive(ANY, &mess);
caller = mess.m_source;
/* Now carry out the work. First check for notifications. */
if (is_notify(mess.m_type)) {
switch (_ENDPOINT_P(mess.m_source)) {
case HARDWARE:
msg_hardware();
break;
default:
dprint("%s: %d uncaught notify!\n",
drv.DriverName, mess.m_type);
}
/* get next message */
continue;
}
/* Normal messages. */
switch(mess.m_type) {
case DEV_OPEN:
/* open the special file ( = parameter) */
r = msg_open(mess.DEVICE);
repl_mess.m_type = DEV_REVIVE;
repl_mess.REP_ENDPT = mess.IO_ENDPT;
repl_mess.REP_STATUS = r;
send(caller, &repl_mess);
continue;
case DEV_CLOSE:
/* close the special file ( = parameter) */
r = msg_close(mess.DEVICE);
repl_mess.m_type = DEV_CLOSE_REPL;
repl_mess.REP_ENDPT = mess.IO_ENDPT;
repl_mess.REP_STATUS = r;
send(caller, &repl_mess);
continue;
case DEV_IOCTL_S:
r = msg_ioctl(&mess);
if (r != SUSPEND)
{
repl_mess.m_type = DEV_REVIVE;
repl_mess.REP_ENDPT = mess.IO_ENDPT;
repl_mess.REP_IO_GRANT =
(unsigned)mess.IO_GRANT;
repl_mess.REP_STATUS = r;
send(caller, &repl_mess);
}
continue;
case DEV_READ_S:
msg_read(&mess); continue; /* don't reply */
case DEV_WRITE_S:
msg_write(&mess); continue; /* don't reply */
case DEV_STATUS:
msg_status(&mess);continue; /* don't reply */
case DEV_REOPEN:
/* reopen the special file ( = parameter) */
r = msg_open(mess.DEVICE);
repl_mess.m_type = DEV_REOPEN_REPL;
repl_mess.REP_ENDPT = mess.IO_ENDPT;
repl_mess.REP_STATUS = r;
send(caller, &repl_mess);
continue;
default:
dprint("%s: %d uncaught msg!\n",
drv.DriverName, mess.m_type);
continue;
}
/* Should not be here. Just continue. */
}
}
/*===========================================================================*
* sef_local_startup *
*===========================================================================*/
PRIVATE void sef_local_startup()
{
/* Register init callbacks. */
sef_setcb_init_fresh(sef_cb_init_fresh);
sef_setcb_init_lu(sef_cb_init_fresh);
sef_setcb_init_restart(sef_cb_init_fresh);
/* Register live update callbacks. */
sef_setcb_lu_prepare(sef_cb_lu_prepare);
sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid);
sef_setcb_lu_state_dump(sef_cb_lu_state_dump);
/* Register signal callbacks. */
sef_setcb_signal_handler(sef_cb_signal_handler);
/* Let SEF perform startup. */
sef_startup();
}
/*===========================================================================*
* sef_cb_init_fresh *
*===========================================================================*/
PRIVATE int sef_cb_init_fresh(int type, sef_init_info_t *info)
{
/* Initialize the audio driver framework. */
return init_driver();
}
PRIVATE int init_driver(void) {
u32_t i; char irq;
static int executed = 0;
sub_dev_t* sub_dev_ptr;
/* init variables, get dma buffers */
for (i = 0; i < drv.NrOfSubDevices; i++) {
sub_dev_ptr = &sub_dev[i];
sub_dev_ptr->Opened = FALSE;
sub_dev_ptr->DmaBusy = FALSE;
sub_dev_ptr->DmaMode = NO_DMA;
sub_dev_ptr->DmaReadNext = 0;
sub_dev_ptr->DmaFillNext = 0;
sub_dev_ptr->DmaLength = 0;
sub_dev_ptr->BufReadNext = 0;
sub_dev_ptr->BufFillNext = 0;
sub_dev_ptr->RevivePending = FALSE;
sub_dev_ptr->OutOfData = FALSE;
sub_dev_ptr->Nr = i;
}
/* initialize hardware*/
if (drv_init_hw() != OK) {
error("%s: Could not initialize hardware\n", drv.DriverName, 0);
return EIO;
}
/* get irq from device driver...*/
if (drv_get_irq(&irq) != OK) {
error("%s: init driver couldn't get IRQ", drv.DriverName, i);
return EIO;
}
/* todo: execute the rest of this function only once
we don't want to set irq policy twice */
if (executed) return OK;
executed = TRUE;
/* ...and register interrupt vector */
if ((i=sys_irqsetpolicy(irq, 0, &irq_hook_id )) != OK){
error("%s: init driver couldn't set IRQ policy", drv.DriverName, i);
return EIO;
}
irq_hook_set = TRUE; /* now signal handler knows it must unregister policy*/
return OK;
}
/*===========================================================================*
* sef_cb_signal_handler *
*===========================================================================*/
PRIVATE void sef_cb_signal_handler(int signo)
{
int i;
char irq;
/* Only check for termination signal, ignore anything else. */
if (signo != SIGTERM) return;
for (i = 0; i < drv.NrOfSubDevices; i++) {
drv_stop(i); /* stop all sub devices */
}
if (irq_hook_set) {
if (sys_irqdisable(&irq_hook_id) != OK) {
error("Could not disable IRQ\n");
}
/* get irq from device driver*/
if (drv_get_irq(&irq) != OK) {
error("Msg SIG_STOP Couldn't get IRQ");
}
/* remove the policy */
if (sys_irqrmpolicy(&irq_hook_id) != OK) {
error("%s: Could not disable IRQ\n",drv.DriverName);
}
}
}
PRIVATE int msg_open (int minor_dev_nr) {
int r, read_chan, write_chan, io_ctl;
special_file_t* special_file_ptr;
dprint("%s: msg_open() special file %d\n", drv.DriverName, minor_dev_nr);
special_file_ptr = get_special_file(minor_dev_nr);
if(special_file_ptr == NULL) {
return EIO;
}
read_chan = special_file_ptr->read_chan;
write_chan = special_file_ptr->write_chan;
io_ctl = special_file_ptr->io_ctl;
if (read_chan==NO_CHANNEL && write_chan==NO_CHANNEL && io_ctl==NO_CHANNEL) {
error("%s: No channel specified for minor device!\n",
drv.DriverName, minor_dev_nr);
return EIO;
}
if (read_chan == write_chan && read_chan != NO_CHANNEL) {
error("%s: Read and write channels are equal!\n",
drv.DriverName, minor_dev_nr);
return EIO;
}
/* init driver */
if (!device_available) {
if (init_driver() != OK) {
error("%s: Couldn't init driver!\n", drv.DriverName, minor_dev_nr);
return EIO;
} else {
device_available = TRUE;
}
}
/* open the sub devices specified in the interface header file */
if (write_chan != NO_CHANNEL) {
/* open sub device for writing */
if (open_sub_dev(write_chan, DEV_WRITE_S) != OK) return EIO;
}
if (read_chan != NO_CHANNEL) {
if (open_sub_dev(read_chan, DEV_READ_S) != OK) return EIO;
}
if (read_chan == io_ctl || write_chan == io_ctl) {
/* io_ctl is already opened because it's the same as read or write */
return OK; /* we're done */
}
if (io_ctl != NO_CHANNEL) { /* Ioctl differs from read/write channels, */
r = open_sub_dev(io_ctl, NO_DMA); /* open it explicitly */
if (r != OK) return EIO;
}
return OK;
}
PRIVATE int open_sub_dev(int sub_dev_nr, int dma_mode) {
sub_dev_t* sub_dev_ptr;
sub_dev_ptr = &sub_dev[sub_dev_nr];
/* Only one open at a time per sub device */
if (sub_dev_ptr->Opened) {
error("%s: Sub device %d is already opened\n",
drv.DriverName, sub_dev_nr);
return EBUSY;
}
if (sub_dev_ptr->DmaBusy) {
error("%s: Sub device %d is still busy\n", drv.DriverName, sub_dev_nr);
return EBUSY;
}
/* Setup variables */
sub_dev_ptr->Opened = TRUE;
sub_dev_ptr->DmaReadNext = 0;
sub_dev_ptr->DmaFillNext = 0;
sub_dev_ptr->DmaLength = 0;
sub_dev_ptr->DmaMode = dma_mode;
sub_dev_ptr->BufReadNext = 0;
sub_dev_ptr->BufFillNext = 0;
sub_dev_ptr->BufLength = 0;
sub_dev_ptr->RevivePending = FALSE;
sub_dev_ptr->OutOfData = TRUE;
/* arrange DMA */
if (dma_mode != NO_DMA) { /* sub device uses DMA */
/* allocate dma buffer and extra buffer space
and configure sub device for dma */
if (init_buffers(sub_dev_ptr) != OK ) return EIO;
}
return OK;
}
PRIVATE int msg_close(int minor_dev_nr) {
int r, read_chan, write_chan, io_ctl;
special_file_t* special_file_ptr;
dprint("%s: msg_close() minor device %d\n", drv.DriverName, minor_dev_nr);
special_file_ptr = get_special_file(minor_dev_nr);
if(special_file_ptr == NULL) {
return EIO;
}
read_chan = special_file_ptr->read_chan;
write_chan = special_file_ptr->write_chan;
io_ctl = special_file_ptr->io_ctl;
r= OK;
/* close all sub devices */
if (write_chan != NO_CHANNEL) {
if (close_sub_dev(write_chan) != OK) r = EIO;
}
if (read_chan != NO_CHANNEL) {
if (close_sub_dev(read_chan) != OK) r = EIO;
}
if (read_chan == io_ctl || write_chan == io_ctl) {
/* io_ctl is already closed because it's the same as read or write */
return r; /* we're done */
}
/* ioctl differs from read/write channels... */
if (io_ctl != NO_CHANNEL) {
if (close_sub_dev(io_ctl) != OK) r = EIO; /* ...close it explicitly */
}
return r;
}
PRIVATE int close_sub_dev(int sub_dev_nr) {
sub_dev_t *sub_dev_ptr;
sub_dev_ptr = &sub_dev[sub_dev_nr];
if (sub_dev_ptr->DmaMode == DEV_WRITE_S && !sub_dev_ptr->OutOfData) {
/* do nothing, still data in buffers that has to be transferred */
sub_dev_ptr->Opened = FALSE; /* keep DMA busy */
return OK;
}
if (sub_dev_ptr->DmaMode == NO_DMA) {
/* do nothing, there is no dma going on */
sub_dev_ptr->Opened = FALSE;
return OK;
}
sub_dev_ptr->Opened = FALSE;
sub_dev_ptr->DmaBusy = FALSE;
/* stop the device */
drv_stop(sub_dev_ptr->Nr);
/* free the buffers */
free(sub_dev_ptr->DmaBuf);
free(sub_dev_ptr->ExtraBuf);
return OK;
}
PRIVATE int msg_ioctl(message *m_ptr)
{
int status, len, chan;
sub_dev_t *sub_dev_ptr;
special_file_t* special_file_ptr;
dprint("%s: msg_ioctl() device %d\n", drv.DriverName, m_ptr->DEVICE);
special_file_ptr = get_special_file(m_ptr->DEVICE);
if(special_file_ptr == NULL) {
return EIO;
}
chan = special_file_ptr->io_ctl;
if (chan == NO_CHANNEL) {
error("%s: No io control channel specified!\n", drv.DriverName);
return EIO;
}
/* get pointer to sub device data */
sub_dev_ptr = &sub_dev[chan];
if(!sub_dev_ptr->Opened) {
error("%s: io control impossible - not opened!\n", drv.DriverName);
return EIO;
}
/* this is a hack...todo: may we intercept reset calls? */
/*
if(m_ptr->REQUEST == DSPIORESET) {
device_available = FALSE;
}
*/
if (m_ptr->REQUEST & _IOC_IN) { /* if there is data for us, copy it */
len = io_ctl_length(m_ptr->REQUEST);
if(sys_safecopyfrom(m_ptr->IO_ENDPT,
(vir_bytes)m_ptr->ADDRESS, 0,
(vir_bytes)io_ctl_buf, len, D) != OK) {
printf("%s:%d: safecopyfrom failed\n", __FILE__, __LINE__);
}
}
/* all ioctl's are passed to the device specific part of the driver */
status = drv_io_ctl(m_ptr->REQUEST, (void *)io_ctl_buf, &len, chan);
/* _IOC_OUT bit -> user expects data */
if (status == OK && m_ptr->REQUEST & _IOC_OUT) {
/* copy result back to user */
if(sys_safecopyto(m_ptr->IO_ENDPT, (vir_bytes)m_ptr->ADDRESS, 0,
(vir_bytes)io_ctl_buf, len, D) != OK) {
printf("%s:%d: safecopyto failed\n", __FILE__, __LINE__);
}
}
return status;
}
PRIVATE void msg_write(message *m_ptr)
{
int chan; sub_dev_t *sub_dev_ptr;
special_file_t* special_file_ptr;
dprint("%s: msg_write() device %d\n", drv.DriverName, m_ptr->DEVICE);
special_file_ptr = get_special_file(m_ptr->DEVICE);
chan = special_file_ptr->write_chan;
if (chan == NO_CHANNEL) {
error("%s: No write channel specified!\n", drv.DriverName);
reply(DEV_REVIVE, m_ptr->m_source, m_ptr->IO_ENDPT, EIO);
return;
}
/* get pointer to sub device data */
sub_dev_ptr = &sub_dev[chan];
if (!sub_dev_ptr->DmaBusy) { /* get fragment size on first write */
if (drv_get_frag_size(&(sub_dev_ptr->FragSize), sub_dev_ptr->Nr) != OK){
error("%s; Failed to get fragment size!\n", drv.DriverName, 0);
return;
}
}
if(m_ptr->COUNT != sub_dev_ptr->FragSize) {
error("Fragment size does not match user's buffer length\n");
reply(DEV_REVIVE, m_ptr->m_source, m_ptr->IO_ENDPT, EINVAL);
return;
}
/* if we are busy with something else than writing, return EBUSY */
if(sub_dev_ptr->DmaBusy && sub_dev_ptr->DmaMode != DEV_WRITE_S) {
error("Already busy with something else then writing\n");
reply(DEV_REVIVE, m_ptr->m_source, m_ptr->IO_ENDPT, EBUSY);
return;
}
sub_dev_ptr->RevivePending = TRUE;
sub_dev_ptr->ReviveProcNr = m_ptr->IO_ENDPT;
sub_dev_ptr->ReviveGrant = (cp_grant_id_t) m_ptr->ADDRESS;
sub_dev_ptr->NotifyProcNr = m_ptr->m_source;
data_from_user(sub_dev_ptr);
if(!sub_dev_ptr->DmaBusy) { /* Dma tranfer not yet started */
dprint("starting audio device\n");
get_started(sub_dev_ptr);
sub_dev_ptr->DmaMode = DEV_WRITE_S; /* Dma mode is writing */
}
}
PRIVATE void msg_read(message *m_ptr)
{
int chan; sub_dev_t *sub_dev_ptr;
special_file_t* special_file_ptr;
dprint("%s: msg_read() device %d\n", drv.DriverName, m_ptr->DEVICE);
special_file_ptr = get_special_file(m_ptr->DEVICE);
chan = special_file_ptr->read_chan;
if (chan == NO_CHANNEL) {
error("%s: No read channel specified!\n", drv.DriverName);
reply(DEV_REVIVE, m_ptr->m_source, m_ptr->IO_ENDPT, EIO);
return;
}
/* get pointer to sub device data */
sub_dev_ptr = &sub_dev[chan];
if (!sub_dev_ptr->DmaBusy) { /* get fragment size on first read */
if (drv_get_frag_size(&(sub_dev_ptr->FragSize), sub_dev_ptr->Nr) != OK){
error("%s: Could not retrieve fragment size!\n", drv.DriverName);
reply(DEV_REVIVE, m_ptr->m_source, m_ptr->IO_ENDPT, EIO);
return;
}
}
if(m_ptr->COUNT != sub_dev_ptr->FragSize) {
reply(DEV_REVIVE, m_ptr->m_source, m_ptr->IO_ENDPT, EINVAL);
error("fragment size does not match message size\n");
return;
}
/* if we are busy with something else than reading, reply EBUSY */
if(sub_dev_ptr->DmaBusy && sub_dev_ptr->DmaMode != DEV_READ_S) {
reply(DEV_REVIVE, m_ptr->m_source, m_ptr->IO_ENDPT, EBUSY);
return;
}
sub_dev_ptr->RevivePending = TRUE;
sub_dev_ptr->ReviveProcNr = m_ptr->IO_ENDPT;
sub_dev_ptr->ReviveGrant = (cp_grant_id_t) m_ptr->ADDRESS;
sub_dev_ptr->NotifyProcNr = m_ptr->m_source;
if(!sub_dev_ptr->DmaBusy) { /* Dma tranfer not yet started */
get_started(sub_dev_ptr);
sub_dev_ptr->DmaMode = DEV_READ_S; /* Dma mode is reading */
return; /* no need to get data from DMA buffer at this point */
}
/* check if data is available and possibly fill user's buffer */
data_to_user(sub_dev_ptr);
}
PRIVATE void msg_hardware(void) {
u32_t i;
dprint("%s: handling hardware message\n", drv.DriverName);
/* while we have an interrupt */
while ( drv_int_sum()) {
/* loop over all sub devices */
for ( i = 0; i < drv.NrOfSubDevices; i++) {
/* if interrupt from sub device and Dma transfer
was actually busy, take care of business */
if( drv_int(i) && sub_dev[i].DmaBusy ) {
if (sub_dev[i].DmaMode == DEV_WRITE_S) handle_int_write(i);
if (sub_dev[i].DmaMode == DEV_READ_S) handle_int_read(i);
}
}
}
/* As IRQ_REENABLE is not on in sys_irqsetpolicy, we must
* re-enable out interrupt after every interrupt.
*/
if ((sys_irqenable(&irq_hook_id)) != OK) {
error("%s: msg_hardware: Couldn't enable IRQ\n", drv.DriverName);
}
}
PRIVATE void msg_status(message *m_ptr)
{
int i;
dprint("got a status message\n");
for (i = 0; i < drv.NrOfSubDevices; i++) {
if(sub_dev[i].ReadyToRevive)
{
m_ptr->m_type = DEV_REVIVE; /* build message */
m_ptr->REP_ENDPT = sub_dev[i].ReviveProcNr;
m_ptr->REP_IO_GRANT = sub_dev[i].ReviveGrant;
m_ptr->REP_STATUS = sub_dev[i].ReviveStatus;
send(m_ptr->m_source, m_ptr); /* send the message */
/* reset variables */
sub_dev[i].ReadyToRevive = FALSE;
sub_dev[i].RevivePending = 0;
is_status_msg_expected = TRUE;
return; /* stop after one mess,
file system will get back for other processes */
}
}
m_ptr->m_type = DEV_NO_STATUS;
m_ptr->REP_STATUS = 0;
send(m_ptr->m_source, m_ptr); /* send DEV_NO_STATUS message */
is_status_msg_expected = FALSE;
}
/* handle interrupt for specified sub device; DmaMode == DEV_WRITE_S*/
PRIVATE void handle_int_write(int sub_dev_nr)
{
sub_dev_t *sub_dev_ptr;
sub_dev_ptr = &sub_dev[sub_dev_nr];
dprint("Finished playing dma[%d] ", sub_dev_ptr->DmaReadNext);
sub_dev_ptr->DmaReadNext =
(sub_dev_ptr->DmaReadNext + 1) % sub_dev_ptr->NrOfDmaFragments;
sub_dev_ptr->DmaLength -= 1;
if (sub_dev_ptr->BufLength != 0) { /* Data in extra buf, copy to Dma buf */
dprint(" buf[%d] -> dma[%d] ",
sub_dev_ptr->BufReadNext, sub_dev_ptr->DmaFillNext);
memcpy(sub_dev_ptr->DmaPtr +
sub_dev_ptr->DmaFillNext * sub_dev_ptr->FragSize,
sub_dev_ptr->ExtraBuf +
sub_dev_ptr->BufReadNext * sub_dev_ptr->FragSize,
sub_dev_ptr->FragSize);
sub_dev_ptr->BufReadNext =
(sub_dev_ptr->BufReadNext + 1) % sub_dev_ptr->NrOfExtraBuffers;
sub_dev_ptr->DmaFillNext =
(sub_dev_ptr->DmaFillNext + 1) % sub_dev_ptr->NrOfDmaFragments;
sub_dev_ptr->BufLength -= 1;
sub_dev_ptr->DmaLength += 1;
}
/* space became available, possibly copy new data from user */
data_from_user(sub_dev_ptr);
if(sub_dev_ptr->DmaLength == 0) { /* Dma buffer empty, stop Dma transfer */
sub_dev_ptr->OutOfData = TRUE; /* we're out of data */
dprint("No more work...!\n");
if (!sub_dev_ptr->Opened) {
close_sub_dev(sub_dev_ptr->Nr);
dprint("Stopping sub device %d\n", sub_dev_ptr->Nr);
return;
}
dprint("Pausing sub device %d\n",sub_dev_ptr->Nr);
drv_pause(sub_dev_ptr->Nr);
return;
}
dprint("\n");
/* confirm and reenable interrupt from this sub dev */
drv_reenable_int(sub_dev_nr);
#if 0
/* reenable irq_hook*/
if (sys_irqenable(&irq_hook_id != OK) {
error("%s Couldn't enable IRQ\n", drv.DriverName);
}
#endif
}
/* handle interrupt for specified sub device; DmaMode == DEV_READ_S */
PRIVATE void handle_int_read(int sub_dev_nr)
{
sub_dev_t *sub_dev_ptr;
sub_dev_ptr = &sub_dev[sub_dev_nr];
dprint("Device filled dma[%d]\n", sub_dev_ptr->DmaFillNext);
sub_dev_ptr->DmaLength += 1;
sub_dev_ptr->DmaFillNext =
(sub_dev_ptr->DmaFillNext + 1) % sub_dev_ptr->NrOfDmaFragments;
/* possibly copy data to user (if it is waiting for us) */
data_to_user(sub_dev_ptr);
if (sub_dev_ptr->DmaLength == sub_dev_ptr->NrOfDmaFragments) {
/* if dma buffer full */
if (sub_dev_ptr->BufLength == sub_dev_ptr->NrOfExtraBuffers) {
error("All buffers full, we have a problem.\n");
drv_stop(sub_dev_nr); /* stop the sub device */
sub_dev_ptr->DmaBusy = FALSE;
sub_dev_ptr->ReviveStatus = 0; /* no data for user,
this is a sad story */
sub_dev_ptr->ReadyToRevive = TRUE; /* wake user up */
return;
}
else { /* dma full, still room in extra buf;
copy from dma to extra buf */
dprint("dma full: going to copy buf[%d] <- dma[%d]\n",
sub_dev_ptr->BufFillNext, sub_dev_ptr->DmaReadNext);
memcpy(sub_dev_ptr->ExtraBuf +
sub_dev_ptr->BufFillNext * sub_dev_ptr->FragSize,
sub_dev_ptr->DmaPtr +
sub_dev_ptr->DmaReadNext * sub_dev_ptr->FragSize,
sub_dev_ptr->FragSize);
sub_dev_ptr->DmaLength -= 1;
sub_dev_ptr->DmaReadNext =
(sub_dev_ptr->DmaReadNext + 1) % sub_dev_ptr->NrOfDmaFragments;
sub_dev_ptr->BufFillNext =
(sub_dev_ptr->BufFillNext + 1) % sub_dev_ptr->NrOfExtraBuffers;
}
}
/* confirm interrupt, and reenable interrupt from this sub dev*/
drv_reenable_int(sub_dev_ptr->Nr);
#if 0
/* reenable irq_hook*/
if (sys_irqenable(&irq_hook_id) != OK) {
error("%s: Couldn't reenable IRQ", drv.DriverName);
}
#endif
}
PRIVATE int get_started(sub_dev_t *sub_dev_ptr) {
u32_t i;
/* enable interrupt messages from MINIX */
if ((i=sys_irqenable(&irq_hook_id)) != OK) {
error("%s: Couldn't enable IRQs: error code %u",drv.DriverName, (unsigned int) i);
return EIO;
}
/* let the lower part of the driver start the device */
if (drv_start(sub_dev_ptr->Nr, sub_dev_ptr->DmaMode) != OK) {
error("%s: Could not start device %d\n",
drv.DriverName, sub_dev_ptr->Nr);
}
sub_dev_ptr->DmaBusy = TRUE; /* Dma is busy from now on */
sub_dev_ptr->DmaReadNext = 0;
return OK;
}
PRIVATE void data_from_user(sub_dev_t *subdev)
{
int r;
message m;
if (subdev->DmaLength == subdev->NrOfDmaFragments &&
subdev->BufLength == subdev->NrOfExtraBuffers) return;/* no space */
if (!subdev->RevivePending) return; /* no new data waiting to be copied */
if (subdev->RevivePending &&
subdev->ReadyToRevive) return; /* we already got this data */
if (subdev->DmaLength < subdev->NrOfDmaFragments) { /* room in dma buf */
sys_safecopyfrom(subdev->ReviveProcNr,
(vir_bytes)subdev->ReviveGrant, 0,
(vir_bytes)subdev->DmaPtr +
subdev->DmaFillNext * subdev->FragSize,
(phys_bytes)subdev->FragSize, D);
dprint(" user -> dma[%d]\n", subdev->DmaFillNext);
subdev->DmaLength += 1;
subdev->DmaFillNext =
(subdev->DmaFillNext + 1) % subdev->NrOfDmaFragments;
} else { /* room in extra buf */
sys_safecopyfrom(subdev->ReviveProcNr,
(vir_bytes)subdev->ReviveGrant, 0,
(vir_bytes)subdev->ExtraBuf +
subdev->BufFillNext * subdev->FragSize,
(phys_bytes)subdev->FragSize, D);
dprint(" user -> buf[%d]\n", subdev->BufFillNext);
subdev->BufLength += 1;
subdev->BufFillNext =
(subdev->BufFillNext + 1) % subdev->NrOfExtraBuffers;
}
if(subdev->OutOfData) { /* if device paused (because of lack of data) */
subdev->OutOfData = FALSE;
drv_reenable_int(subdev->Nr);
/* reenable irq_hook*/
if ((sys_irqenable(&irq_hook_id)) != OK) {
error("%s: Couldn't enable IRQ", drv.DriverName);
}
drv_resume(subdev->Nr); /* resume resume the sub device */
}
subdev->ReviveStatus = subdev->FragSize;
subdev->ReadyToRevive = TRUE;
m.m_type = DEV_REVIVE; /* build message */
m.REP_ENDPT = subdev->ReviveProcNr;
m.REP_IO_GRANT = subdev->ReviveGrant;
m.REP_STATUS = subdev->ReviveStatus;
r= send(subdev->NotifyProcNr, &m); /* send the message */
if (r != OK)
{
printf("audio_fw: send to %d failed: %d\n",
subdev->NotifyProcNr, r);
}
/* reset variables */
subdev->ReadyToRevive = FALSE;
subdev->RevivePending = 0;
}
PRIVATE void data_to_user(sub_dev_t *sub_dev_ptr)
{
int r;
message m;
if (!sub_dev_ptr->RevivePending) return; /* nobody is wating for data */
if (sub_dev_ptr->ReadyToRevive) return;/* we already filled user's buffer */
if (sub_dev_ptr->BufLength == 0 && sub_dev_ptr->DmaLength == 0) return;
/* no data for user */
if(sub_dev_ptr->BufLength != 0) { /* data in extra buffer available */
sys_safecopyto(sub_dev_ptr->ReviveProcNr,
(vir_bytes)sub_dev_ptr->ReviveGrant,
0, (vir_bytes)sub_dev_ptr->ExtraBuf +
sub_dev_ptr->BufReadNext * sub_dev_ptr->FragSize,
(phys_bytes)sub_dev_ptr->FragSize, D);
dprint(" copied buf[%d] to user\n", sub_dev_ptr->BufReadNext);
/* adjust the buffer status variables */
sub_dev_ptr->BufReadNext =
(sub_dev_ptr->BufReadNext + 1) % sub_dev_ptr->NrOfExtraBuffers;
sub_dev_ptr->BufLength -= 1;
} else { /* extra buf empty, but data in dma buf*/
sys_safecopyto(
sub_dev_ptr->ReviveProcNr,
(vir_bytes)sub_dev_ptr->ReviveGrant, 0,
(vir_bytes)sub_dev_ptr->DmaPtr +
sub_dev_ptr->DmaReadNext * sub_dev_ptr->FragSize,
(phys_bytes)sub_dev_ptr->FragSize, D);
dprint(" copied dma[%d] to user\n", sub_dev_ptr->DmaReadNext);
/* adjust the buffer status variables */
sub_dev_ptr->DmaReadNext =
(sub_dev_ptr->DmaReadNext + 1) % sub_dev_ptr->NrOfDmaFragments;
sub_dev_ptr->DmaLength -= 1;
}
sub_dev_ptr->ReviveStatus = sub_dev_ptr->FragSize;
sub_dev_ptr->ReadyToRevive = TRUE;
/* drv_status will send REVIVE mess to FS*/
m.m_type = DEV_REVIVE; /* build message */
m.REP_ENDPT = sub_dev_ptr->ReviveProcNr;
m.REP_IO_GRANT = sub_dev_ptr->ReviveGrant;
m.REP_STATUS = sub_dev_ptr->ReviveStatus;
r= send(sub_dev_ptr->NotifyProcNr, &m); /* send the message */
if (r != OK)
{
printf("audio_fw: send to %d failed: %d\n",
sub_dev_ptr->NotifyProcNr, r);
}
/* reset variables */
sub_dev_ptr->ReadyToRevive = FALSE;
sub_dev_ptr->RevivePending = 0;
}
PRIVATE int init_buffers(sub_dev_t *sub_dev_ptr)
{
#if (CHIP == INTEL)
char *base;
size_t size;
unsigned left;
u32_t i;
phys_bytes ph;
/* allocate dma buffer space */
size= sub_dev_ptr->DmaSize + 64 * 1024;
base= alloc_contig(size, AC_ALIGN4K, &ph);
if (!base) {
error("%s: failed to allocate dma buffer for a channel\n",
drv.DriverName);
return EIO;
}
sub_dev_ptr->DmaBuf= base;
tell_dev((vir_bytes)base, size, 0, 0, 0);
/* allocate extra buffer space */
if (!(sub_dev_ptr->ExtraBuf = malloc(sub_dev_ptr->NrOfExtraBuffers *
sub_dev_ptr->DmaSize /
sub_dev_ptr->NrOfDmaFragments))) {
error("%s failed to allocate extra buffer for a channel\n",
drv.DriverName);
return EIO;
}
sub_dev_ptr->DmaPtr = sub_dev_ptr->DmaBuf;
i = sys_umap(SELF, D,
(vir_bytes) sub_dev_ptr->DmaBuf,
(phys_bytes) sizeof(sub_dev_ptr->DmaBuf),
&(sub_dev_ptr->DmaPhys));
if (i != OK) {
return EIO;
}
if ((left = dma_bytes_left(sub_dev_ptr->DmaPhys)) <
sub_dev_ptr->DmaSize) {
/* First half of buffer crosses a 64K boundary,
* can't DMA into that */
sub_dev_ptr->DmaPtr += left;
sub_dev_ptr->DmaPhys += left;
}
/* write the physical dma address and size to the device */
drv_set_dma(sub_dev_ptr->DmaPhys,
sub_dev_ptr->DmaSize, sub_dev_ptr->Nr);
return OK;
#else /* CHIP != INTEL */
error("%s: init_buffer() failed, CHIP != INTEL", drv.DriverName);
return EIO;
#endif /* CHIP == INTEL */
}
PRIVATE void reply(int code, int replyee, int process, int status) {
message m;
m.m_type = code; /* DEV_REVIVE */
m.REP_STATUS = status; /* result of device operation */
m.REP_ENDPT = process; /* which user made the request */
send(replyee, &m);
}
PRIVATE int io_ctl_length(int io_request) {
io_request >>= 16;
return io_request & _IOCPARM_MASK;
}
PRIVATE special_file_t* get_special_file(int minor_dev_nr) {
int i;
for(i = 0; i < drv.NrOfSpecialFiles; i++) {
if(special_file[i].minor_dev_nr == minor_dev_nr) {
return &special_file[i];
}
}
error("%s: No subdevice specified for minor device %d!\n",
drv.DriverName, minor_dev_nr);
return NULL;
}
PRIVATE void tell_dev(buf, size, pci_bus, pci_dev, pci_func)
vir_bytes buf;
size_t size;
int pci_bus;
int pci_dev;
int pci_func;
{
int r;
endpoint_t dev_e;
u32_t u32;
message m;
r= ds_retrieve_label_num("amddev", &u32);
if (r != OK)
{
#if 0
printf("tell_dev: ds_retrieve_label_num failed for 'amddev': %d\n",
r);
#endif
return;
}
dev_e= u32;
m.m_type= IOMMU_MAP;
m.m2_i1= pci_bus;
m.m2_i2= pci_dev;
m.m2_i3= pci_func;
m.m2_l1= buf;
m.m2_l2= size;
r= sendrec(dev_e, &m);
if (r != OK)
{
printf("tell_dev: sendrec to %d failed: %d\n", dev_e, r);
return;
}
if (m.m_type != OK)
{
printf("tell_dev: dma map request failed: %d\n", m.m_type);
return;
}
}