|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
| 2 | +/* Copyright(c) 2024-2025 Intel Corporation. All rights reserved. */ |
| 3 | + |
| 4 | +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| 5 | + |
| 6 | +#include <linux/module.h> |
| 7 | +#include <linux/slab.h> |
| 8 | +#include <linux/sysfs.h> |
| 9 | + |
| 10 | +#define CREATE_TRACE_POINTS |
| 11 | +#include <trace/events/tsm_mr.h> |
| 12 | + |
| 13 | +/* |
| 14 | + * struct tm_context - contains everything necessary to implement sysfs |
| 15 | + * attributes for MRs. |
| 16 | + * @rwsem: protects the MR cache from concurrent access. |
| 17 | + * @agrp: contains all MR attributes created by tsm_mr_create_attribute_group(). |
| 18 | + * @tm: input to tsm_mr_create_attribute_group() containing MR definitions/ops. |
| 19 | + * @in_sync: %true if MR cache is up-to-date. |
| 20 | + * @mrs: array of &struct bin_attribute, one for each MR. |
| 21 | + * |
| 22 | + * This internal structure contains everything needed to implement |
| 23 | + * tm_digest_read() and tm_digest_write(). |
| 24 | + * |
| 25 | + * Given tm->refresh() is potentially expensive, tm_digest_read() caches MR |
| 26 | + * values and calls tm->refresh() only when necessary. Only live MRs (i.e., with |
| 27 | + * %TSM_MR_F_LIVE set) can trigger tm->refresh(), while others are assumed to |
| 28 | + * retain their values from the last tm->write(). @in_sync tracks if there have |
| 29 | + * been tm->write() calls since the last tm->refresh(). That is, tm->refresh() |
| 30 | + * will be called only when a live MR is being read and the cache is stale |
| 31 | + * (@in_sync is %false). |
| 32 | + * |
| 33 | + * tm_digest_write() sets @in_sync to %false and calls tm->write(), whose |
| 34 | + * semantics is arch and MR specific. Most (if not all) writable MRs support the |
| 35 | + * extension semantics (i.e., tm->write() extends the input buffer into the MR). |
| 36 | + */ |
| 37 | +struct tm_context { |
| 38 | + struct rw_semaphore rwsem; |
| 39 | + struct attribute_group agrp; |
| 40 | + const struct tsm_measurements *tm; |
| 41 | + bool in_sync; |
| 42 | + struct bin_attribute mrs[]; |
| 43 | +}; |
| 44 | + |
| 45 | +static ssize_t tm_digest_read(struct file *filp, struct kobject *kobj, |
| 46 | + const struct bin_attribute *attr, char *buffer, |
| 47 | + loff_t off, size_t count) |
| 48 | +{ |
| 49 | + struct tm_context *ctx; |
| 50 | + const struct tsm_measurement_register *mr; |
| 51 | + int rc; |
| 52 | + |
| 53 | + ctx = attr->private; |
| 54 | + rc = down_read_interruptible(&ctx->rwsem); |
| 55 | + if (rc) |
| 56 | + return rc; |
| 57 | + |
| 58 | + mr = &ctx->tm->mrs[attr - ctx->mrs]; |
| 59 | + |
| 60 | + /* |
| 61 | + * @ctx->in_sync indicates if the MR cache is stale. It is a global |
| 62 | + * instead of a per-MR flag for simplicity, as most (if not all) archs |
| 63 | + * allow reading all MRs in oneshot. |
| 64 | + * |
| 65 | + * ctx->refresh() is necessary only for LIVE MRs, while others retain |
| 66 | + * their values from their respective last ctx->write(). |
| 67 | + */ |
| 68 | + if ((mr->mr_flags & TSM_MR_F_LIVE) && !ctx->in_sync) { |
| 69 | + up_read(&ctx->rwsem); |
| 70 | + |
| 71 | + rc = down_write_killable(&ctx->rwsem); |
| 72 | + if (rc) |
| 73 | + return rc; |
| 74 | + |
| 75 | + if (!ctx->in_sync) { |
| 76 | + rc = ctx->tm->refresh(ctx->tm); |
| 77 | + ctx->in_sync = !rc; |
| 78 | + trace_tsm_mr_refresh(mr, rc); |
| 79 | + } |
| 80 | + |
| 81 | + downgrade_write(&ctx->rwsem); |
| 82 | + } |
| 83 | + |
| 84 | + memcpy(buffer, mr->mr_value + off, count); |
| 85 | + trace_tsm_mr_read(mr); |
| 86 | + |
| 87 | + up_read(&ctx->rwsem); |
| 88 | + return rc ?: count; |
| 89 | +} |
| 90 | + |
| 91 | +static ssize_t tm_digest_write(struct file *filp, struct kobject *kobj, |
| 92 | + const struct bin_attribute *attr, char *buffer, |
| 93 | + loff_t off, size_t count) |
| 94 | +{ |
| 95 | + struct tm_context *ctx; |
| 96 | + const struct tsm_measurement_register *mr; |
| 97 | + ssize_t rc; |
| 98 | + |
| 99 | + /* partial writes are not supported */ |
| 100 | + if (off != 0 || count != attr->size) |
| 101 | + return -EINVAL; |
| 102 | + |
| 103 | + ctx = attr->private; |
| 104 | + mr = &ctx->tm->mrs[attr - ctx->mrs]; |
| 105 | + |
| 106 | + rc = down_write_killable(&ctx->rwsem); |
| 107 | + if (rc) |
| 108 | + return rc; |
| 109 | + |
| 110 | + rc = ctx->tm->write(ctx->tm, mr, buffer); |
| 111 | + |
| 112 | + /* mark MR cache stale */ |
| 113 | + if (!rc) { |
| 114 | + ctx->in_sync = false; |
| 115 | + trace_tsm_mr_write(mr, buffer); |
| 116 | + } |
| 117 | + |
| 118 | + up_write(&ctx->rwsem); |
| 119 | + return rc ?: count; |
| 120 | +} |
| 121 | + |
| 122 | +/** |
| 123 | + * tsm_mr_create_attribute_group() - creates an attribute group for measurement |
| 124 | + * registers (MRs) |
| 125 | + * @tm: pointer to &struct tsm_measurements containing the MR definitions. |
| 126 | + * |
| 127 | + * This function creates attributes corresponding to the MR definitions |
| 128 | + * provided by @tm->mrs. |
| 129 | + * |
| 130 | + * The created attributes will reference @tm and its members. The caller must |
| 131 | + * not free @tm until after tsm_mr_free_attribute_group() is called. |
| 132 | + * |
| 133 | + * Context: Process context. May sleep due to memory allocation. |
| 134 | + * |
| 135 | + * Return: |
| 136 | + * * On success, the pointer to a an attribute group is returned; otherwise |
| 137 | + * * %-EINVAL - Invalid MR definitions. |
| 138 | + * * %-ENOMEM - Out of memory. |
| 139 | + */ |
| 140 | +const struct attribute_group * |
| 141 | +tsm_mr_create_attribute_group(const struct tsm_measurements *tm) |
| 142 | +{ |
| 143 | + size_t nlen; |
| 144 | + |
| 145 | + if (!tm || !tm->mrs) |
| 146 | + return ERR_PTR(-EINVAL); |
| 147 | + |
| 148 | + /* aggregated length of all MR names */ |
| 149 | + nlen = 0; |
| 150 | + for (size_t i = 0; i < tm->nr_mrs; ++i) { |
| 151 | + if ((tm->mrs[i].mr_flags & TSM_MR_F_LIVE) && !tm->refresh) |
| 152 | + return ERR_PTR(-EINVAL); |
| 153 | + |
| 154 | + if ((tm->mrs[i].mr_flags & TSM_MR_F_WRITABLE) && !tm->write) |
| 155 | + return ERR_PTR(-EINVAL); |
| 156 | + |
| 157 | + if (!tm->mrs[i].mr_name) |
| 158 | + return ERR_PTR(-EINVAL); |
| 159 | + |
| 160 | + if (tm->mrs[i].mr_flags & TSM_MR_F_NOHASH) |
| 161 | + continue; |
| 162 | + |
| 163 | + if (tm->mrs[i].mr_hash >= HASH_ALGO__LAST) |
| 164 | + return ERR_PTR(-EINVAL); |
| 165 | + |
| 166 | + /* MR sysfs attribute names have the form of MRNAME:HASH */ |
| 167 | + nlen += strlen(tm->mrs[i].mr_name) + 1 + |
| 168 | + strlen(hash_algo_name[tm->mrs[i].mr_hash]) + 1; |
| 169 | + } |
| 170 | + |
| 171 | + /* |
| 172 | + * @attrs and the MR name strings are combined into a single allocation |
| 173 | + * so that we don't have to free MR names one-by-one in |
| 174 | + * tsm_mr_free_attribute_group() |
| 175 | + */ |
| 176 | + const struct bin_attribute * const *attrs __free(kfree) = |
| 177 | + kzalloc(sizeof(*attrs) * (tm->nr_mrs + 1) + nlen, GFP_KERNEL); |
| 178 | + struct tm_context *ctx __free(kfree) = |
| 179 | + kzalloc(struct_size(ctx, mrs, tm->nr_mrs), GFP_KERNEL); |
| 180 | + char *name, *end; |
| 181 | + |
| 182 | + if (!ctx || !attrs) |
| 183 | + return ERR_PTR(-ENOMEM); |
| 184 | + |
| 185 | + /* @attrs is followed immediately by MR name strings */ |
| 186 | + name = (char *)&attrs[tm->nr_mrs + 1]; |
| 187 | + end = name + nlen; |
| 188 | + |
| 189 | + for (size_t i = 0; i < tm->nr_mrs; ++i) { |
| 190 | + /* break const for init */ |
| 191 | + struct bin_attribute **bas = (struct bin_attribute **)attrs; |
| 192 | + |
| 193 | + bas[i] = &ctx->mrs[i]; |
| 194 | + sysfs_bin_attr_init(bas[i]); |
| 195 | + |
| 196 | + if (tm->mrs[i].mr_flags & TSM_MR_F_NOHASH) |
| 197 | + bas[i]->attr.name = tm->mrs[i].mr_name; |
| 198 | + else if (name < end) { |
| 199 | + bas[i]->attr.name = name; |
| 200 | + name += snprintf(name, end - name, "%s:%s", |
| 201 | + tm->mrs[i].mr_name, |
| 202 | + hash_algo_name[tm->mrs[i].mr_hash]); |
| 203 | + ++name; |
| 204 | + } else |
| 205 | + return ERR_PTR(-EINVAL); |
| 206 | + |
| 207 | + /* check for duplicated MR definitions */ |
| 208 | + for (size_t j = 0; j < i; ++j) |
| 209 | + if (!strcmp(bas[i]->attr.name, bas[j]->attr.name)) |
| 210 | + return ERR_PTR(-EINVAL); |
| 211 | + |
| 212 | + if (tm->mrs[i].mr_flags & TSM_MR_F_READABLE) { |
| 213 | + bas[i]->attr.mode |= 0444; |
| 214 | + bas[i]->read_new = tm_digest_read; |
| 215 | + } |
| 216 | + |
| 217 | + if (tm->mrs[i].mr_flags & TSM_MR_F_WRITABLE) { |
| 218 | + bas[i]->attr.mode |= 0200; |
| 219 | + bas[i]->write_new = tm_digest_write; |
| 220 | + } |
| 221 | + |
| 222 | + bas[i]->size = tm->mrs[i].mr_size; |
| 223 | + bas[i]->private = ctx; |
| 224 | + } |
| 225 | + |
| 226 | + if (name != end) |
| 227 | + return ERR_PTR(-EINVAL); |
| 228 | + |
| 229 | + init_rwsem(&ctx->rwsem); |
| 230 | + ctx->agrp.name = "measurements"; |
| 231 | + ctx->agrp.bin_attrs_new = no_free_ptr(attrs); |
| 232 | + ctx->tm = tm; |
| 233 | + return &no_free_ptr(ctx)->agrp; |
| 234 | +} |
| 235 | +EXPORT_SYMBOL_GPL(tsm_mr_create_attribute_group); |
| 236 | + |
| 237 | +/** |
| 238 | + * tsm_mr_free_attribute_group() - frees the attribute group returned by |
| 239 | + * tsm_mr_create_attribute_group() |
| 240 | + * @attr_grp: attribute group returned by tsm_mr_create_attribute_group() |
| 241 | + * |
| 242 | + * Context: Process context. |
| 243 | + */ |
| 244 | +void tsm_mr_free_attribute_group(const struct attribute_group *attr_grp) |
| 245 | +{ |
| 246 | + if (!IS_ERR_OR_NULL(attr_grp)) { |
| 247 | + kfree(attr_grp->bin_attrs); |
| 248 | + kfree(container_of(attr_grp, struct tm_context, agrp)); |
| 249 | + } |
| 250 | +} |
| 251 | +EXPORT_SYMBOL_GPL(tsm_mr_free_attribute_group); |
0 commit comments