+/**
+ * Bootstrap the driver for AGP DMA.
+ *
+ * \todo
+ * Investigate whether there is any benifit to storing the WARP microcode in
+ * AGP memory. If not, the microcode may as well always be put in PCI
+ * memory.
+ *
+ * \todo
+ * This routine needs to set dma_bs->agp_mode to the mode actually configured
+ * in the hardware. Looking just at the Linux AGP driver code, I don't see
+ * an easy way to determine this.
+ *
+ * \sa mga_do_dma_bootstrap, mga_do_pci_dma_bootstrap
+ */
+static int mga_do_agp_dma_bootstrap(drm_device_t * dev,
+ drm_mga_dma_bootstrap_t * dma_bs)
+{
+ drm_mga_private_t * const dev_priv = (drm_mga_private_t *) dev->dev_private;
+ const unsigned int warp_size = mga_warp_microcode_size(dev_priv);
+ int err;
+ unsigned offset;
+ const unsigned secondary_size = dma_bs->secondary_bin_count
+ * dma_bs->secondary_bin_size;
+ const unsigned agp_size = (dma_bs->agp_size << 20);
+ drm_buf_desc_t req;
+ drm_agp_mode_t mode;
+ drm_agp_info_t info;
+
+
+ /* Acquire AGP. */
+ err = drm_agp_acquire(dev);
+ if (err) {
+ DRM_ERROR("Unable to acquire AGP\n");
+ return err;
+ }
+
+ err = drm_agp_info(dev, &info);
+ if (err) {
+ DRM_ERROR("Unable to get AGP info\n");
+ return err;
+ }
+
+ mode.mode = (info.mode & ~0x07) | dma_bs->agp_mode;
+ err = drm_agp_enable(dev, mode);
+ if (err) {
+ DRM_ERROR("Unable to enable AGP (mode = 0x%lx)\n", mode.mode);
+ return err;
+ }
+
+
+ /* In addition to the usual AGP mode configuration, the G200 AGP cards
+ * need to have the AGP mode "manually" set.
+ */
+
+ if (dev_priv->chipset == MGA_CARD_TYPE_G200) {
+ if (mode.mode & 0x02) {
+ MGA_WRITE(MGA_AGP_PLL, MGA_AGP2XPLL_ENABLE);
+ }
+ else {
+ MGA_WRITE(MGA_AGP_PLL, MGA_AGP2XPLL_DISABLE);
+ }
+ }
+
+
+ /* Allocate and bind AGP memory. */
+ dev_priv->agp_pages = agp_size / PAGE_SIZE;
+ dev_priv->agp_mem = drm_alloc_agp( dev_priv->agp_pages, 0 );
+ if (dev_priv->agp_mem == NULL) {
+ dev_priv->agp_pages = 0;
+ DRM_ERROR("Unable to allocate %uMB AGP memory\n",
+ dma_bs->agp_size);
+ return DRM_ERR(ENOMEM);
+ }
+
+ err = drm_bind_agp( dev_priv->agp_mem, 0 );
+ if (err) {
+ DRM_ERROR("Unable to bind AGP memory\n");
+ return err;
+ }
+
+ offset = 0;
+ err = drm_addmap( dev, offset, warp_size,
+ _DRM_AGP, _DRM_READ_ONLY, & dev_priv->warp );
+ if (err) {
+ DRM_ERROR("Unable to map WARP microcode\n");
+ return err;
+ }
+
+ offset += warp_size;
+ err = drm_addmap( dev, offset, dma_bs->primary_size,
+ _DRM_AGP, _DRM_READ_ONLY, & dev_priv->primary );
+ if (err) {
+ DRM_ERROR("Unable to map primary DMA region\n");
+ return err;
+ }
+
+ offset += dma_bs->primary_size;
+ err = drm_addmap( dev, offset, secondary_size,
+ _DRM_AGP, 0, & dev->agp_buffer_map );
+ if (err) {
+ DRM_ERROR("Unable to map secondary DMA region\n");
+ return err;
+ }
+
+ (void) memset( &req, 0, sizeof(req) );
+ req.count = dma_bs->secondary_bin_count;
+ req.size = dma_bs->secondary_bin_size;
+ req.flags = _DRM_AGP_BUFFER;
+ req.agp_start = offset;
+
+ err = drm_addbufs_agp( dev, & req );
+ if (err) {
+ DRM_ERROR("Unable to add secondary DMA buffers\n");
+ return err;
+ }
+
+ offset += secondary_size;
+ err = drm_addmap( dev, offset, agp_size - offset,
+ _DRM_AGP, 0, & dev_priv->agp_textures );
+ if (err) {
+ DRM_ERROR("Unable to map AGP texture region\n");
+ return err;
+ }
+
+ drm_core_ioremap(dev_priv->warp, dev);
+ drm_core_ioremap(dev_priv->primary, dev);
+ drm_core_ioremap(dev->agp_buffer_map, dev);
+
+ if (!dev_priv->warp->handle ||
+ !dev_priv->primary->handle || !dev->agp_buffer_map->handle) {
+ DRM_ERROR("failed to ioremap agp regions! (%p, %p, %p)\n",
+ dev_priv->warp->handle, dev_priv->primary->handle,
+ dev->agp_buffer_map->handle);
+ return DRM_ERR(ENOMEM);
+ }
+
+ dev_priv->dma_access = MGA_PAGPXFER;
+ dev_priv->wagp_enable = MGA_WAGP_ENABLE;
+
+ DRM_INFO("Initialized card for AGP DMA.\n");
+ return 0;
+}
+
+/**
+ * Create a "fake" drm_map_t for a pre-mapped range of PCI consistent memory.
+ *
+ * Unlike \c drm_addmap, this function just creates a \c drm_map_t wrapper for
+ * a block of PCI consistent memory. \c drm_addmap, basically, converts a bus
+ * address to a virtual address. However, \c drm_pci_alloc gives both the bus
+ * address and the virtual address for the memory region. Not only is there
+ * no need to map it again, but mapping it again will cause problems.
+ *
+ * \param dmah DRM DMA handle returned by \c drm_pci_alloc.
+ * \param map_ptr Location to store a pointer to the \c drm_map_t.
+ *
+ * \returns
+ * On success, zero is returned. Otherwise and error code suitable for
+ * returning from an ioctl is returned.
+ */
+static int mga_fake_addmap(drm_dma_handle_t * dmah, drm_map_t ** map_ptr)
+{
+ drm_map_t * map;
+
+
+ map = drm_alloc(sizeof(drm_map_t), DRM_MEM_DRIVER);
+ if (map == NULL) {
+ return DRM_ERR(ENOMEM);
+ }
+
+ map->offset = dmah->busaddr;
+ map->size = dmah->size;
+ map->type = _DRM_CONSISTENT;
+ map->flags = _DRM_READ_ONLY;
+ map->handle = dmah->vaddr;
+ map->mtrr = 0;
+
+ *map_ptr = map;
+
+ return 0;
+}
+
+/**
+ * Bootstrap the driver for PCI DMA.
+ *
+ * \todo
+ * The algorithm for decreasing the size of the primary DMA buffer could be
+ * better. The size should be rounded up to the nearest page size, then
+ * decrease the request size by a single page each pass through the loop.
+ *
+ * \todo
+ * Determine whether the maximum address passed to drm_pci_alloc is correct.
+ * The same goes for drm_addbufs_pci.
+ *
+ * \sa mga_do_dma_bootstrap, mga_do_agp_dma_bootstrap
+ */
+static int mga_do_pci_dma_bootstrap(drm_device_t * dev,
+ drm_mga_dma_bootstrap_t * dma_bs)
+{
+ drm_mga_private_t * const dev_priv = (drm_mga_private_t *) dev->dev_private;
+ const unsigned int warp_size = mga_warp_microcode_size(dev_priv);
+ unsigned int primary_size;
+ unsigned int bin_count;
+ int err;
+ drm_buf_desc_t req;
+
+
+ if (dev->dma == NULL) {
+ DRM_ERROR("dev->dma is NULL\n");
+ return DRM_ERR(EFAULT);
+ }
+
+
+ /* The WARP microcode base address must be 256-byte aligned.
+ */
+ dev_priv->warp_dmah = drm_pci_alloc(dev, warp_size, 0x100, 0x7fffffff);
+ err = mga_fake_addmap(dev_priv->warp_dmah, & dev_priv->warp);
+ if (err) {
+ DRM_ERROR("Unable to map WARP microcode\n");
+ return err;
+ }
+
+
+ /* Other than the bottom two bits being used to encode other
+ * information, there don't appear to be any restrictions on the
+ * alignment of the primary or secondary DMA buffers.
+ */
+
+ dev_priv->primary_dmah = NULL;
+ for ( primary_size = dma_bs->primary_size
+ ; primary_size != 0
+ ; primary_size >>= 1 ) {
+ dev_priv->primary_dmah = drm_pci_alloc(dev, primary_size,
+ 0x04, 0x7fffffff);
+ if (dev_priv->primary_dmah != NULL) {
+ break;
+ }
+ }
+
+ if (dev_priv->primary_dmah == NULL) {
+ DRM_ERROR("Unable to allocate primary DMA region\n");
+ return DRM_ERR(ENOMEM);
+ }
+
+ if (dev_priv->primary_dmah->size != dma_bs->primary_size) {
+ DRM_INFO("Primary DMA buffer size reduced from %u to %u.\n",
+ dma_bs->primary_size,
+ (unsigned) dev_priv->primary_dmah->size);
+ dma_bs->primary_size = dev_priv->primary_dmah->size;
+ }
+
+ err = mga_fake_addmap(dev_priv->primary_dmah, & dev_priv->primary);
+ if (err) {
+ DRM_ERROR("Unable to map primary DMA region\n");
+ return err;
+ }
+
+
+ for ( bin_count = dma_bs->secondary_bin_count
+ ; bin_count > 0
+ ; bin_count-- ) {
+ (void) memset( &req, 0, sizeof(req) );
+ req.count = bin_count;
+ req.size = dma_bs->secondary_bin_size;
+
+ err = drm_addbufs_pci( dev, & req );
+ if (!err) {
+ break;
+ }
+ }
+
+ if (bin_count == 0) {
+ DRM_ERROR("Unable to add secondary DMA buffers\n");
+ return err;
+ }
+
+ if (bin_count != dma_bs->secondary_bin_count) {
+ DRM_INFO("Secondary PCI DMA buffer bin count reduced from %u "
+ "to %u.\n", dma_bs->secondary_bin_count, bin_count);
+
+ dma_bs->secondary_bin_count = bin_count;
+ }
+
+ dev_priv->dma_access = 0;
+ dev_priv->wagp_enable = 0;
+
+ dma_bs->agp_mode = 0;
+
+ DRM_INFO("Initialized card for PCI DMA.\n");
+ return 0;
+}
+
+
+static int mga_do_dma_bootstrap(drm_device_t * dev,
+ drm_mga_dma_bootstrap_t * dma_bs)
+{
+ const int is_agp = (dma_bs->agp_mode != 0) && drm_device_is_agp(dev);
+ int err;
+ drm_mga_private_t * const dev_priv =
+ (drm_mga_private_t *) dev->dev_private;
+
+
+ dev_priv->used_new_dma_init = 1;
+
+ /* The first steps are the same for both PCI and AGP based DMA. Map
+ * the cards MMIO registers and map a status page.
+ */
+ err = drm_addmap( dev, dev_priv->mmio_base, dev_priv->mmio_size,
+ _DRM_REGISTERS, _DRM_READ_ONLY, & dev_priv->mmio );
+ if (err) {
+ DRM_ERROR("Unable to map MMIO region\n");
+ return err;
+ }
+
+
+ err = drm_addmap( dev, 0, SAREA_MAX, _DRM_SHM,
+ _DRM_READ_ONLY | _DRM_LOCKED | _DRM_KERNEL,
+ & dev_priv->status );
+ if (err) {
+ DRM_ERROR("Unable to map status region\n");
+ return err;
+ }
+
+
+ /* The DMA initialization procedure is slightly different for PCI and
+ * AGP cards. AGP cards just allocate a large block of AGP memory and
+ * carve off portions of it for internal uses. The remaining memory
+ * is returned to user-mode to be used for AGP textures.
+ */
+
+ if (is_agp) {
+ err = mga_do_agp_dma_bootstrap(dev, dma_bs);
+ }
+
+ /* If we attempted to initialize the card for AGP DMA but failed,
+ * clean-up any mess that may have been created.
+ */
+
+ if (err) {
+ mga_do_cleanup_dma(dev);
+ }
+
+
+ /* Not only do we want to try and initialized PCI cards for PCI DMA,
+ * but we also try to initialized AGP cards that could not be
+ * initialized for AGP DMA. This covers the case where we have an AGP
+ * card in a system with an unsupported AGP chipset. In that case the
+ * card will be detected as AGP, but we won't be able to allocate any
+ * AGP memory, etc.
+ */
+
+ if (!is_agp || err) {
+ err = mga_do_pci_dma_bootstrap(dev, dma_bs);
+ }
+
+
+ return err;
+}
+
+int mga_dma_bootstrap(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_mga_dma_bootstrap_t bootstrap;
+ int err;
+
+
+ DRM_COPY_FROM_USER_IOCTL(bootstrap,
+ (drm_mga_dma_bootstrap_t __user *) data,
+ sizeof(bootstrap));
+
+ err = mga_do_dma_bootstrap(dev, & bootstrap);
+ if (! err) {
+ static const int modes[] = { 0, 1, 2, 2, 4, 4, 4, 4 };
+ const drm_mga_private_t * const dev_priv =
+ (drm_mga_private_t *) dev->dev_private;
+
+ if (dev_priv->agp_textures != NULL) {
+ bootstrap.texture_handle = dev_priv->agp_textures->offset;
+ bootstrap.texture_size = dev_priv->agp_textures->size;
+ }
+ else {
+ bootstrap.texture_handle = 0;
+ bootstrap.texture_size = 0;
+ }
+
+ bootstrap.agp_mode = modes[ bootstrap.agp_mode & 0x07 ];
+ if (DRM_COPY_TO_USER( (void __user *) data, & bootstrap,
+ sizeof(bootstrap))) {
+ err = DRM_ERR(EFAULT);
+ }
+ }
+ else {
+ mga_do_cleanup_dma(dev);
+ }
+
+ return err;
+}
+
+