patch-1.3.58 linux/drivers/sound/dmabuf.c

Next file: linux/drivers/sound/experimental.txt
Previous file: linux/drivers/sound/dev_table.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.57/linux/drivers/sound/dmabuf.c linux/drivers/sound/dmabuf.c
@@ -29,16 +29,14 @@
 
 #include "sound_config.h"
 
-#ifdef CONFIGURE_SOUNDCARD
+#if defined(CONFIG_AUDIO) || defined(CONFIG_GUS)
 
-#if !defined(EXCLUDE_AUDIO) || !defined(EXCLUDE_GUS)
-
-static struct wait_queue *in_sleeper[MAX_AUDIO_DEV] =
+static wait_handle *in_sleeper[MAX_AUDIO_DEV] =
 {NULL};
 static volatile struct snd_wait in_sleep_flag[MAX_AUDIO_DEV] =
 {
   {0}};
-static struct wait_queue *out_sleeper[MAX_AUDIO_DEV] =
+static wait_handle *out_sleeper[MAX_AUDIO_DEV] =
 {NULL};
 static volatile struct snd_wait out_sleep_flag[MAX_AUDIO_DEV] =
 {
@@ -133,6 +131,9 @@
     }
 
   bsz &= ~0x03;			/* Force size which is multiple of 4 bytes */
+#ifdef OS_DMA_ALIGN_CHECK
+  OS_DMA_ALIGN_CHECK (bsz);
+#endif
 
   n = dsp_dev->buffsize / bsz;
   if (n > MAX_SUB_BUFFERS)
@@ -158,7 +159,9 @@
       out_sleep_flag[dev].mode = WK_NONE;
     }
   else
-    in_sleep_flag[dev].mode = WK_NONE;
+    {
+      in_sleep_flag[dev].mode = WK_NONE;
+    }
 
   dmap->flags = DMA_BUSY;	/* Other flags off */
   dmap->qlen = dmap->qhead = dmap->qtail = 0;
@@ -168,6 +171,8 @@
   dmap->dma_mode = DMODE_NONE;
   dmap->mapping_flags = 0;
   dmap->neutral_byte = 0x00;
+  dmap->cfrag = -1;
+  dmap->closing = 0;
 }
 
 static int
@@ -262,6 +267,8 @@
       }
   audio_devs[dev]->open_mode = mode;
   audio_devs[dev]->go = 1;
+  in_sleep_flag[dev].mode = WK_NONE;
+  out_sleep_flag[dev].mode = WK_NONE;
 
   audio_devs[dev]->ioctl (dev, SOUND_PCM_WRITE_BITS, (ioctl_arg) 8, 1);
   audio_devs[dev]->ioctl (dev, SOUND_PCM_WRITE_CHANNELS, (ioctl_arg) 1, 1);
@@ -335,19 +342,19 @@
       save_flags (flags);
       cli ();
 
-      while (!(current->signal & ~current->blocked)
-	     && audio_devs[dev]->dmap_out->qlen)
+      while (!current_got_fatal_signal ()
+	     && audio_devs[dev]->dmap_out->qlen > 1)
 	{
 
 	  {
 	    unsigned long   tl;
 
-	    if (10 * HZ)
-	      current->timeout = tl = jiffies + (10 * HZ);
+	    if (HZ)
+	      current_set_timeout (tl = jiffies + (HZ));
 	    else
 	      tl = 0xffffffff;
 	    out_sleep_flag[dev].mode = WK_SLEEP;
-	    interruptible_sleep_on (&out_sleeper[dev]);
+	    module_interruptible_sleep_on (&out_sleeper[dev]);
 	    if (!(out_sleep_flag[dev].mode & WK_WAKEUP))
 	      {
 		if (jiffies >= tl)
@@ -372,7 +379,7 @@
       cli ();
       if (audio_devs[dev]->local_qlen)	/* Device has hidden buffers */
 	{
-	  while (!((current->signal & ~current->blocked))
+	  while (!(current_got_fatal_signal ())
 		 && audio_devs[dev]->local_qlen (dev))
 	    {
 
@@ -380,11 +387,11 @@
 		unsigned long   tl;
 
 		if (HZ)
-		  current->timeout = tl = jiffies + (HZ);
+		  current_set_timeout (tl = jiffies + (HZ));
 		else
 		  tl = 0xffffffff;
 		out_sleep_flag[dev].mode = WK_SLEEP;
-		interruptible_sleep_on (&out_sleeper[dev]);
+		module_interruptible_sleep_on (&out_sleeper[dev]);
 		if (!(out_sleep_flag[dev].mode & WK_WAKEUP))
 		  {
 		    if (jiffies >= tl)
@@ -404,7 +411,10 @@
 {
   unsigned long   flags;
 
-  if (!((current->signal & ~current->blocked))
+  audio_devs[dev]->dmap_out->closing = 1;
+  audio_devs[dev]->dmap_in->closing = 1;
+
+  if (!(current_got_fatal_signal ())
       && (audio_devs[dev]->dmap_out->dma_mode == DMODE_OUTPUT))
     {
       dma_sync (dev);
@@ -412,7 +422,6 @@
 
   save_flags (flags);
   cli ();
-  audio_devs[dev]->reset (dev);
 
   audio_devs[dev]->close (dev);
 
@@ -485,6 +494,14 @@
 
   save_flags (flags);
   cli ();
+#ifdef ALLOW_BUFFER_MAPPING
+  if (audio_devs[dev]->dmap_in->mapping_flags & DMA_MAP_MAPPED)
+    {
+      printk ("Sound: Can't read from mmapped device (1)\n");
+      return -EINVAL;
+    }
+  else
+#endif
   if (!dmap->qlen)
     {
       int             timeout;
@@ -520,11 +537,11 @@
 	unsigned long   tl;
 
 	if (timeout)
-	  current->timeout = tl = jiffies + (timeout);
+	  current_set_timeout (tl = jiffies + (timeout));
 	else
 	  tl = 0xffffffff;
 	in_sleep_flag[dev].mode = WK_SLEEP;
-	interruptible_sleep_on (&in_sleeper[dev]);
+	module_interruptible_sleep_on (&in_sleeper[dev]);
 	if (!(in_sleep_flag[dev].mode & WK_WAKEUP))
 	  {
 	    if (jiffies >= tl)
@@ -536,6 +553,7 @@
 	{
 	  printk ("Sound: DMA (input) timed out - IRQ/DRQ config error?\n");
 	  err = EIO;
+	  audio_devs[dev]->reset (dev);
 	  ;
 	}
       else
@@ -559,6 +577,14 @@
 
   int             p = dmap->counts[dmap->qhead] + c;
 
+#ifdef ALLOW_BUFFER_MAPPING
+  if (audio_devs[dev]->dmap_in->mapping_flags & DMA_MAP_MAPPED)
+    {
+      printk ("Sound: Can't read from mmapped device (2)\n");
+      return -EINVAL;
+    }
+  else
+#endif
   if (p >= dmap->fragment_size)
     {				/* This buffer is completely empty */
       dmap->counts[dmap->qhead] = 0;
@@ -623,6 +649,11 @@
   if (count < 2)
     return -EINVAL;
 
+#ifdef OS_DMA_MINBITS
+  if (bytes < OS_DMA_MINBITS)
+    bytes = OS_DMA_MINBITS;
+#endif
+
   dmap->fragment_size = (1 << bytes);
   dmap->max_fragments = count;
 
@@ -739,6 +770,10 @@
 	  if (cmd == SNDCTL_DSP_GETISPACE && audio_devs[dev]->flags & DMA_DUPLEX)
 	    dmap = dmap_in;
 
+#ifdef ALLOW_BUFFER_MAPPING
+	  if (dmap->mapping_flags & DMA_MAP_MAPPED)
+	    return -EINVAL;
+#endif
 
 	  if (!(dmap->flags & DMA_ALLOC_DONE))
 	    reorganize_buffers (dev, dmap);
@@ -758,7 +793,7 @@
 		    {
 		      int             tmp = audio_devs[dev]->local_qlen (dev);
 
-		      if (tmp & info->fragments)
+		      if (tmp && info->fragments)
 			tmp--;	/*
 				   * This buffer has been counted twice
 				 */
@@ -809,6 +844,18 @@
 	    activate_recording (dev, dmap_in);
 	  }
 
+#ifdef ALLOW_BUFFER_MAPPING
+	if ((changed & bits) & PCM_ENABLE_OUTPUT &&
+	    dmap_out->mapping_flags & DMA_MAP_MAPPED &&
+	    audio_devs[dev]->go)
+	  {
+	    if (!(dmap_out->flags & DMA_ALLOC_DONE))
+	      reorganize_buffers (dev, dmap_out);
+
+	    dmap_out->counts[dmap_out->qhead] = dmap_out->fragment_size;
+	    DMAbuf_start_output (dev, 0, dmap_out->fragment_size);
+	  }
+#endif
 
 	audio_devs[dev]->enable_bits = bits;
 	if (changed && audio_devs[dev]->trigger)
@@ -819,6 +866,16 @@
       return snd_ioctl_return ((int *) arg, audio_devs[dev]->enable_bits);
       break;
 
+    case SNDCTL_DSP_SETSYNCRO:
+
+      if (!audio_devs[dev]->trigger)
+	return -EINVAL;
+
+      audio_devs[dev]->trigger (dev, 0);
+      audio_devs[dev]->go = 0;
+      return 0;
+      break;
+
     case SNDCTL_DSP_GETIPTR:
       {
 	count_info      info;
@@ -832,6 +889,10 @@
 	info.bytes += info.ptr;
 	memcpy_tofs ((&((char *) arg)[0]), (char *) &info, sizeof (info));
 
+#ifdef ALLOW_BUFFER_MAPPING
+	if (audio_devs[dev]->dmap_in->mapping_flags & DMA_MAP_MAPPED)
+	  audio_devs[dev]->dmap_in->qlen = 0;	/* Acknowledge interrupts */
+#endif
 	restore_flags (flags);
 	return 0;
       }
@@ -850,6 +911,10 @@
 	info.bytes += info.ptr;
 	memcpy_tofs ((&((char *) arg)[0]), (char *) &info, sizeof (info));
 
+#ifdef ALLOW_BUFFER_MAPPING
+	if (audio_devs[dev]->dmap_out->mapping_flags & DMA_MAP_MAPPED)
+	  audio_devs[dev]->dmap_out->qlen = 0;	/* Acknowledge interrupts */
+#endif
 	restore_flags (flags);
 	return 0;
       }
@@ -879,6 +944,10 @@
 	  {
 	    /* OK to start the device */
 	    audio_devs[dev]->go = 1;
+
+	    if (audio_devs[dev]->trigger)
+	      audio_devs[dev]->trigger (dev,
+			audio_devs[dev]->enable_bits * audio_devs[dev]->go);
 	  }
 }
 
@@ -902,7 +971,7 @@
   if (audio_devs[dev]->local_qlen)
     {
       tmp = audio_devs[dev]->local_qlen (dev);
-      if (tmp & len)
+      if (tmp && len)
 	tmp--;			/*
 				   * This buffer has been counted twice
 				 */
@@ -921,6 +990,13 @@
   int             abort, err = EIO;
   struct dma_buffparms *dmap = audio_devs[dev]->dmap_out;
 
+#ifdef ALLOW_BUFFER_MAPPING
+  if (audio_devs[dev]->dmap_out->mapping_flags & DMA_MAP_MAPPED)
+    {
+      printk ("Sound: Can't write to mmapped device (3)\n");
+      return -EINVAL;
+    }
+#endif
 
   if (dmap->dma_mode == DMODE_INPUT)	/* Direction change */
     {
@@ -983,11 +1059,11 @@
 	unsigned long   tl;
 
 	if (timeout)
-	  current->timeout = tl = jiffies + (timeout);
+	  current_set_timeout (tl = jiffies + (timeout));
 	else
 	  tl = 0xffffffff;
 	out_sleep_flag[dev].mode = WK_SLEEP;
-	interruptible_sleep_on (&out_sleeper[dev]);
+	module_interruptible_sleep_on (&out_sleeper[dev]);
 	if (!(out_sleep_flag[dev].mode & WK_WAKEUP))
 	  {
 	    if (jiffies >= tl)
@@ -1001,8 +1077,9 @@
 	  err = EIO;
 	  abort = 1;
 	  ;
+	  audio_devs[dev]->reset (dev);
 	}
-      else if ((current->signal & ~current->blocked))
+      else if (current_got_fatal_signal ())
 	{
 	  err = EINTR;
 	  abort = 1;
@@ -1023,19 +1100,58 @@
 }
 
 int
+DMAbuf_get_curr_buffer (int dev, int *buf_no, char **dma_buf, int *buf_ptr, int *buf_size)
+{
+  struct dma_buffparms *dmap = audio_devs[dev]->dmap_out;
+
+  if (dmap->cfrag < 0)
+    return -1;
+
+  *dma_buf = dmap->raw_buf + dmap->qtail * dmap->fragment_size;
+  *buf_ptr = dmap->counts[dmap->qtail];
+  *buf_size = dmap->fragment_size;
+  return *buf_no = dmap->cfrag;
+}
+
+int
+DMAbuf_set_count (int dev, int buff_no, int l)
+{
+  struct dma_buffparms *dmap = audio_devs[dev]->dmap_out;
+
+  if (buff_no == dmap->qtail)
+    {
+      dmap->cfrag = buff_no;
+      dmap->counts[buff_no] = l;
+    }
+  else
+    dmap->cfrag = -1;
+  return 0;
+}
+
+int
 DMAbuf_start_output (int dev, int buff_no, int l)
 {
   struct dma_buffparms *dmap = audio_devs[dev]->dmap_out;
 
+  dmap->cfrag = -1;
 /*
  * Bypass buffering if using mmaped access
  */
 
+#ifdef ALLOW_BUFFER_MAPPING
+  if (audio_devs[dev]->dmap_out->mapping_flags & DMA_MAP_MAPPED)
+    {
+      l = dmap->fragment_size;
+      dmap->counts[dmap->qtail] = l;
+      dmap->flags &= ~DMA_RESTART;
+      dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+    }
+  else
+#else
   if (dmap != NULL)
+#endif
     {
 
-      if (buff_no != dmap->qtail)
-	printk ("Sound warning: DMA buffers out of sync %d != %d\n", buff_no, dmap->qtail);
 
       dmap->qlen++;
       if (dmap->qlen <= 0 || dmap->qlen > dmap->nbufs)
@@ -1165,12 +1281,43 @@
   unsigned long   flags;
   struct dma_buffparms *dmap = audio_devs[dev]->dmap_out;
 
+  int             p;
+
   dmap->byte_counter += dmap->counts[dmap->qhead];
 
 #ifdef OS_DMA_INTR
   sound_dma_intr (dev, audio_devs[dev]->dmap_out, audio_devs[dev]->dmachan1);
 #endif
 
+#ifdef ALLOW_BUFFER_MAPPING
+  if (dmap->mapping_flags & DMA_MAP_MAPPED)
+    {
+      /* mmapped access */
+
+      p = dmap->fragment_size * dmap->qhead;
+      memset (dmap->raw_buf + p,
+	      dmap->neutral_byte,
+	      dmap->fragment_size);
+
+      dmap->qhead = (dmap->qhead + 1) % dmap->nbufs;
+      dmap->qlen++;		/* Yes increment it (don't decrement) */
+      dmap->flags &= ~DMA_ACTIVE;
+      dmap->counts[dmap->qhead] = dmap->fragment_size;
+
+      if (!(audio_devs[dev]->flags & DMA_AUTOMODE))
+	{
+	  audio_devs[dev]->output_block (dev, dmap->raw_buf_phys +
+					 dmap->qhead * dmap->fragment_size,
+					 dmap->counts[dmap->qhead], 1,
+				  !(audio_devs[dev]->flags & DMA_AUTOMODE));
+	  if (audio_devs[dev]->trigger)
+	    audio_devs[dev]->trigger (dev,
+			audio_devs[dev]->enable_bits * audio_devs[dev]->go);
+	}
+      dmap->flags |= DMA_ACTIVE;
+    }
+  else
+#endif
   if (event_type != 2)
     {
       if (dmap->qlen <= 0 || dmap->qlen > dmap->nbufs)
@@ -1184,6 +1331,27 @@
       dmap->qhead = (dmap->qhead + 1) % dmap->nbufs;
       dmap->flags &= ~DMA_ACTIVE;
 
+      if (event_type == 1 && dmap->qlen < 1)
+	{
+	  dmap->underrun_count++;
+	  /* Ignore underrun. Just move the tail pointer forward and go */
+	  if (dmap->closing)
+	    {
+	      audio_devs[dev]->halt_xfer (dev);
+	    }
+	  else
+	    {
+	      dmap->qlen++;
+	      dmap->cfrag = -1;
+	      dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+
+	      p = dmap->fragment_size * dmap->qhead;
+	      memset (dmap->raw_buf + p,
+		      dmap->neutral_byte,
+		      dmap->fragment_size);
+	    }
+	}
+
       if (dmap->qlen)
 	{
 	  if (!(audio_devs[dev]->flags & DMA_AUTOMODE))
@@ -1198,21 +1366,6 @@
 	    }
 	  dmap->flags |= DMA_ACTIVE;
 	}
-      else if (event_type == 1)
-	{
-	  dmap->underrun_count++;
-	  if ((audio_devs[dev]->flags & DMA_DUPLEX) &&
-	      audio_devs[dev]->halt_output)
-	    audio_devs[dev]->halt_output (dev);
-	  else
-	    audio_devs[dev]->halt_xfer (dev);
-
-	  if ((audio_devs[dev]->flags & DMA_AUTOMODE) &&
-	      audio_devs[dev]->flags & NEEDS_RESTART)
-	    dmap->flags |= DMA_RESTART;
-	  else
-	    dmap->flags &= ~DMA_RESTART;
-	}
     }				/* event_type != 2 */
 
   save_flags (flags);
@@ -1221,7 +1374,7 @@
     {
       {
 	out_sleep_flag[dev].mode = WK_WAKEUP;
-	wake_up (&out_sleeper[dev]);
+	module_wake_up (&out_sleeper[dev]);
       };
     }
   restore_flags (flags);
@@ -1239,21 +1392,47 @@
   sound_dma_intr (dev, audio_devs[dev]->dmap_in, audio_devs[dev]->dmachan2);
 #endif
 
+#ifdef ALLOW_BUFFER_MAPPING
+  if (dmap->mapping_flags & DMA_MAP_MAPPED)
+    {
+      dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+      dmap->qlen++;
+
+      if (!(audio_devs[dev]->flags & DMA_AUTOMODE))
+	{
+	  audio_devs[dev]->start_input (dev, dmap->raw_buf_phys +
+					dmap->qtail * dmap->fragment_size,
+					dmap->fragment_size, 1,
+				  !(audio_devs[dev]->flags & DMA_AUTOMODE));
+	  if (audio_devs[dev]->trigger)
+	    audio_devs[dev]->trigger (dev,
+			audio_devs[dev]->enable_bits * audio_devs[dev]->go);
+	}
+
+      dmap->flags |= DMA_ACTIVE;
+    }
+  else
+#endif
   if (dmap->qlen == (dmap->nbufs - 1))
     {
       printk ("Sound: Recording overrun\n");
       dmap->underrun_count++;
-      if ((audio_devs[dev]->flags & DMA_DUPLEX) &&
-	  audio_devs[dev]->halt_input)
-	audio_devs[dev]->halt_input (dev);
-      else
-	audio_devs[dev]->halt_xfer (dev);
 
-      dmap->flags &= ~DMA_ACTIVE;
       if (audio_devs[dev]->flags & DMA_AUTOMODE)
-	dmap->flags |= DMA_RESTART;
-      else
-	dmap->flags &= ~DMA_RESTART;
+	{
+	  /* Force restart on next write */
+	  if ((audio_devs[dev]->flags & DMA_DUPLEX) &&
+	      audio_devs[dev]->halt_input)
+	    audio_devs[dev]->halt_input (dev);
+	  else
+	    audio_devs[dev]->halt_xfer (dev);
+
+	  dmap->flags &= ~DMA_ACTIVE;
+	  if (audio_devs[dev]->flags & DMA_AUTOMODE)
+	    dmap->flags |= DMA_RESTART;
+	  else
+	    dmap->flags &= ~DMA_RESTART;
+	}
     }
   else
     {
@@ -1262,28 +1441,28 @@
 	printk ("\nSound: Audio queue4 corrupted for dev%d (%d/%d)\n",
 		dev, dmap->qlen, dmap->nbufs);
       dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+    }
 
-      if (!(audio_devs[dev]->flags & DMA_AUTOMODE))
-	{
-	  audio_devs[dev]->start_input (dev, dmap->raw_buf_phys +
-					dmap->qtail * dmap->fragment_size,
-					dmap->fragment_size, 1,
+  if (!(audio_devs[dev]->flags & DMA_AUTOMODE))
+    {
+      audio_devs[dev]->start_input (dev, dmap->raw_buf_phys +
+				    dmap->qtail * dmap->fragment_size,
+				    dmap->fragment_size, 1,
 				  !(audio_devs[dev]->flags & DMA_AUTOMODE));
-	  if (audio_devs[dev]->trigger)
-	    audio_devs[dev]->trigger (dev,
+      if (audio_devs[dev]->trigger)
+	audio_devs[dev]->trigger (dev,
 			audio_devs[dev]->enable_bits * audio_devs[dev]->go);
-	}
-
-      dmap->flags |= DMA_ACTIVE;
     }
 
+  dmap->flags |= DMA_ACTIVE;
+
   save_flags (flags);
   cli ();
   if ((in_sleep_flag[dev].mode & WK_SLEEP))
     {
       {
 	in_sleep_flag[dev].mode = WK_WAKEUP;
-	wake_up (&in_sleeper[dev]);
+	module_wake_up (&in_sleeper[dev]);
       };
     }
   restore_flags (flags);
@@ -1352,7 +1531,7 @@
 }
 
 int
-DMAbuf_select (int dev, struct fileinfo *file, int sel_type, select_table * wait)
+DMAbuf_select (int dev, struct fileinfo *file, int sel_type, select_table_handle * wait)
 {
   struct dma_buffparms *dmap;
   unsigned long   flags;
@@ -1362,6 +1541,20 @@
     case SEL_IN:
       dmap = audio_devs[dev]->dmap_in;
 
+#ifdef ALLOW_BUFFER_MAPPING
+      if (dmap->mapping_flags & DMA_MAP_MAPPED)
+	{
+	  if (dmap->qlen)
+	    return 1;
+
+	  save_flags (flags);
+	  cli ();
+	  in_sleep_flag[dev].mode = WK_SLEEP;
+	  module_select_wait (&in_sleeper[dev], wait);
+	  restore_flags (flags);
+	  return 0;
+	}
+#endif
 
       if (dmap->dma_mode != DMODE_INPUT)
 	{
@@ -1384,7 +1577,7 @@
 	  save_flags (flags);
 	  cli ();
 	  in_sleep_flag[dev].mode = WK_SLEEP;
-	  select_wait (&in_sleeper[dev], wait);
+	  module_select_wait (&in_sleeper[dev], wait);
 	  restore_flags (flags);
 	  return 0;
 	}
@@ -1394,6 +1587,20 @@
     case SEL_OUT:
       dmap = audio_devs[dev]->dmap_out;
 
+#ifdef ALLOW_BUFFER_MAPPING
+      if (dmap->mapping_flags & DMA_MAP_MAPPED)
+	{
+	  if (dmap->qlen)
+	    return 1;
+
+	  save_flags (flags);
+	  cli ();
+	  out_sleep_flag[dev].mode = WK_SLEEP;
+	  module_select_wait (&out_sleeper[dev], wait);
+	  restore_flags (flags);
+	  return 0;
+	}
+#endif
 
       if (dmap->dma_mode == DMODE_INPUT)
 	{
@@ -1410,7 +1617,7 @@
 	  save_flags (flags);
 	  cli ();
 	  out_sleep_flag[dev].mode = WK_SLEEP;
-	  select_wait (&out_sleeper[dev], wait);
+	  module_select_wait (&out_sleeper[dev], wait);
 	  restore_flags (flags);
 	  return 0;
 	}
@@ -1425,7 +1632,7 @@
 }
 
 
-#else /* EXCLUDE_AUDIO */
+#else /* CONFIG_AUDIO */
 /*
  * Stub versions if audio services not included
  */
@@ -1513,6 +1720,4 @@
 {
   return;
 }
-#endif
-
 #endif

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this