// SPDX-License-Identifier: GPL-2.0 /* * Intel Speed Select -- Read HFI events for OOB * Copyright (c) 2022 Intel Corporation. */ /* * This file incorporates work covered by the following copyright and * permission notice: * WPA Supplicant - driver interaction with Linux nl80211/cfg80211 * Copyright (c) 2003-2008, Jouni Malinen <j@w1.fi> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * Alternatively, this software may be distributed under the terms of * BSD license. * * Requires * libnl-genl-3-dev * * For Fedora/CenOS * dnf install libnl3-devel * For Ubuntu * apt install libnl-3-dev libnl-genl-3-dev */ #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/file.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <getopt.h> #include <signal.h> #include <netlink/genl/genl.h> #include <netlink/genl/family.h> #include <netlink/genl/ctrl.h> #include <linux/thermal.h> #include "isst.h" struct hfi_event_data { struct nl_sock *nl_handle; struct nl_cb *nl_cb; }; struct hfi_event_data drv; static int ack_handler(struct nl_msg *msg, void *arg) { int *err = arg; *err = 0; return NL_STOP; } static int finish_handler(struct nl_msg *msg, void *arg) { int *ret = arg; *ret = 0; return NL_SKIP; } static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) { int *ret = arg; *ret = err->error; return NL_SKIP; } static int seq_check_handler(struct nl_msg *msg, void *arg) { return NL_OK; } static int send_and_recv_msgs(struct hfi_event_data *drv, struct nl_msg *msg, int (*valid_handler)(struct nl_msg *, void *), void *valid_data) { struct nl_cb *cb; int err = -ENOMEM; cb = nl_cb_clone(drv->nl_cb); if (!cb) goto out; err = nl_send_auto_complete(drv->nl_handle, msg); if (err < 0) goto out; err = 1; nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err); nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err); nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err); if (valid_handler) nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, valid_handler, valid_data); while (err > 0) nl_recvmsgs(drv->nl_handle, cb); out: nl_cb_put(cb); nlmsg_free(msg); return err; } struct family_data { const char *group; int id; }; static int family_handler(struct nl_msg *msg, void *arg) { struct family_data *res = arg; struct nlattr *tb[CTRL_ATTR_MAX + 1]; struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); struct nlattr *mcgrp; int i; nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL); if (!tb[CTRL_ATTR_MCAST_GROUPS]) return NL_SKIP; nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], i) { struct nlattr *tb2[CTRL_ATTR_MCAST_GRP_MAX + 1]; nla_parse(tb2, CTRL_ATTR_MCAST_GRP_MAX, nla_data(mcgrp), nla_len(mcgrp), NULL); if (!tb2[CTRL_ATTR_MCAST_GRP_NAME] || !tb2[CTRL_ATTR_MCAST_GRP_ID] || strncmp(nla_data(tb2[CTRL_ATTR_MCAST_GRP_NAME]), res->group, nla_len(tb2[CTRL_ATTR_MCAST_GRP_NAME])) != 0) continue; res->id = nla_get_u32(tb2[CTRL_ATTR_MCAST_GRP_ID]); break; } return 0; } static int nl_get_multicast_id(struct hfi_event_data *drv, const char *family, const char *group) { struct nl_msg *msg; int ret = -1; struct family_data res = { group, -ENOENT }; msg = nlmsg_alloc(); if (!msg) return -ENOMEM; genlmsg_put(msg, 0, 0, genl_ctrl_resolve(drv->nl_handle, "nlctrl"), 0, 0, CTRL_CMD_GETFAMILY, 0); NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family); ret = send_and_recv_msgs(drv, msg, family_handler, &res); msg = NULL; if (ret == 0) ret = res.id; nla_put_failure: nlmsg_free(msg); return ret; } struct perf_cap { int cpu; int perf; int eff; }; static void process_hfi_event(struct perf_cap *perf_cap) { struct isst_id id; set_isst_id(&id, perf_cap->cpu); process_level_change(&id); } static int handle_event(struct nl_msg *n, void *arg) { struct nlmsghdr *nlh = nlmsg_hdr(n); struct genlmsghdr *genlhdr = genlmsg_hdr(nlh); struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1]; int ret; struct perf_cap perf_cap = {0}; ret = genlmsg_parse(nlh, 0, attrs, THERMAL_GENL_ATTR_MAX, NULL); debug_printf("Received event %d parse_rer:%d\n", genlhdr->cmd, ret); if (genlhdr->cmd == THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE) { struct nlattr *cap; int j, index = 0; debug_printf("THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE\n"); nla_for_each_nested(cap, attrs[THERMAL_GENL_ATTR_CPU_CAPABILITY], j) { switch (index) { case 0: perf_cap.cpu = nla_get_u32(cap); break; case 1: perf_cap.perf = nla_get_u32(cap); break; case 2: perf_cap.eff = nla_get_u32(cap); break; default: break; } ++index; if (index == 3) { index = 0; process_hfi_event(&perf_cap); } } } return 0; } static int _hfi_exit; static int check_hf_suport(void) { unsigned int eax = 0, ebx = 0, ecx = 0, edx = 0; __cpuid(6, eax, ebx, ecx, edx); if (eax & BIT(19)) return 1; return 0; } int hfi_main(void) { struct nl_sock *sock; struct nl_cb *cb; int err = 0; int mcast_id; if (!check_hf_suport()) { fprintf(stderr, "CPU Doesn't support HFI\n"); return -1; } sock = nl_socket_alloc(); if (!sock) { fprintf(stderr, "nl_socket_alloc failed\n"); return -1; } if (genl_connect(sock)) { fprintf(stderr, "genl_connect(sk_event) failed\n"); goto free_sock; } drv.nl_handle = sock; drv.nl_cb = cb = nl_cb_alloc(NL_CB_DEFAULT); if (drv.nl_cb == NULL) { printf("Failed to allocate netlink callbacks"); goto free_sock; } mcast_id = nl_get_multicast_id(&drv, THERMAL_GENL_FAMILY_NAME, THERMAL_GENL_EVENT_GROUP_NAME); if (mcast_id < 0) { fprintf(stderr, "nl_get_multicast_id failed\n"); goto free_sock; } if (nl_socket_add_membership(sock, mcast_id)) { fprintf(stderr, "nl_socket_add_membership failed"); goto free_sock; } nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, seq_check_handler, 0); nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, handle_event, NULL); debug_printf("hfi is initialized\n"); while (!_hfi_exit && !err) { err = nl_recvmsgs(sock, cb); debug_printf("nl_recv_message err:%d\n", err); } return 0; /* Netlink library doesn't have calls to dealloc cb or disconnect */ free_sock: nl_socket_free(sock); return -1; } void hfi_exit(void) { _hfi_exit = 1; }