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/sched.h>
16 #include <xen/pci.h>
17 #include <xen/pci_regs.h>
18 #include "../ats.h"
19 
20 bool_t __read_mostly ats_enabled = 0;
21 boolean_param("ats", ats_enabled);
22 
enable_ats_device(struct pci_dev * pdev,struct list_head * ats_list)23 int enable_ats_device(struct pci_dev *pdev, struct list_head *ats_list)
24 {
25     u32 value;
26     u16 seg = pdev->seg;
27     u8 bus = pdev->bus, devfn = pdev->devfn;
28     int pos;
29 
30     pos = pci_find_ext_capability(seg, bus, devfn, PCI_EXT_CAP_ID_ATS);
31     BUG_ON(!pos);
32 
33     if ( iommu_verbose )
34         dprintk(XENLOG_INFO, "%04x:%02x:%02x.%u: ATS capability found\n",
35                 seg, bus, PCI_SLOT(devfn), PCI_FUNC(devfn));
36 
37     value = pci_conf_read16(seg, bus, PCI_SLOT(devfn),
38                             PCI_FUNC(devfn), 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(seg, bus, PCI_SLOT(devfn), PCI_FUNC(devfn),
55                          pos + ATS_REG_CTL, value);
56     }
57 
58     if ( pos )
59     {
60         pdev->ats.cap_pos = pos;
61         value = pci_conf_read16(seg, bus, PCI_SLOT(devfn),
62                                 PCI_FUNC(devfn), pos + ATS_REG_CAP);
63         pdev->ats.queue_depth = value & ATS_QUEUE_DEPTH_MASK ?:
64                                 ATS_QUEUE_DEPTH_MASK + 1;
65         list_add(&pdev->ats.list, ats_list);
66     }
67 
68     if ( iommu_verbose )
69         dprintk(XENLOG_INFO, "%04x:%02x:%02x.%u: ATS %s enabled\n",
70                 seg, bus, PCI_SLOT(devfn), PCI_FUNC(devfn),
71                 pos ? "is" : "was");
72 
73     return pos;
74 }
75 
disable_ats_device(struct pci_dev * pdev)76 void disable_ats_device(struct pci_dev *pdev)
77 {
78     u32 value;
79     u16 seg = pdev->seg;
80     u8 bus = pdev->bus, devfn = pdev->devfn;
81 
82     BUG_ON(!pdev->ats.cap_pos);
83 
84     value = pci_conf_read16(seg, bus, PCI_SLOT(devfn), PCI_FUNC(devfn),
85                             pdev->ats.cap_pos + ATS_REG_CTL);
86     value &= ~ATS_ENABLE;
87     pci_conf_write16(seg, bus, PCI_SLOT(devfn), PCI_FUNC(devfn),
88                      pdev->ats.cap_pos + ATS_REG_CTL, value);
89 
90     list_del(&pdev->ats.list);
91 
92     if ( iommu_verbose )
93         dprintk(XENLOG_INFO, "%04x:%02x:%02x.%u: ATS is disabled\n",
94                 seg, bus, PCI_SLOT(devfn), PCI_FUNC(devfn));
95 }
96