1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * DMA-BUF sysfs statistics. |
4 | * |
5 | * Copyright (C) 2021 Google LLC. |
6 | */ |
7 | |
8 | #include <linux/dma-buf.h> |
9 | #include <linux/dma-resv.h> |
10 | #include <linux/kobject.h> |
11 | #include <linux/printk.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/sysfs.h> |
14 | |
15 | #include "dma-buf-sysfs-stats.h" |
16 | |
17 | #define to_dma_buf_entry_from_kobj(x) container_of(x, struct dma_buf_sysfs_entry, kobj) |
18 | |
19 | /** |
20 | * DOC: overview |
21 | * |
22 | * ``/sys/kernel/debug/dma_buf/bufinfo`` provides an overview of every DMA-BUF |
23 | * in the system. However, since debugfs is not safe to be mounted in |
24 | * production, procfs and sysfs can be used to gather DMA-BUF statistics on |
25 | * production systems. |
26 | * |
27 | * The ``/proc/<pid>/fdinfo/<fd>`` files in procfs can be used to gather |
28 | * information about DMA-BUF fds. Detailed documentation about the interface |
29 | * is present in Documentation/filesystems/proc.rst. |
30 | * |
31 | * Unfortunately, the existing procfs interfaces can only provide information |
32 | * about the DMA-BUFs for which processes hold fds or have the buffers mmapped |
33 | * into their address space. This necessitated the creation of the DMA-BUF sysfs |
34 | * statistics interface to provide per-buffer information on production systems. |
35 | * |
36 | * The interface at ``/sys/kernel/dmabuf/buffers`` exposes information about |
37 | * every DMA-BUF when ``CONFIG_DMABUF_SYSFS_STATS`` is enabled. |
38 | * |
39 | * The following stats are exposed by the interface: |
40 | * |
41 | * * ``/sys/kernel/dmabuf/buffers/<inode_number>/exporter_name`` |
42 | * * ``/sys/kernel/dmabuf/buffers/<inode_number>/size`` |
43 | * |
44 | * The information in the interface can also be used to derive per-exporter |
45 | * statistics. The data from the interface can be gathered on error conditions |
46 | * or other important events to provide a snapshot of DMA-BUF usage. |
47 | * It can also be collected periodically by telemetry to monitor various metrics. |
48 | * |
49 | * Detailed documentation about the interface is present in |
50 | * Documentation/ABI/testing/sysfs-kernel-dmabuf-buffers. |
51 | */ |
52 | |
53 | struct dma_buf_stats_attribute { |
54 | struct attribute attr; |
55 | ssize_t (*show)(struct dma_buf *dmabuf, |
56 | struct dma_buf_stats_attribute *attr, char *buf); |
57 | }; |
58 | #define to_dma_buf_stats_attr(x) container_of(x, struct dma_buf_stats_attribute, attr) |
59 | |
60 | static ssize_t dma_buf_stats_attribute_show(struct kobject *kobj, |
61 | struct attribute *attr, |
62 | char *buf) |
63 | { |
64 | struct dma_buf_stats_attribute *attribute; |
65 | struct dma_buf_sysfs_entry *sysfs_entry; |
66 | struct dma_buf *dmabuf; |
67 | |
68 | attribute = to_dma_buf_stats_attr(attr); |
69 | sysfs_entry = to_dma_buf_entry_from_kobj(kobj); |
70 | dmabuf = sysfs_entry->dmabuf; |
71 | |
72 | if (!dmabuf || !attribute->show) |
73 | return -EIO; |
74 | |
75 | return attribute->show(dmabuf, attribute, buf); |
76 | } |
77 | |
78 | static const struct sysfs_ops dma_buf_stats_sysfs_ops = { |
79 | .show = dma_buf_stats_attribute_show, |
80 | }; |
81 | |
82 | static ssize_t exporter_name_show(struct dma_buf *dmabuf, |
83 | struct dma_buf_stats_attribute *attr, |
84 | char *buf) |
85 | { |
86 | return sysfs_emit(buf, fmt: "%s\n" , dmabuf->exp_name); |
87 | } |
88 | |
89 | static ssize_t size_show(struct dma_buf *dmabuf, |
90 | struct dma_buf_stats_attribute *attr, |
91 | char *buf) |
92 | { |
93 | return sysfs_emit(buf, fmt: "%zu\n" , dmabuf->size); |
94 | } |
95 | |
96 | static struct dma_buf_stats_attribute exporter_name_attribute = |
97 | __ATTR_RO(exporter_name); |
98 | static struct dma_buf_stats_attribute size_attribute = __ATTR_RO(size); |
99 | |
100 | static struct attribute *dma_buf_stats_default_attrs[] = { |
101 | &exporter_name_attribute.attr, |
102 | &size_attribute.attr, |
103 | NULL, |
104 | }; |
105 | ATTRIBUTE_GROUPS(dma_buf_stats_default); |
106 | |
107 | static void dma_buf_sysfs_release(struct kobject *kobj) |
108 | { |
109 | struct dma_buf_sysfs_entry *sysfs_entry; |
110 | |
111 | sysfs_entry = to_dma_buf_entry_from_kobj(kobj); |
112 | kfree(objp: sysfs_entry); |
113 | } |
114 | |
115 | static const struct kobj_type dma_buf_ktype = { |
116 | .sysfs_ops = &dma_buf_stats_sysfs_ops, |
117 | .release = dma_buf_sysfs_release, |
118 | .default_groups = dma_buf_stats_default_groups, |
119 | }; |
120 | |
121 | void dma_buf_stats_teardown(struct dma_buf *dmabuf) |
122 | { |
123 | struct dma_buf_sysfs_entry *sysfs_entry; |
124 | |
125 | sysfs_entry = dmabuf->sysfs_entry; |
126 | if (!sysfs_entry) |
127 | return; |
128 | |
129 | kobject_del(kobj: &sysfs_entry->kobj); |
130 | kobject_put(kobj: &sysfs_entry->kobj); |
131 | } |
132 | |
133 | |
134 | /* Statistics files do not need to send uevents. */ |
135 | static int dmabuf_sysfs_uevent_filter(const struct kobject *kobj) |
136 | { |
137 | return 0; |
138 | } |
139 | |
140 | static const struct kset_uevent_ops dmabuf_sysfs_no_uevent_ops = { |
141 | .filter = dmabuf_sysfs_uevent_filter, |
142 | }; |
143 | |
144 | static struct kset *dma_buf_stats_kset; |
145 | static struct kset *dma_buf_per_buffer_stats_kset; |
146 | int dma_buf_init_sysfs_statistics(void) |
147 | { |
148 | dma_buf_stats_kset = kset_create_and_add(name: "dmabuf" , |
149 | u: &dmabuf_sysfs_no_uevent_ops, |
150 | parent_kobj: kernel_kobj); |
151 | if (!dma_buf_stats_kset) |
152 | return -ENOMEM; |
153 | |
154 | dma_buf_per_buffer_stats_kset = kset_create_and_add(name: "buffers" , |
155 | u: &dmabuf_sysfs_no_uevent_ops, |
156 | parent_kobj: &dma_buf_stats_kset->kobj); |
157 | if (!dma_buf_per_buffer_stats_kset) { |
158 | kset_unregister(kset: dma_buf_stats_kset); |
159 | return -ENOMEM; |
160 | } |
161 | |
162 | return 0; |
163 | } |
164 | |
165 | void dma_buf_uninit_sysfs_statistics(void) |
166 | { |
167 | kset_unregister(kset: dma_buf_per_buffer_stats_kset); |
168 | kset_unregister(kset: dma_buf_stats_kset); |
169 | } |
170 | |
171 | int dma_buf_stats_setup(struct dma_buf *dmabuf, struct file *file) |
172 | { |
173 | struct dma_buf_sysfs_entry *sysfs_entry; |
174 | int ret; |
175 | |
176 | if (!dmabuf->exp_name) { |
177 | pr_err("exporter name must not be empty if stats needed\n" ); |
178 | return -EINVAL; |
179 | } |
180 | |
181 | sysfs_entry = kzalloc(size: sizeof(struct dma_buf_sysfs_entry), GFP_KERNEL); |
182 | if (!sysfs_entry) |
183 | return -ENOMEM; |
184 | |
185 | sysfs_entry->kobj.kset = dma_buf_per_buffer_stats_kset; |
186 | sysfs_entry->dmabuf = dmabuf; |
187 | |
188 | dmabuf->sysfs_entry = sysfs_entry; |
189 | |
190 | /* create the directory for buffer stats */ |
191 | ret = kobject_init_and_add(kobj: &sysfs_entry->kobj, ktype: &dma_buf_ktype, NULL, |
192 | fmt: "%lu" , file_inode(f: file)->i_ino); |
193 | if (ret) |
194 | goto err_sysfs_dmabuf; |
195 | |
196 | return 0; |
197 | |
198 | err_sysfs_dmabuf: |
199 | kobject_put(kobj: &sysfs_entry->kobj); |
200 | dmabuf->sysfs_entry = NULL; |
201 | return ret; |
202 | } |
203 | |