1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms and conditions of the GNU General Public License,
4  * version 2, as published by the Free Software Foundation.
5  *
6  * This program is distributed in the hope it will be useful, but WITHOUT
7  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
8  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
9  * more details.
10  *
11  * You should have received a copy of the GNU General Public License along with
12  * this program; If not, see <http://www.gnu.org/licenses/>.
13  */
14 
15 #include <xen/param.h>
16 #include <xen/sched.h>
17 #include <xen/pci.h>
18 #include <xen/pci_regs.h>
19 #include "../ats.h"
20 
21 bool_t __read_mostly ats_enabled = 0;
22 boolean_param("ats", ats_enabled);
23 
enable_ats_device(struct pci_dev * pdev,struct list_head * ats_list)24 int enable_ats_device(struct pci_dev *pdev, struct list_head *ats_list)
25 {
26     u32 value;
27     u16 seg = pdev->seg;
28     u8 bus = pdev->bus, devfn = pdev->devfn;
29     int pos;
30 
31     pos = pci_find_ext_capability(seg, bus, devfn, PCI_EXT_CAP_ID_ATS);
32     BUG_ON(!pos);
33 
34     if ( iommu_verbose )
35         dprintk(XENLOG_INFO, "%04x:%02x:%02x.%u: ATS capability found\n",
36                 seg, bus, PCI_SLOT(devfn), PCI_FUNC(devfn));
37 
38     value = pci_conf_read16(pdev->sbdf, pos + ATS_REG_CTL);
39     if ( value & ATS_ENABLE )
40     {
41         struct pci_dev *other;
42 
43         list_for_each_entry ( other, ats_list, ats.list )
44             if ( other == pdev )
45             {
46                 pos = 0;
47                 break;
48             }
49     }
50 
51     if ( !(value & ATS_ENABLE) )
52     {
53         value |= ATS_ENABLE;
54         pci_conf_write16(pdev->sbdf, pos + ATS_REG_CTL, value);
55     }
56 
57     if ( pos )
58     {
59         pdev->ats.cap_pos = pos;
60         value = pci_conf_read16(pdev->sbdf, pos + ATS_REG_CAP);
61         pdev->ats.queue_depth = value & ATS_QUEUE_DEPTH_MASK ?:
62                                 ATS_QUEUE_DEPTH_MASK + 1;
63         list_add(&pdev->ats.list, ats_list);
64     }
65 
66     if ( iommu_verbose )
67         dprintk(XENLOG_INFO, "%04x:%02x:%02x.%u: ATS %s enabled\n",
68                 seg, bus, PCI_SLOT(devfn), PCI_FUNC(devfn),
69                 pos ? "is" : "was");
70 
71     return pos;
72 }
73 
disable_ats_device(struct pci_dev * pdev)74 void disable_ats_device(struct pci_dev *pdev)
75 {
76     u32 value;
77     u16 seg = pdev->seg;
78     u8 bus = pdev->bus, devfn = pdev->devfn;
79 
80     BUG_ON(!pdev->ats.cap_pos);
81 
82     value = pci_conf_read16(pdev->sbdf, pdev->ats.cap_pos + ATS_REG_CTL);
83     value &= ~ATS_ENABLE;
84     pci_conf_write16(pdev->sbdf, pdev->ats.cap_pos + ATS_REG_CTL, value);
85 
86     list_del(&pdev->ats.list);
87 
88     if ( iommu_verbose )
89         dprintk(XENLOG_INFO, "%04x:%02x:%02x.%u: ATS is disabled\n",
90                 seg, bus, PCI_SLOT(devfn), PCI_FUNC(devfn));
91 }
92