| // SPDX-License-Identifier: LGPL-2.1+ | |
| // Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> | |
| #include <errno.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <thermal.h> | |
| #include "thermal_nl.h" | |
| struct handler_args { | |
| const char *group; | |
| int id; | |
| }; | |
| static __thread int err; | |
| static __thread int done; | |
| static int nl_seq_check_handler(struct nl_msg *msg, void *arg) | |
| { | |
| return NL_OK; | |
| } | |
| static int nl_error_handler(struct sockaddr_nl *nla, struct nlmsgerr *nl_err, | |
| void *arg) | |
| { | |
| int *ret = arg; | |
| if (ret) | |
| *ret = nl_err->error; | |
| return NL_STOP; | |
| } | |
| static int nl_finish_handler(struct nl_msg *msg, void *arg) | |
| { | |
| int *ret = arg; | |
| if (ret) | |
| *ret = 1; | |
| return NL_OK; | |
| } | |
| static int nl_ack_handler(struct nl_msg *msg, void *arg) | |
| { | |
| int *ret = arg; | |
| if (ret) | |
| *ret = 1; | |
| return NL_OK; | |
| } | |
| int nl_send_msg(struct nl_sock *sock, struct nl_cb *cb, struct nl_msg *msg, | |
| int (*rx_handler)(struct nl_msg *, void *), void *data) | |
| { | |
| if (!rx_handler) | |
| return THERMAL_ERROR; | |
| err = nl_send_auto_complete(sock, msg); | |
| if (err < 0) | |
| return err; | |
| nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, rx_handler, data); | |
| err = done = 0; | |
| while (err == 0 && done == 0) | |
| nl_recvmsgs(sock, cb); | |
| return err; | |
| } | |
| static int nl_family_handler(struct nl_msg *msg, void *arg) | |
| { | |
| struct handler_args *grp = arg; | |
| struct nlattr *tb[CTRL_ATTR_MAX + 1]; | |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); | |
| struct nlattr *mcgrp; | |
| int rem_mcgrp; | |
| nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), | |
| genlmsg_attrlen(gnlh, 0), NULL); | |
| if (!tb[CTRL_ATTR_MCAST_GROUPS]) | |
| return THERMAL_ERROR; | |
| nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) { | |
| struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1]; | |
| nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX, | |
| nla_data(mcgrp), nla_len(mcgrp), NULL); | |
| if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] || | |
| !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]) | |
| continue; | |
| if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]), | |
| grp->group, | |
| nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]))) | |
| continue; | |
| grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]); | |
| break; | |
| } | |
| return THERMAL_SUCCESS; | |
| } | |
| static int nl_get_multicast_id(struct nl_sock *sock, struct nl_cb *cb, | |
| const char *family, const char *group) | |
| { | |
| struct nl_msg *msg; | |
| int ret = 0, ctrlid; | |
| struct handler_args grp = { | |
| .group = group, | |
| .id = -ENOENT, | |
| }; | |
| msg = nlmsg_alloc(); | |
| if (!msg) | |
| return THERMAL_ERROR; | |
| ctrlid = genl_ctrl_resolve(sock, "nlctrl"); | |
| genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0); | |
| nla_put_string(msg, CTRL_ATTR_FAMILY_NAME, family); | |
| ret = nl_send_msg(sock, cb, msg, nl_family_handler, &grp); | |
| if (ret) | |
| goto nla_put_failure; | |
| ret = grp.id; | |
| nla_put_failure: | |
| nlmsg_free(msg); | |
| return ret; | |
| } | |
| int nl_thermal_connect(struct nl_sock **nl_sock, struct nl_cb **nl_cb) | |
| { | |
| struct nl_cb *cb; | |
| struct nl_sock *sock; | |
| cb = nl_cb_alloc(NL_CB_DEFAULT); | |
| if (!cb) | |
| return THERMAL_ERROR; | |
| sock = nl_socket_alloc(); | |
| if (!sock) | |
| goto out_cb_free; | |
| if (genl_connect(sock)) | |
| goto out_socket_free; | |
| if (nl_cb_err(cb, NL_CB_CUSTOM, nl_error_handler, &err) || | |
| nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, nl_finish_handler, &done) || | |
| nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, nl_ack_handler, &done) || | |
| nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, nl_seq_check_handler, &done)) | |
| return THERMAL_ERROR; | |
| *nl_sock = sock; | |
| *nl_cb = cb; | |
| return THERMAL_SUCCESS; | |
| out_socket_free: | |
| nl_socket_free(sock); | |
| out_cb_free: | |
| nl_cb_put(cb); | |
| return THERMAL_ERROR; | |
| } | |
| void nl_thermal_disconnect(struct nl_sock *nl_sock, struct nl_cb *nl_cb) | |
| { | |
| nl_close(nl_sock); | |
| nl_socket_free(nl_sock); | |
| nl_cb_put(nl_cb); | |
| } | |
| int nl_unsubscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb, | |
| const char *group) | |
| { | |
| int mcid; | |
| mcid = nl_get_multicast_id(nl_sock, nl_cb, THERMAL_GENL_FAMILY_NAME, | |
| group); | |
| if (mcid < 0) | |
| return THERMAL_ERROR; | |
| if (nl_socket_drop_membership(nl_sock, mcid)) | |
| return THERMAL_ERROR; | |
| return THERMAL_SUCCESS; | |
| } | |
| int nl_subscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb, | |
| const char *group) | |
| { | |
| int mcid; | |
| mcid = nl_get_multicast_id(nl_sock, nl_cb, THERMAL_GENL_FAMILY_NAME, | |
| group); | |
| if (mcid < 0) | |
| return THERMAL_ERROR; | |
| if (nl_socket_add_membership(nl_sock, mcid)) | |
| return THERMAL_ERROR; | |
| return THERMAL_SUCCESS; | |
| } |