|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
| 2 | +/* |
| 3 | + * MIPI DisCo for Imaging support. |
| 4 | + * |
| 5 | + * Copyright (C) 2023 Intel Corporation |
| 6 | + * |
| 7 | + * Support MIPI DisCo for Imaging by parsing ACPI _CRS CSI-2 records defined in |
| 8 | + * Section 6.4.3.8.2.4 "Camera Serial Interface (CSI-2) Connection Resource |
| 9 | + * Descriptor" of ACPI 6.5. |
| 10 | + * |
| 11 | + * The implementation looks for the information in the ACPI namespace (CSI-2 |
| 12 | + * resource descriptors in _CRS) and constructs software nodes compatible with |
| 13 | + * Documentation/firmware-guide/acpi/dsd/graph.rst to represent the CSI-2 |
| 14 | + * connection graph. |
| 15 | + */ |
| 16 | + |
| 17 | +#include <linux/acpi.h> |
| 18 | +#include <linux/limits.h> |
| 19 | +#include <linux/list.h> |
| 20 | +#include <linux/module.h> |
| 21 | +#include <linux/overflow.h> |
| 22 | +#include <linux/types.h> |
| 23 | +#include <linux/slab.h> |
| 24 | +#include <linux/string.h> |
| 25 | + |
| 26 | +#include "internal.h" |
| 27 | + |
| 28 | +static LIST_HEAD(acpi_mipi_crs_csi2_list); |
| 29 | + |
| 30 | +static void acpi_mipi_data_tag(acpi_handle handle, void *context) |
| 31 | +{ |
| 32 | +} |
| 33 | + |
| 34 | +/* Connection data extracted from one _CRS CSI-2 resource descriptor. */ |
| 35 | +struct crs_csi2_connection { |
| 36 | + struct list_head entry; |
| 37 | + struct acpi_resource_csi2_serialbus csi2_data; |
| 38 | + acpi_handle remote_handle; |
| 39 | + char remote_name[]; |
| 40 | +}; |
| 41 | + |
| 42 | +/* Data extracted from _CRS CSI-2 resource descriptors for one device. */ |
| 43 | +struct crs_csi2 { |
| 44 | + struct list_head entry; |
| 45 | + acpi_handle handle; |
| 46 | + struct acpi_device_software_nodes *swnodes; |
| 47 | + struct list_head connections; |
| 48 | + u32 port_count; |
| 49 | +}; |
| 50 | + |
| 51 | +struct csi2_resources_walk_data { |
| 52 | + acpi_handle handle; |
| 53 | + struct list_head connections; |
| 54 | +}; |
| 55 | + |
| 56 | +static acpi_status parse_csi2_resource(struct acpi_resource *res, void *context) |
| 57 | +{ |
| 58 | + struct csi2_resources_walk_data *crwd = context; |
| 59 | + struct acpi_resource_csi2_serialbus *csi2_res; |
| 60 | + struct acpi_resource_source *csi2_res_src; |
| 61 | + u16 csi2_res_src_length; |
| 62 | + struct crs_csi2_connection *conn; |
| 63 | + acpi_handle remote_handle; |
| 64 | + |
| 65 | + if (res->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) |
| 66 | + return AE_OK; |
| 67 | + |
| 68 | + csi2_res = &res->data.csi2_serial_bus; |
| 69 | + |
| 70 | + if (csi2_res->type != ACPI_RESOURCE_SERIAL_TYPE_CSI2) |
| 71 | + return AE_OK; |
| 72 | + |
| 73 | + csi2_res_src = &csi2_res->resource_source; |
| 74 | + if (ACPI_FAILURE(acpi_get_handle(NULL, csi2_res_src->string_ptr, |
| 75 | + &remote_handle))) { |
| 76 | + acpi_handle_debug(crwd->handle, |
| 77 | + "unable to find resource source\n"); |
| 78 | + return AE_OK; |
| 79 | + } |
| 80 | + csi2_res_src_length = csi2_res_src->string_length; |
| 81 | + if (!csi2_res_src_length) { |
| 82 | + acpi_handle_debug(crwd->handle, |
| 83 | + "invalid resource source string length\n"); |
| 84 | + return AE_OK; |
| 85 | + } |
| 86 | + |
| 87 | + conn = kmalloc(struct_size(conn, remote_name, csi2_res_src_length + 1), |
| 88 | + GFP_KERNEL); |
| 89 | + if (!conn) |
| 90 | + return AE_OK; |
| 91 | + |
| 92 | + conn->csi2_data = *csi2_res; |
| 93 | + strscpy(conn->remote_name, csi2_res_src->string_ptr, csi2_res_src_length); |
| 94 | + conn->csi2_data.resource_source.string_ptr = conn->remote_name; |
| 95 | + conn->remote_handle = remote_handle; |
| 96 | + |
| 97 | + list_add(&conn->entry, &crwd->connections); |
| 98 | + |
| 99 | + return AE_OK; |
| 100 | +} |
| 101 | + |
| 102 | +static struct crs_csi2 *acpi_mipi_add_crs_csi2(acpi_handle handle, |
| 103 | + struct list_head *list) |
| 104 | +{ |
| 105 | + struct crs_csi2 *csi2; |
| 106 | + |
| 107 | + csi2 = kzalloc(sizeof(*csi2), GFP_KERNEL); |
| 108 | + if (!csi2) |
| 109 | + return NULL; |
| 110 | + |
| 111 | + csi2->handle = handle; |
| 112 | + INIT_LIST_HEAD(&csi2->connections); |
| 113 | + csi2->port_count = 1; |
| 114 | + |
| 115 | + if (ACPI_FAILURE(acpi_attach_data(handle, acpi_mipi_data_tag, csi2))) { |
| 116 | + kfree(csi2); |
| 117 | + return NULL; |
| 118 | + } |
| 119 | + |
| 120 | + list_add(&csi2->entry, list); |
| 121 | + |
| 122 | + return csi2; |
| 123 | +} |
| 124 | + |
| 125 | +static struct crs_csi2 *acpi_mipi_get_crs_csi2(acpi_handle handle) |
| 126 | +{ |
| 127 | + struct crs_csi2 *csi2; |
| 128 | + |
| 129 | + if (ACPI_FAILURE(acpi_get_data_full(handle, acpi_mipi_data_tag, |
| 130 | + (void **)&csi2, NULL))) |
| 131 | + return NULL; |
| 132 | + |
| 133 | + return csi2; |
| 134 | +} |
| 135 | + |
| 136 | +static void csi_csr2_release_connections(struct list_head *list) |
| 137 | +{ |
| 138 | + struct crs_csi2_connection *conn, *conn_tmp; |
| 139 | + |
| 140 | + list_for_each_entry_safe(conn, conn_tmp, list, entry) { |
| 141 | + list_del(&conn->entry); |
| 142 | + kfree(conn); |
| 143 | + } |
| 144 | +} |
| 145 | + |
| 146 | +static void acpi_mipi_del_crs_csi2(struct crs_csi2 *csi2) |
| 147 | +{ |
| 148 | + list_del(&csi2->entry); |
| 149 | + acpi_detach_data(csi2->handle, acpi_mipi_data_tag); |
| 150 | + kfree(csi2->swnodes); |
| 151 | + csi_csr2_release_connections(&csi2->connections); |
| 152 | + kfree(csi2); |
| 153 | +} |
| 154 | + |
| 155 | +/** |
| 156 | + * acpi_mipi_check_crs_csi2 - Look for CSI-2 resources in _CRS |
| 157 | + * @handle: Device object handle to evaluate _CRS for. |
| 158 | + * |
| 159 | + * Find all CSI-2 resource descriptors in the given device's _CRS |
| 160 | + * and collect them into a list. |
| 161 | + */ |
| 162 | +void acpi_mipi_check_crs_csi2(acpi_handle handle) |
| 163 | +{ |
| 164 | + struct csi2_resources_walk_data crwd = { |
| 165 | + .handle = handle, |
| 166 | + .connections = LIST_HEAD_INIT(crwd.connections), |
| 167 | + }; |
| 168 | + struct crs_csi2 *csi2; |
| 169 | + |
| 170 | + /* |
| 171 | + * Avoid allocating _CRS CSI-2 objects for devices without any CSI-2 |
| 172 | + * resource descriptions in _CRS to reduce overhead. |
| 173 | + */ |
| 174 | + acpi_walk_resources(handle, METHOD_NAME__CRS, parse_csi2_resource, &crwd); |
| 175 | + if (list_empty(&crwd.connections)) |
| 176 | + return; |
| 177 | + |
| 178 | + /* |
| 179 | + * Create a _CRS CSI-2 entry to store the extracted connection |
| 180 | + * information and add it to the global list. |
| 181 | + */ |
| 182 | + csi2 = acpi_mipi_add_crs_csi2(handle, &acpi_mipi_crs_csi2_list); |
| 183 | + if (!csi2) { |
| 184 | + csi_csr2_release_connections(&crwd.connections); |
| 185 | + return; /* Nothing really can be done about this. */ |
| 186 | + } |
| 187 | + |
| 188 | + list_replace(&crwd.connections, &csi2->connections); |
| 189 | +} |
| 190 | + |
| 191 | +#define NO_CSI2_PORT (UINT_MAX - 1) |
| 192 | + |
| 193 | +static void alloc_crs_csi2_swnodes(struct crs_csi2 *csi2) |
| 194 | +{ |
| 195 | + size_t port_count = csi2->port_count; |
| 196 | + struct acpi_device_software_nodes *swnodes; |
| 197 | + size_t alloc_size; |
| 198 | + unsigned int i; |
| 199 | + |
| 200 | + /* |
| 201 | + * Allocate memory for ports, node pointers (number of nodes + |
| 202 | + * 1 (guardian), nodes (root + number of ports * 2 (because for |
| 203 | + * every port there is an endpoint)). |
| 204 | + */ |
| 205 | + if (check_mul_overflow(sizeof(*swnodes->ports) + |
| 206 | + sizeof(*swnodes->nodes) * 2 + |
| 207 | + sizeof(*swnodes->nodeptrs) * 2, |
| 208 | + port_count, &alloc_size) || |
| 209 | + check_add_overflow(sizeof(*swnodes) + |
| 210 | + sizeof(*swnodes->nodes) + |
| 211 | + sizeof(*swnodes->nodeptrs) * 2, |
| 212 | + alloc_size, &alloc_size)) { |
| 213 | + acpi_handle_info(csi2->handle, |
| 214 | + "too many _CRS CSI-2 resource handles (%zu)", |
| 215 | + port_count); |
| 216 | + return; |
| 217 | + } |
| 218 | + |
| 219 | + swnodes = kmalloc(alloc_size, GFP_KERNEL); |
| 220 | + if (!swnodes) |
| 221 | + return; |
| 222 | + |
| 223 | + swnodes->ports = (struct acpi_device_software_node_port *)(swnodes + 1); |
| 224 | + swnodes->nodes = (struct software_node *)(swnodes->ports + port_count); |
| 225 | + swnodes->nodeptrs = (const struct software_node **)(swnodes->nodes + 1 + |
| 226 | + 2 * port_count); |
| 227 | + swnodes->num_ports = port_count; |
| 228 | + |
| 229 | + for (i = 0; i < 2 * port_count + 1; i++) |
| 230 | + swnodes->nodeptrs[i] = &swnodes->nodes[i]; |
| 231 | + |
| 232 | + swnodes->nodeptrs[i] = NULL; |
| 233 | + |
| 234 | + for (i = 0; i < port_count; i++) |
| 235 | + swnodes->ports[i].port_nr = NO_CSI2_PORT; |
| 236 | + |
| 237 | + csi2->swnodes = swnodes; |
| 238 | +} |
| 239 | + |
| 240 | +/** |
| 241 | + * acpi_mipi_scan_crs_csi2 - Create ACPI _CRS CSI-2 software nodes |
| 242 | + * |
| 243 | + * Note that this function must be called before any struct acpi_device objects |
| 244 | + * are bound to any ACPI drivers or scan handlers, so it cannot assume the |
| 245 | + * existence of struct acpi_device objects for every device present in the ACPI |
| 246 | + * namespace. |
| 247 | + * |
| 248 | + * acpi_scan_lock in scan.c must be held when calling this function. |
| 249 | + */ |
| 250 | +void acpi_mipi_scan_crs_csi2(void) |
| 251 | +{ |
| 252 | + struct crs_csi2 *csi2; |
| 253 | + LIST_HEAD(aux_list); |
| 254 | + |
| 255 | + /* Count references to each ACPI handle in the CSI-2 connection graph. */ |
| 256 | + list_for_each_entry(csi2, &acpi_mipi_crs_csi2_list, entry) { |
| 257 | + struct crs_csi2_connection *conn; |
| 258 | + |
| 259 | + list_for_each_entry(conn, &csi2->connections, entry) { |
| 260 | + struct crs_csi2 *remote_csi2; |
| 261 | + |
| 262 | + csi2->port_count++; |
| 263 | + |
| 264 | + remote_csi2 = acpi_mipi_get_crs_csi2(conn->remote_handle); |
| 265 | + if (remote_csi2) { |
| 266 | + remote_csi2->port_count++; |
| 267 | + continue; |
| 268 | + } |
| 269 | + /* |
| 270 | + * The remote endpoint has no _CRS CSI-2 list entry yet, |
| 271 | + * so create one for it and add it to the list. |
| 272 | + */ |
| 273 | + acpi_mipi_add_crs_csi2(conn->remote_handle, &aux_list); |
| 274 | + } |
| 275 | + } |
| 276 | + list_splice(&aux_list, &acpi_mipi_crs_csi2_list); |
| 277 | + |
| 278 | + /* Allocate software nodes for representing the CSI-2 information. */ |
| 279 | + list_for_each_entry(csi2, &acpi_mipi_crs_csi2_list, entry) |
| 280 | + alloc_crs_csi2_swnodes(csi2); |
| 281 | +} |
| 282 | + |
| 283 | +/** |
| 284 | + * acpi_mipi_crs_csi2_cleanup - Free _CRS CSI-2 temporary data |
| 285 | + */ |
| 286 | +void acpi_mipi_crs_csi2_cleanup(void) |
| 287 | +{ |
| 288 | + struct crs_csi2 *csi2, *csi2_tmp; |
| 289 | + |
| 290 | + list_for_each_entry_safe(csi2, csi2_tmp, &acpi_mipi_crs_csi2_list, entry) |
| 291 | + acpi_mipi_del_crs_csi2(csi2); |
| 292 | +} |
0 commit comments