/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 * or http://www.opensolaris.org/os/licensing. * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Misc module for AGP master device support */ #include #include #include #include #include #include #include #include #include #include #define PGTBL_CTL 0x2020 /* Page table control register */ #define I8XX_FB_BAR 1 #define I8XX_MMIO_BAR 2 #define I8XX_PTE_OFFSET 0x10000 #define I915_MMADR 1 /* mem-mapped registers BAR */ #define I915_GMADR 3 /* graphics mem BAR */ #define I915_GTTADDR 4 /* GTT BAR */ #define I965_GTTMMADR 1 /* mem-mapped registers BAR + GTT */ #define I965_GMADR 2 /* graphics mem BAR */ /* In 965 1MB GTTMMADR, GTT reside in the latter 512KB */ #define I965_GTT_OFFSET 0x80000 #define GTT_SIZE_MASK 0xe #define GTT_512KB (0 << 1) #define GTT_256KB (1 << 1) #define GTT_128KB (2 << 1) #define MMIO_BASE(x) (x)->agpm_data.agpm_gtt.gtt_mmio_base #define MMIO_HANDLE(x) (x)->agpm_data.agpm_gtt.gtt_mmio_handle #define GTT_ADDR(x) (x)->agpm_data.agpm_gtt.gtt_addr #define APER_BASE(x) (x)->agpm_data.agpm_gtt.gtt_info.igd_aperbase #define AGPM_WRITE(x, off, val) \ ddi_put32(MMIO_HANDLE(x), (uint32_t *)(MMIO_BASE(x) + (off)), (val)); #define AGPM_READ(x, off) \ ddi_get32(MMIO_HANDLE(x), (uint32_t *)(MMIO_BASE(x) + (off))); #ifdef DEBUG #define CONFIRM(value) ASSERT(value) #else #define CONFIRM(value) if (!(value)) return (EINVAL) #endif int agpm_debug = 0; #define AGPM_DEBUG(args) if (agpm_debug >= 1) cmn_err args /* * Whether it is a Intel integrated graphics card */ #define IS_IGD(agpmaster) ((agpmaster->agpm_dev_type == DEVICE_IS_I810) || \ (agpmaster->agpm_dev_type == DEVICE_IS_I830)) /* Intel 915 and 945 series */ #define IS_INTEL_915(agpmaster) ((agpmaster->agpm_id == INTEL_IGD_915) || \ (agpmaster->agpm_id == INTEL_IGD_915GM) || \ (agpmaster->agpm_id == INTEL_IGD_945) || \ (agpmaster->agpm_id == INTEL_IGD_945GM)) /* Intel 965 series */ #define IS_INTEL_965(agpmaster) ((agpmaster->agpm_id == INTEL_IGD_946GZ) || \ (agpmaster->agpm_id == INTEL_IGD_965G1) || \ (agpmaster->agpm_id == INTEL_IGD_965Q) || \ (agpmaster->agpm_id == INTEL_IGD_965G2) || \ (agpmaster->agpm_id == INTEL_IGD_965GM) || \ (agpmaster->agpm_id == INTEL_IGD_965GME)) static struct modlmisc modlmisc = { &mod_miscops, "AGP master interfaces v%I%" }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modlmisc, NULL }; static ddi_device_acc_attr_t i8xx_dev_access = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static off_t agpmaster_cap_find(ddi_acc_handle_t); static int detect_i8xx_device(agp_master_softc_t *); static int detect_agp_devcice(agp_master_softc_t *, ddi_acc_handle_t); static int i8xx_add_to_gtt(gtt_impl_t *, igd_gtt_seg_t); static void i8xx_remove_from_gtt(gtt_impl_t *, igd_gtt_seg_t); int _init(void) { int err; if ((err = mod_install(&modlinkage)) != 0) return (err); return (0); } int _fini(void) { int err; if ((err = mod_remove(&modlinkage)) != 0) return (err); return (0); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* * Minor node is not removed here, since the caller (xx_attach) is * responsible for removing all nodes. */ void agpmaster_detach(agp_master_softc_t **master_softcp) { agp_master_softc_t *master_softc; ASSERT(master_softcp); master_softc = *master_softcp; /* intel integrated device */ if (IS_IGD(master_softc)) { if (MMIO_HANDLE(master_softc) != NULL) { ddi_regs_map_free(&MMIO_HANDLE(master_softc)); } } kmem_free(master_softc, sizeof (agp_master_softc_t)); master_softc = NULL; return; } /* * 965 has a fixed GTT table size (512KB), so check to see the actual aperture * size. Aperture size = GTT table size * 1024. */ static off_t i965_apersize(agp_master_softc_t *agpmaster) { off_t apersize; apersize = AGPM_READ(agpmaster, PGTBL_CTL); AGPM_DEBUG((CE_NOTE, "i965_apersize: PGTBL_CTL = %lx", apersize)); switch (apersize & GTT_SIZE_MASK) { case GTT_512KB: apersize = 512; break; case GTT_256KB: apersize = 256; break; case GTT_128KB: apersize = 128; break; default: AGPM_DEBUG((CE_WARN, "i965_apersize: invalid GTT size in PGTBL_CTL")); } apersize = MB2BYTES(apersize); return (apersize); } #define CHECK_STATUS(status) \ if (status != DDI_SUCCESS) { \ AGPM_DEBUG((CE_WARN, \ "set_gtt_mmio: regs_map_setup error")); \ return (-1); \ } /* * Set gtt_addr, gtt_mmio_base, igd_apersize, igd_aperbase and igd_devid * according to chipset. */ static int set_gtt_mmio(dev_info_t *devi, agp_master_softc_t *agpmaster, ddi_acc_handle_t pci_acc_hdl) { off_t apersize; uint32_t value; off_t conf_off; /* offset in PCI conf space for aperture */ int status; if (IS_INTEL_965(agpmaster)) { status = ddi_regs_map_setup(devi, I965_GTTMMADR, &MMIO_BASE(agpmaster), 0, 0, &i8xx_dev_access, &MMIO_HANDLE(agpmaster)); CHECK_STATUS(status); GTT_ADDR(agpmaster) = MMIO_BASE(agpmaster) + I965_GTT_OFFSET; conf_off = I915_CONF_GMADR; apersize = i965_apersize(agpmaster); /* make this the last line, to clear follow-up status check */ status = DDI_SUCCESS; } else if (IS_INTEL_915(agpmaster)) { status = ddi_regs_map_setup(devi, I915_GTTADDR, >T_ADDR(agpmaster), 0, 0, &i8xx_dev_access, &MMIO_HANDLE(agpmaster)); CHECK_STATUS(status); status = ddi_regs_map_setup(devi, I915_MMADR, &MMIO_BASE(agpmaster), 0, 0, &i8xx_dev_access, &MMIO_HANDLE(agpmaster)); CHECK_STATUS(status); conf_off = I915_CONF_GMADR; status = ddi_dev_regsize(devi, I915_GMADR, &apersize); } else { /* I8XX series */ status = ddi_regs_map_setup(devi, I8XX_MMIO_BAR, &MMIO_BASE(agpmaster), 0, 0, &i8xx_dev_access, &MMIO_HANDLE(agpmaster)); CHECK_STATUS(status); GTT_ADDR(agpmaster) = MMIO_BASE(agpmaster) + I8XX_PTE_OFFSET; conf_off = I8XX_CONF_GMADR; status = ddi_dev_regsize(devi, I8XX_FB_BAR, &apersize); CHECK_STATUS(status); } /* * if memory size is smaller than a certain value, it means * the register set number for graphics memory range might * be wrong */ if (status != DDI_SUCCESS || apersize < 0x400000) { AGPM_DEBUG((CE_WARN, "set_gtt_mmio: ddi_dev_regsize error")); return (-1); } agpmaster->agpm_data.agpm_gtt.gtt_info.igd_apersize = BYTES2MB(apersize); /* get GTT base */ value = pci_config_get32(pci_acc_hdl, conf_off); APER_BASE(agpmaster) = value & GTT_BASE_MASK; agpmaster->agpm_data.agpm_gtt.gtt_info.igd_devid = agpmaster->agpm_id; AGPM_DEBUG((CE_NOTE, "set_gtt_mmio: aperbase = %x, apersize = %lx, " "gtt_addr = %p, mmio_base = %p", APER_BASE(agpmaster), apersize, (void *)GTT_ADDR(agpmaster), (void *)MMIO_BASE(agpmaster))); return (0); } /* * Try to initialize agp master. * 0 is returned if the device is successfully initialized. AGP master soft * state is returned in master_softcp if needed. * Otherwise -1 is returned and *master_softcp is set to NULL. */ int agpmaster_attach(dev_info_t *devi, agp_master_softc_t **master_softcp, ddi_acc_handle_t pci_acc_hdl, minor_t minor) { int instance; int status; agp_master_softc_t *agpmaster; char buf[80]; ASSERT(pci_acc_hdl); *master_softcp = NULL; agpmaster = (agp_master_softc_t *) kmem_zalloc(sizeof (agp_master_softc_t), KM_SLEEP); agpmaster->agpm_id = pci_config_get32(pci_acc_hdl, PCI_CONF_VENID); agpmaster->agpm_acc_hdl = pci_acc_hdl; if (!detect_i8xx_device(agpmaster)) { /* Intel 8XX, 915, 945 and 965 series */ if (set_gtt_mmio(devi, agpmaster, pci_acc_hdl) != 0) goto fail; } else if (detect_agp_devcice(agpmaster, pci_acc_hdl)) { /* non IGD or AGP devices, AMD64 gart */ AGPM_DEBUG((CE_WARN, "agpmaster_attach: neither IGD or AGP devices exists")); agpmaster_detach(&agpmaster); return (0); } /* create minor node for IGD or AGP device */ instance = ddi_get_instance(devi); (void) sprintf(buf, "%s%d", AGPMASTER_NAME, instance); status = ddi_create_minor_node(devi, buf, S_IFCHR, minor, DDI_NT_AGP_MASTER, 0); if (status != DDI_SUCCESS) { AGPM_DEBUG((CE_WARN, "agpmaster_attach: create agpmaster node failed")); goto fail; } *master_softcp = agpmaster; return (0); fail: agpmaster_detach(&agpmaster); return (-1); } /* * Currently, it handles ioctl requests related with agp master device for * layered driver (agpgart) only. */ /*ARGSUSED*/ int agpmaster_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *cred, int *rval, agp_master_softc_t *softc) { uint32_t base; uint32_t addr; igd_gtt_seg_t seg; agp_info_t info; uint32_t value; off_t cap; uint32_t command; static char kernel_only[] = "agpmaster_ioctl: %s is a kernel only ioctl"; CONFIRM(softc); switch (cmd) { case DEVICE_DETECT: if (!(mode & FKIOCTL)) { AGPM_DEBUG((CE_CONT, kernel_only, "DEVICE_DETECT")); return (ENXIO); } if (ddi_copyout(&softc->agpm_dev_type, (void *)data, sizeof (int), mode)) return (EFAULT); break; case AGP_MASTER_SETCMD: if (!(mode & FKIOCTL)) { AGPM_DEBUG((CE_CONT, kernel_only, "AGP_MASTER_SETCMD")); return (ENXIO); } CONFIRM(softc->agpm_dev_type == DEVICE_IS_AGP); CONFIRM(softc->agpm_data.agpm_acaptr); if (ddi_copyin((void *)data, &command, sizeof (uint32_t), mode)) return (EFAULT); pci_config_put32(softc->agpm_acc_hdl, softc->agpm_data.agpm_acaptr + AGP_CONF_COMMAND, command); break; case AGP_MASTER_GETINFO: if (!(mode & FKIOCTL)) { AGPM_DEBUG((CE_CONT, kernel_only, "AGP_MASTER_GETINFO")); return (ENXIO); } CONFIRM(softc->agpm_dev_type == DEVICE_IS_AGP); CONFIRM(softc->agpm_data.agpm_acaptr); cap = softc->agpm_data.agpm_acaptr; value = pci_config_get32(softc->agpm_acc_hdl, cap); info.agpi_version.agpv_major = (uint16_t)((value >> 20) & 0xf); info.agpi_version.agpv_minor = (uint16_t)((value >> 16) & 0xf); info.agpi_devid = softc->agpm_id; info.agpi_mode = pci_config_get32( softc->agpm_acc_hdl, cap + AGP_CONF_STATUS); if (ddi_copyout(&info, (void *)data, sizeof (agp_info_t), mode)) return (EFAULT); break; case I810_SET_GTT_BASE: if (!(mode & FKIOCTL)) { AGPM_DEBUG((CE_CONT, kernel_only, "I810_SET_GTT_ADDR")); return (ENXIO); } CONFIRM(softc->agpm_dev_type == DEVICE_IS_I810); if (ddi_copyin((void *)data, &base, sizeof (uint32_t), mode)) return (EFAULT); /* enables page table */ addr = (base & GTT_BASE_MASK) | GTT_TABLE_VALID; AGPM_WRITE(softc, PGTBL_CTL, addr); break; case I8XX_GET_INFO: if (!(mode & FKIOCTL)) { AGPM_DEBUG((CE_CONT, kernel_only, "I8XX_GET_INFO")); return (ENXIO); } CONFIRM(IS_IGD(softc)); if (ddi_copyout(&softc->agpm_data.agpm_gtt.gtt_info, (void *)data, sizeof (igd_info_t), mode)) return (EFAULT); break; case I8XX_ADD2GTT: if (!(mode & FKIOCTL)) { AGPM_DEBUG((CE_CONT, kernel_only, "I8XX_ADD2GTT")); return (ENXIO); } CONFIRM(IS_IGD(softc)); if (ddi_copyin((void *)data, &seg, sizeof (igd_gtt_seg_t), mode)) return (EFAULT); if (i8xx_add_to_gtt(&softc->agpm_data.agpm_gtt, seg)) return (EINVAL); break; case I8XX_REM_GTT: if (!(mode & FKIOCTL)) { AGPM_DEBUG((CE_CONT, kernel_only, "I8XX_REM_GTT")); return (ENXIO); } CONFIRM(IS_IGD(softc)); if (ddi_copyin((void *)data, &seg, sizeof (igd_gtt_seg_t), mode)) return (EFAULT); i8xx_remove_from_gtt(&softc->agpm_data.agpm_gtt, seg); break; case I8XX_UNCONFIG: if (!(mode & FKIOCTL)) { AGPM_DEBUG((CE_CONT, kernel_only, "I8XX_UNCONFIG")); return (ENXIO); } CONFIRM(IS_IGD(softc)); if (softc->agpm_dev_type == DEVICE_IS_I810) AGPM_WRITE(softc, PGTBL_CTL, 0); /* * may need to clear all gtt entries here for i830 series, * but may not be necessary */ break; } return (0); } /* * If AGP cap pointer is successfully found, none-zero value is returned. * Otherwise 0 is returned. */ static off_t agpmaster_cap_find(ddi_acc_handle_t acc_handle) { off_t nextcap; uint32_t ncapid; uint8_t value; /* check if this device supports capibility pointer */ value = (uint8_t)(pci_config_get16(acc_handle, PCI_CONF_STAT) & PCI_CONF_CAP_MASK); if (!value) return (0); /* get the offset of the first capability pointer from CAPPTR */ nextcap = (off_t)(pci_config_get8(acc_handle, AGP_CONF_CAPPTR)); /* check AGP capability from the first capability pointer */ while (nextcap) { ncapid = pci_config_get32(acc_handle, nextcap); if ((ncapid & PCI_CONF_CAPID_MASK) == AGP_CAP_ID) /* find AGP cap */ break; nextcap = (off_t)((ncapid & PCI_CONF_NCAPID_MASK) >> 8); } return (nextcap); } /* * If i8xx device is successfully detected, 0 is returned. * Otherwise -1 is returned. */ static int detect_i8xx_device(agp_master_softc_t *master_softc) { switch (master_softc->agpm_id) { case INTEL_IGD_810: case INTEL_IGD_810DC: case INTEL_IGD_810E: case INTEL_IGD_815: master_softc->agpm_dev_type = DEVICE_IS_I810; break; case INTEL_IGD_830M: case INTEL_IGD_845G: case INTEL_IGD_855GM: case INTEL_IGD_865G: case INTEL_IGD_915: case INTEL_IGD_915GM: case INTEL_IGD_945: case INTEL_IGD_945GM: case INTEL_IGD_946GZ: case INTEL_IGD_965G1: case INTEL_IGD_965G2: case INTEL_IGD_965GM: case INTEL_IGD_965GME: case INTEL_IGD_965Q: master_softc->agpm_dev_type = DEVICE_IS_I830; break; default: /* unknown id */ return (-1); } return (0); } /* * If agp master is succssfully detected, 0 is returned. * Otherwise -1 is returned. */ static int detect_agp_devcice(agp_master_softc_t *master_softc, ddi_acc_handle_t acc_handle) { off_t cap; cap = agpmaster_cap_find(acc_handle); if (cap) { master_softc->agpm_dev_type = DEVICE_IS_AGP; master_softc->agpm_data.agpm_acaptr = cap; return (0); } else { return (-1); } } /* * Please refer to GART and GTT entry format table in agpdefs.h for * intel GTT entry format. */ static int phys2entry(uint32_t type, uint32_t physaddr, uint32_t *entry) { uint32_t value; switch (type) { case AGP_PHYSICAL: case AGP_NORMAL: value = (physaddr & GTT_PTE_MASK) | GTT_PTE_VALID; break; default: return (-1); } *entry = value; return (0); } static int i8xx_add_to_gtt(gtt_impl_t *gtt, igd_gtt_seg_t seg) { int i; uint32_t *paddr; uint32_t entry; uint32_t maxpages; maxpages = gtt->gtt_info.igd_apersize; maxpages = GTT_MB_TO_PAGES(maxpages); paddr = seg.igs_phyaddr; /* check if gtt max page number is reached */ if ((seg.igs_pgstart + seg.igs_npage) > maxpages) return (-1); paddr = seg.igs_phyaddr; for (i = seg.igs_pgstart; i < (seg.igs_pgstart + seg.igs_npage); i++, paddr++) { if (phys2entry(seg.igs_type, *paddr, &entry)) return (-1); ddi_put32(gtt->gtt_mmio_handle, (uint32_t *)(gtt->gtt_addr + i * sizeof (uint32_t)), entry); } return (0); } static void i8xx_remove_from_gtt(gtt_impl_t *gtt, igd_gtt_seg_t seg) { int i; uint32_t maxpages; maxpages = gtt->gtt_info.igd_apersize; maxpages = GTT_MB_TO_PAGES(maxpages); /* check if gtt max page number is reached */ if ((seg.igs_pgstart + seg.igs_npage) > maxpages) return; for (i = seg.igs_pgstart; i < (seg.igs_pgstart + seg.igs_npage); i++) { ddi_put32(gtt->gtt_mmio_handle, (uint32_t *)(gtt->gtt_addr + i * sizeof (uint32_t)), 0); } }